O utilitário ugrep-indexer indexa arquivos recursivamente para acelerar o grepping recursivo.
Além disso, o conteúdo dos arquivos compactados e compactados é indexado quando especificado com uma opção de linha de comando. Isso elimina a busca neles quando nenhum de seus conteúdos corresponde aos padrões especificados.
ugrep é um pesquisador rápido de arquivos compatível com grep que suporta pesquisa baseada em índice. A pesquisa baseada em índice pode ser significativamente mais rápida em sistemas de arquivos lentos e quando o cache do sistema de arquivos é ineficaz: se o sistema de arquivos em uma unidade pesquisada não estiver armazenado em cache na RAM, ou seja, estiver "frio", a indexação acelerará a pesquisa. Ele pesquisa apenas os arquivos que podem corresponder a um padrão regex especificado usando um índice do arquivo. Este índice permite uma verificação rápida se existe uma possível correspondência, evitando assim a busca em todos os arquivos.
A pesquisa baseada em indexação com ugrep é segura e nunca ignora arquivos atualizados que agora podem corresponder. Se quaisquer arquivos e diretórios forem adicionados ou alterados após a indexação, a pesquisa sempre pesquisará essas adições e alterações feitas no sistema de arquivos, comparando os registros de data e hora do arquivo e do diretório com o registro de data e hora da indexação.
Quando muitos arquivos são adicionados ou alterados após a indexação, podemos querer reindexar para atualizar os índices. A reindexação é incremental, portanto não levará tanto tempo quanto o processo de indexação inicial.
Um exemplo típico, mas pequeno, de pesquisa baseada em índice, por exemplo, no repositório ugrep v3.12.6 colocado em uma unidade separada:
$ 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
A pesquisa normal em um sistema de arquivos frio sem indexação leva 1,02 segundos após desmontar a drive
e montá-la novamente para limpar o cache do FS e registrar o efeito da indexação:
$ 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 leva mais tempo com 1,18 segundos para a mesma pesquisa fria (ripgrep ignora arquivos binários por padrão, então a opção -I
não é especificada):
$ time rg -l 'std::chrono'
src/ugrep.cpp
1.18 real 0.01 user 0.06 sys
Por outro lado, com a indexação, a pesquisa em um sistema de arquivos frio leva apenas 0,0487 segundos com o ugrep, que é 21 vezes mais rápido, após desmontar drive
e montá-la novamente para limpar o cache do FS e registrar o efeito da indexação:
$ 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
Sempre há alguma variação no tempo decorrido, sendo 0,0487 segundos o melhor tempo de quatro execuções de pesquisa que produziram um intervalo de tempo de pesquisa de 0,0487 (aceleração de 21x) a 0,0983 segundos (aceleração de 10x).
O aumento de velocidade pode ser significativamente maior em geral em comparação com esta pequena demonstração, dependendo de vários fatores, do tamanho dos arquivos indexados, da velocidade de leitura do sistema de arquivos e assumindo que a maioria dos arquivos está fria.
O algoritmo de indexação que projetei é provavelmente monotônico : uma maior precisão garante um maior desempenho de pesquisa, reduzindo a taxa de falsos positivos, mas também aumenta a sobrecarga de armazenamento do índice. Da mesma forma, uma precisão menor diminui o desempenho da pesquisa, mas também reduz a sobrecarga de armazenamento do índice. Portanto, chamei meu indexador de indexador monotônico .
Se o espaço de armazenamento de arquivos for escasso, podemos reduzir a sobrecarga de armazenamento do índice especificando uma precisão de indexação mais baixa.
Indexar o exemplo acima com nível 0 (opção -0
) reduz a sobrecarga de armazenamento de indexação em 8,6 vezes, de 4.256 bytes por arquivo para míseros 490 bytes por arquivo:
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
A pesquisa indexada ainda é muito mais rápida em 12x do que a não indexada neste exemplo, com 16 arquivos realmente pesquisados (15 falsos positivos):
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
Os padrões Regex que são mais complexos do que este exemplo podem ter uma taxa de falsos positivos mais alta naturalmente, que é a taxa de arquivos considerados possivelmente correspondentes, quando não o são. Uma taxa de falsos positivos mais alta pode reduzir a velocidade de pesquisa quando a taxa for grande o suficiente para causar impacto.
A tabela a seguir mostra como a precisão da indexação afeta o armazenamento da indexação e o ruído médio por arquivo indexado. As colunas mais à direita mostram a velocidade de pesquisa e a taxa de falsos positivos para ugrep --index -I -l 'std::chrono'
:
conta. | armazenamento de índice (KB) | ruído médio | falsos positivos | tempo(s) de pesquisa |
---|---|---|---|---|
-0 | 631 | 42% | 15 | 0,0722 |
-1 | 1276 | 39% | 1 | 0,0506 |
-2 | 1576 | 36% | 0 | 0,0487 |
-3 | 2692 | 31% | 0 | infeliz |
-4 | 2966 | 28% | 0 | infeliz |
-5 | 4953 | 23% | 0 | infeliz |
-6 | 5474 | 19% | 0 | infeliz |
-7 | 9513 | 15% | 0 | infeliz |
-8 | 10889 | 11% | 0 | infeliz |
-9 | 13388 | 7% | 0 | infeliz |
Se a regex especificada corresponder a muitos outros padrões possíveis, por exemplo, com a pesquisa ugrep --index -I -l '(todo|TODO)[: ]'
, então poderemos observar uma taxa maior de falsos positivos entre os 1317 arquivos pesquisados, resultando em tempos de pesquisa um pouco mais longos:
conta. | falsos positivos | tempo(s) de pesquisa |
---|---|---|
-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 | infeliz |
-7 | 0 | infeliz |
-8 | 0 | infeliz |
-9 | 0 | infeliz |
Precisão -4
é o padrão (de -5
anteriormente em versões mais antigas), que tende a funcionar muito bem para pesquisas com padrões regex de complexidade modesta.
Uma palavra de cautela. Sempre há uma pequena sobrecarga para verificar os índices. Isso significa que se todos os arquivos já estiverem armazenados em cache na RAM, porque os arquivos foram pesquisados ou lidos recentemente, a indexação não irá necessariamente acelerar a pesquisa, obviamente. Nesse caso, uma pesquisa não indexada pode ser mais rápida. Além disso, uma pesquisa baseada em índice tem um tempo de inicialização mais longo. Esse tempo de inicialização aumenta quando são usadas classes de caracteres Unicode e curingas que devem ser convertidas em tabelas hash.
Para resumir, a pesquisa baseada em índice é mais eficaz ao pesquisar muitos arquivos frios e quando os padrões regex não correspondem muito, ou seja, queremos limitar o uso de repetições ilimitadas *
e +
e limitar o uso de classes de caracteres Unicode quando possível. Isso reduz o tempo de inicialização do ugrep e limita a taxa de correspondências de padrões falsos positivos (veja também as perguntas e respostas abaixo).
Indexe recursiva e incrementalmente todos os arquivos não binários mostrando o progresso:
ugrep-indexer -I -v
Indexe recursiva e incrementalmente todos os arquivos não binários, incluindo arquivos não binários armazenados em arquivos e em arquivos compactados, mostrando o progresso:
ugrep-indexer -z -I -v
Indexe incrementalmente todos os arquivos não binários, incluindo arquivos e arquivos compactados, mostre o progresso, siga links simbólicos para arquivos (mas não para diretórios), mas não indexe arquivos e diretórios que correspondam aos globs em .gitignore:
ugrep-indexer -z -I -v -S -X
Force a reindexação de todos os arquivos não binários, incluindo arquivos e arquivos compactados, siga links simbólicos para arquivos (mas não para diretórios), mas não indexe arquivos e diretórios que correspondam aos globs em .gitignore:
ugrep-indexer -f -z -I -v -S -X
O mesmo, mas reduza o armazenamento do arquivo de índice ao mínimo, diminuindo a precisão da indexação de 5 (padrão) para 0:
ugrep-indexer -f -0 -z -I -v -S -X
Aumente o desempenho da pesquisa aumentando a precisão da indexação de 5 (padrão) para 7 ao custo de arquivos de índice maiores:
ugrep-indexer -f7zIvSX
Exclua recursivamente todos os arquivos de índice ._UG#_Store
ocultos para restaurar a árvore de diretórios para não indexada:
ugrep-indexer -d
Configure e compile com:
./build.sh
Se desejar, mas não for obrigatório, instale com:
sudo make install
Adicione uma opção para criar um arquivo de índice, por exemplo, especificado explicitamente para ugrep. Isso poderia melhorar ainda mais a velocidade da pesquisa indexada se o arquivo de índice estiver localizado em um sistema de arquivos rápido. Caso contrário, não espere muitas melhorias ou até mesmo uma possível lentidão, já que um único arquivo de índice não pode ser pesquisado simultaneamente e mais entradas de índice serão verificadas quando na verdade os diretórios forem ignorados (ignorando seus índices também). As experiências dirão. Uma advertência crítica desta abordagem é que a pesquisa baseada em índice com ugrep --index
não é mais segura: arquivos novos e modificados que ainda não estão indexados não serão pesquisados.
Cada filtro Bloom de N-gram tem seu próprio "nível de bits" na tabela hash para evitar conflitos de hash. Por exemplo, 2 gramas não compartilham nenhum bit com 3 gramas. Isso garante que nunca teremos falsos positivos com caracteres falsamente correspondidos que na verdade não fazem parte do padrão. No entanto, o espaço de bits de 1 grama (caractere único) é pequeno (no máximo 256 bits). Portanto, desperdiçamos alguns bits quando as tabelas hash são maiores. Uma abordagem possível para reduzir o desperdício é combinar 1 grama com 2 gramas para compartilhar o mesmo espaço de bits. Isso é fácil de fazer se considerarmos 1 grama igual a 2 gramas com o segundo caractere definido como