Утилита ugrep-indexer рекурсивно индексирует файлы, чтобы ускорить рекурсивный поиск.
Также содержимое архивов и сжатых файлов индексируется, если указано в параметре командной строки. Это исключает их поиск, когда ни одно из их содержимого не соответствует указанным шаблонам.
ugrep — это быстрый поиск файлов, совместимый с grep, который поддерживает поиск на основе индекса. Поиск по индексу может быть значительно быстрее на медленных файловых системах и когда кэширование файловой системы неэффективно: если файловая система на искомом диске не кэшируется в оперативной памяти, т.е. она «холодная», то индексирование ускорит поиск. Он ищет только те файлы, которые могут соответствовать указанному шаблону регулярного выражения, используя индекс файла. Этот индекс позволяет быстро проверить наличие потенциального совпадения, поэтому мы избегаем поиска по всем файлам.
Индексированный поиск с помощью Ugrep безопасен и никогда не пропускает обновленные файлы, которые теперь могут совпадать. Если какие-либо файлы и каталоги добавляются или изменяются после индексирования, то поиск всегда будет выполнять поиск этих добавлений и изменений, внесенных в файловую систему, путем сравнения меток времени файлов и каталогов с отметкой времени индексации.
Если после индексирования добавляется или изменяется много файлов, мы можем захотеть выполнить повторную индексацию, чтобы обновить индексы. Повторная индексация является инкрементальной, поэтому она не займет столько времени, сколько первоначальный процесс индексации.
Типичный, но небольшой пример поиска по индексу, например в репозитории ugrep v3.12.6, размещенном на отдельном диске:
$ cd drive/ugrep
$ ugrep-indexer -I
12247077 bytes scanned and indexed with 19% noise on average
1317 files indexed in 28 directories
28 new directories indexed
1317 new files indexed
0 modified files indexed
0 deleted files removed from indexes
128 binary files ignored with --ignore-binary
0 symbolic links skipped
0 devices skipped
5605227 bytes indexing storage increase at 4256 bytes/file
Обычный поиск в холодной файловой системе без индексации занимает 1,02 секунды после отключения drive
и повторного монтирования, чтобы очистить кэш ФС и записать эффект индексации:
$ ugrep -I -l 'std::chrono' --stats
src/ugrep.cpp
Searched 1317 files in 28 directories in 1.02 seconds with 8 threads: 1 matching (0.07593%)
Ripgrep 13.0.0 занимает больше времени — 1,18 секунды для того же холодного поиска (ripgrep по умолчанию пропускает двоичные файлы, поэтому опция -I
не указана):
$ time rg -l 'std::chrono'
src/ugrep.cpp
1.18 real 0.01 user 0.06 sys
Напротив, при индексировании поиск в холодной файловой системе с помощью ugrep занимает всего 0,0487 секунды, что в 21 раз быстрее, после отключения drive
и повторного монтирования для очистки кэша FS для записи эффекта индексации:
$ ugrep --index -I -l 'std::chrono' --stats
src/ugrep.cpp
Searched 1317 files in 28 directories in 0.0487 seconds with 8 threads: 1 matching (0.07593%)
Skipped 1316 of 1317 files with non-matching indexes
Всегда существует некоторая разница в затраченном времени: 0,0487 секунды — лучшее время из четырех прогонов поиска, которые дали диапазон времени поиска от 0,0487 (ускорение в 21 раз) до 0,0983 секунды (ускорение в 10 раз).
В целом прирост скорости может быть значительно выше по сравнению с этой небольшой демонстрацией, в зависимости от нескольких факторов: размера индексируемых файлов, скорости чтения файловой системы и предположения, что большинство файлов являются холодными.
Алгоритм индексации, который я разработал, является доказуемо монотонным : более высокая точность гарантирует повышение производительности поиска за счет уменьшения количества ложных срабатываний, но также увеличивает накладные расходы на хранение индекса. Аналогичным образом, более низкая точность снижает производительность поиска, но также снижает накладные расходы на хранение индекса. Поэтому я назвал свой индексатор монотонным индексатором .
Если пространство для хранения файлов ограничено, мы можем снизить нагрузку на хранилище индекса, указав более низкую точность индексации.
Индексирование приведенного выше примера с уровнем 0 (опция -0
) уменьшает накладные расходы на индексное хранилище в 8,6 раз, с 4256 байт на файл до жалких 490 байт на файл:
12247077 bytes scanned and indexed with 42% noise on average
1317 files indexed in 28 directories
0 new directories indexed
1317 new files indexed
0 modified files indexed
0 deleted files removed from indexes
128 binary files ignored with --ignore-binary
0 symbolic links skipped
0 devices skipped
646123 bytes indexing storage increase at 490 bytes/file
В этом примере индексированный поиск по-прежнему выполняется в 12 раз быстрее, чем неиндексированный: фактически было найдено 16 файлов (15 ложных срабатываний):
Searched 1317 files in 28 directories in 0.0722 seconds with 8 threads: 1 matching (0.07593%)
Skipped 1301 of 1317 files with non-matching indexes
Более сложные шаблоны регулярных выражений, чем в этом примере, естественным образом могут иметь более высокий уровень ложных срабатываний, то есть количество файлов, которые считаются возможно совпадающими, хотя это не так. Более высокий уровень ложных срабатываний может снизить скорость поиска, если этот показатель достаточно велик, чтобы оказать влияние.
В следующей таблице показано, как точность индексирования влияет на хранилище индексации и средний шум на индексируемый файл. Крайние правые столбцы показывают скорость поиска и частоту ложных срабатываний для ugrep --index -I -l 'std::chrono'
:
соотв. | индексное хранилище (КБ) | средний шум | ложные срабатывания | время поиска (с) |
---|---|---|---|---|
-0 | 631 | 42% | 15 | 0,0722 |
-1 | 1276 | 39% | 1 | 0,0506 |
-2 | 1576 г. | 36% | 0 | 0,0487 |
-3 | 2692 | 31% | 0 | разблокировать |
-4 | 2966 | 28% | 0 | разблокировать |
-5 | 4953 | 23% | 0 | разблокировать |
-6 | 5474 | 19% | 0 | разблокировать |
-7 | 9513 | 15% | 0 | разблокировать |
-8 | 10889 | 11% | 0 | разблокировать |
-9 | 13388 | 7% | 0 | разблокировать |
Если указанное регулярное выражение соответствует гораздо большему количеству возможных шаблонов, например, с поиском ugrep --index -I -l '(todo|TODO)[: ]'
, то мы можем наблюдать более высокий уровень ложных срабатываний среди 1317 найденных файлов, что приводит к немного большему времени поиска:
соотв. | ложные срабатывания | время поиска (с) |
---|---|---|
-0 | 189 | 0,292 |
-1 | 69 | 0,122 |
-2 | 43 | 0,103 |
-3 | 19 | 0,101 |
-4 | 16 | 0,097 |
-5 | 2 | 0,096 |
-6 | 1 | разблокировать |
-7 | 0 | разблокировать |
-8 | 0 | разблокировать |
-9 | 0 | разблокировать |
Точность -4
используется по умолчанию (ранее -5
в более старых версиях), что очень хорошо работает при поиске по шаблонам регулярных выражений умеренной сложности.
Одно слово предостережения. Всегда есть небольшие накладные расходы на проверку индексов. Это означает, что если все файлы уже кэшированы в оперативной памяти, поскольку файлы были найдены или прочитаны недавно, то индексирование, очевидно, не обязательно ускорит поиск. В этом случае неиндексированный поиск может быть быстрее. Кроме того, поиск на основе индекса имеет более длительное время запуска. Это время запуска увеличивается, когда используются классы символов Юникода и подстановочные знаки, которые необходимо преобразовать в хэш-таблицы.
Подводя итог, можно сказать, что поиск на основе индекса наиболее эффективен при поиске большого количества «холодных» файлов и когда шаблоны регулярных выражений не слишком совпадают, т. е. мы хотим ограничить использование неограниченного количества повторений *
и +
и ограничить использование классов символов Юникода, когда возможный. Это сокращает время запуска Ugrep и ограничивает количество ложных совпадений с шаблоном (см. также вопросы и ответы ниже).
Рекурсивно и постепенно индексируйте все недвоичные файлы, показывающие прогресс:
ugrep-indexer -I -v
Рекурсивно и постепенно индексировать все недвоичные файлы, включая недвоичные файлы, хранящиеся в архивах и в сжатых файлах, показывая прогресс:
ugrep-indexer -z -I -v
Инкрементно индексировать все небинарные файлы, включая архивы и сжатые файлы, показывать прогресс, переходить по символическим ссылкам на файлы (но не на каталоги), но не индексировать файлы и каталоги, соответствующие globs в .gitignore:
ugrep-indexer -z -I -v -S -X
Принудительно переиндексировать все недвоичные файлы, включая архивы и сжатые файлы, следовать символическим ссылкам на файлы (но не на каталоги), но не индексировать файлы и каталоги, соответствующие globs в .gitignore:
ugrep-indexer -f -z -I -v -S -X
То же самое, но уменьшите объем хранилища индексных файлов до минимума, уменьшив точность индексирования с 5 (по умолчанию) до 0:
ugrep-indexer -f -0 -z -I -v -S -X
Повысьте производительность поиска, увеличив точность индексации с 5 (по умолчанию) до 7 за счет увеличения индексных файлов:
ugrep-indexer -f7zIvSX
Рекурсивно удалите все скрытые индексные файлы ._UG#_Store
чтобы восстановить неиндексированное дерево каталогов:
ugrep-indexer -d
Настройте и скомпилируйте с помощью:
./build.sh
Если желательно, но не обязательно, установите с помощью:
sudo make install
Добавьте опцию для создания одного индексного файла, например явно указанную в ugrep. Это может еще больше повысить скорость индексированного поиска, если индексный файл расположен в быстрой файловой системе. В противном случае не ждите значительного улучшения или даже возможного замедления, поскольку в одном индексном файле нельзя искать одновременно, и будет проверяться больше записей индекса, хотя на самом деле каталоги пропускаются (также пропуская их индексы). Эксперименты покажут. Важным предостережением этого подхода является то, что поиск по индексу с помощью ugrep --index
больше не безопасен: новые и измененные файлы, которые еще не проиндексированы, не будут искаться.
Каждый N-граммный фильтр Блума имеет свой собственный «битовый уровень» в хеш-таблице, чтобы избежать конфликтов хеширования. Например, 2-граммы не имеют общих битов с 3-граммами. Это гарантирует, что у нас никогда не будет ложных срабатываний при ошибочном сопоставлении символов, которые на самом деле не являются частью шаблона. Однако битовое пространство в 1 грамм (один символ) невелико (не более 256 бит). Поэтому мы теряем некоторые биты, когда хэш-таблицы больше. Возможный подход к сокращению потерь — объединить 1 грамм с 2 граммами для совместного использования одного и того же битового пространства. Это легко сделать, если мы считаем, что 1-грамма равна 2-грамме со вторым символом, установленным на