Das Dienstprogramm ugrep-indexer indiziert Dateien rekursiv, um das rekursive Grepping zu beschleunigen.
Auch der Inhalt von Archiven und komprimierten Dateien wird indiziert, wenn dies mit einer Befehlszeilenoption angegeben wird. Dadurch entfällt die Suche, wenn keiner ihrer Inhalte mit den angegebenen Mustern übereinstimmt.
ugrep ist ein grep-kompatibler schneller Dateisucher, der die indexbasierte Suche unterstützt. Die indexbasierte Suche kann auf langsamen Dateisystemen und wenn das Dateisystem-Caching ineffektiv ist, erheblich schneller sein: Wenn das Dateisystem auf einem durchsuchten Laufwerk nicht im RAM zwischengespeichert ist, also „kalt“ ist, beschleunigt die Indizierung die Suche. Es durchsucht nur die Dateien, die möglicherweise einem angegebenen Regex-Muster entsprechen, indem ein Index der Datei verwendet wird. Dieser Index ermöglicht eine schnelle Überprüfung, ob eine mögliche Übereinstimmung vorliegt, sodass wir nicht alle Dateien durchsuchen müssen.
Die indizierte Suche mit ugrep ist sicher und überspringt niemals aktualisierte Dateien, die jetzt möglicherweise übereinstimmen. Wenn nach der Indizierung Dateien und Verzeichnisse hinzugefügt oder geändert werden, werden bei der Suche immer diese am Dateisystem vorgenommenen Hinzufügungen und Änderungen durchsucht, indem die Zeitstempel der Dateien und Verzeichnisse mit dem Zeitstempel der Indizierung verglichen werden.
Wenn nach der Indizierung viele Dateien hinzugefügt oder geändert werden, möchten wir möglicherweise eine Neuindizierung durchführen, um die Indizes auf den neuesten Stand zu bringen. Die Neuindizierung erfolgt inkrementell und dauert daher nicht so lange wie der anfängliche Indexierungsprozess.
Ein typisches, aber kleines Beispiel für eine indexbasierte Suche, zum Beispiel im ugrep v3.12.6-Repository auf einem separaten Laufwerk:
$ 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
Die normale Suche in einem kalten Dateisystem ohne Indizierung dauert 1,02 Sekunden, nachdem das drive
ausgehängt und erneut gemountet wurde, um den FS-Cache zu leeren und die Auswirkungen der Indizierung aufzuzeichnen:
$ 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 benötigt mit 1,18 Sekunden länger für die gleiche Kaltsuche (Ripgrep überspringt standardmäßig Binärdateien, daher ist die Option -I
nicht angegeben):
$ time rg -l 'std::chrono'
src/ugrep.cpp
1.18 real 0.01 user 0.06 sys
Im Gegensatz dazu dauert die Suche in einem kalten Dateisystem mit ugrep bei der Indizierung nur 0,0487 Sekunden, was 21-mal schneller ist, nachdem drive
ausgehängt und erneut gemountet wurde, um den FS-Cache zu leeren und den Effekt der Indizierung aufzuzeichnen:
$ 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
Es gibt immer eine gewisse Abweichung in der verstrichenen Zeit, wobei 0,0487 Sekunden die beste Zeit von vier Suchläufen waren, die einen Suchzeitbereich von 0,0487 (21-fache Beschleunigung) bis 0,0983 Sekunden (10-fache Beschleunigung) ergaben.
Die Geschwindigkeitssteigerung kann im Vergleich zu dieser kleinen Demo im Allgemeinen deutlich höher ausfallen, abhängig von mehreren Faktoren, der Größe der indizierten Dateien, der Lesegeschwindigkeit des Dateisystems und der Annahme, dass die meisten Dateien kalt sind.
Der von mir entworfene Indexierungsalgorithmus ist nachweislich monoton : Eine höhere Genauigkeit garantiert eine höhere Suchleistung durch Reduzierung der Falsch-Positiv-Rate, erhöht aber auch den Speicheraufwand für den Index. Ebenso verringert eine geringere Genauigkeit die Suchleistung, verringert aber auch den Indexspeicheraufwand. Deshalb habe ich meinen Indexer einen monotonen Indexer genannt.
Wenn der Dateispeicherplatz knapp ist, können wir den Speicheraufwand für den Index verringern, indem wir eine geringere Indizierungsgenauigkeit festlegen.
Durch die Indizierung des Beispiels von oben mit Stufe 0 (Option -0
) wird der Speicheraufwand für die Indizierung um das 8,6-fache reduziert, von 4256 Byte pro Datei auf mickrige 490 Byte pro Datei:
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
Die indizierte Suche ist in diesem Beispiel immer noch um das 12-fache schneller als die nicht indizierte Suche, wobei tatsächlich 16 Dateien durchsucht wurden (15 Fehlalarme):
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
Regex-Muster, die komplexer sind als in diesem Beispiel, können naturgemäß eine höhere Falsch-Positiv-Rate aufweisen, d. h. die Rate der Dateien, die als möglicherweise übereinstimmend angesehen werden, obwohl dies nicht der Fall ist. Eine höhere Falsch-Positiv-Rate kann die Suchgeschwindigkeit verringern, wenn die Rate groß genug ist, um wirkungsvoll zu sein.
Die folgende Tabelle zeigt, wie sich die Indizierungsgenauigkeit auf den Indizierungsspeicher und den durchschnittlichen Lärm pro indizierter Datei auswirkt. Die Spalten ganz rechts zeigen die Suchgeschwindigkeit und die Falsch-Positiv-Rate für ugrep --index -I -l 'std::chrono'
:
gem. | Indexspeicher (KB) | durchschnittlicher Lärm | Fehlalarme | Suchzeit(en) |
---|---|---|---|---|
-0 | 631 | 42 % | 15 | 0,0722 |
-1 | 1276 | 39 % | 1 | 0,0506 |
-2 | 1576 | 36 % | 0 | 0,0487 |
-3 | 2692 | 31 % | 0 | unch |
-4 | 2966 | 28 % | 0 | unch |
-5 | 4953 | 23 % | 0 | unch |
-6 | 5474 | 19 % | 0 | unch |
-7 | 9513 | 15 % | 0 | unch |
-8 | 10889 | 11 % | 0 | unch |
-9 | 13388 | 7 % | 0 | unch |
Wenn der angegebene reguläre Ausdruck mit vielen weiteren möglichen Mustern übereinstimmt, beispielsweise mit der Suche ugrep --index -I -l '(todo|TODO)[: ]'
, dann beobachten wir möglicherweise eine höhere Rate falsch positiver Ergebnisse unter den 1317 durchsuchten Dateien. was zu etwas längeren Suchzeiten führt:
gem. | Fehlalarme | Suchzeit(en) |
---|---|---|
-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 | unch |
-7 | 0 | unch |
-8 | 0 | unch |
-9 | 0 | unch |
Genauigkeit -4
ist die Standardeinstellung (von -5
zuvor in älteren Versionen), die bei der Suche mit Regex-Mustern mittlerer Komplexität sehr gut funktioniert.
Ein Wort der Vorsicht. Die Überprüfung der Indizes verursacht immer einen kleinen Aufwand. Dies bedeutet, dass die Indizierung die Suche natürlich nicht unbedingt beschleunigt, wenn alle Dateien bereits im RAM zwischengespeichert sind, weil Dateien kürzlich durchsucht oder gelesen wurden. In diesem Fall ist eine nicht indizierte Suche möglicherweise schneller. Darüber hinaus hat eine indexbasierte Suche eine längere Startzeit. Diese Startzeit erhöht sich, wenn Unicode-Zeichenklassen und Platzhalter verwendet werden, die in Hash-Tabellen konvertiert werden müssen.
Zusammenfassend lässt sich sagen, dass die indexbasierte Suche am effektivsten ist, wenn viele kalte Dateien durchsucht werden und wenn Regex-Muster nicht allzu sehr übereinstimmen, d. h. wir möchten die Verwendung unbegrenzter Wiederholungen *
und +
und die Verwendung von Unicode-Zeichenklassen einschränken, wenn möglich. Dies verkürzt die Startzeit von ugrep und begrenzt die Rate falsch positiver Musterübereinstimmungen (siehe auch Fragen und Antworten unten).
Rekursiv und inkrementell alle nicht-binären Dateien indizieren, die den Fortschritt anzeigen:
ugrep-indexer -I -v
Indizieren Sie rekursiv und inkrementell alle nicht-binären Dateien, einschließlich nicht-binärer Dateien, die in Archiven und komprimierten Dateien gespeichert sind, und zeigen Sie den Fortschritt an:
ugrep-indexer -z -I -v
Indizieren Sie inkrementell alle nicht-binären Dateien, einschließlich Archiven und komprimierten Dateien, zeigen Sie den Fortschritt an, folgen Sie symbolischen Links zu Dateien (aber nicht zu Verzeichnissen), indizieren Sie jedoch keine Dateien und Verzeichnisse, die den Globs in .gitignore entsprechen:
ugrep-indexer -z -I -v -S -X
Erzwingen Sie die Neuindizierung aller nicht-binären Dateien, einschließlich Archiven und komprimierten Dateien, folgen Sie symbolischen Links zu Dateien (aber nicht zu Verzeichnissen), indizieren Sie jedoch keine Dateien und Verzeichnisse, die den Globs in .gitignore entsprechen:
ugrep-indexer -f -z -I -v -S -X
Das Gleiche, aber reduzieren Sie den Speicher der Indexdatei auf ein Minimum, indem Sie die Indexierungsgenauigkeit von 5 (Standard) auf 0 verringern:
ugrep-indexer -f -0 -z -I -v -S -X
Erhöhen Sie die Suchleistung, indem Sie die Indexierungsgenauigkeit von 5 (Standard) auf 7 erhöhen, allerdings auf Kosten größerer Indexdateien:
ugrep-indexer -f7zIvSX
Löschen Sie rekursiv alle versteckten ._UG#_Store
-Indexdateien, um den Verzeichnisbaum wieder in den nicht indizierten Zustand zu versetzen:
ugrep-indexer -d
Konfigurieren und kompilieren mit:
./build.sh
Falls gewünscht, aber nicht erforderlich, installieren Sie mit:
sudo make install
Fügen Sie eine Option zum Erstellen einer Indexdatei hinzu, z. B. explizit für ugrep angegeben. Dies könnte die Geschwindigkeit der indizierten Suche weiter verbessern, wenn sich die Indexdatei in einem schnellen Dateisystem befindet. Andernfalls ist keine große Verbesserung oder gar eine mögliche Verlangsamung zu erwarten, da eine einzelne Indexdatei nicht gleichzeitig durchsucht werden kann und mehr Indexeinträge überprüft werden, wenn tatsächlich Verzeichnisse übersprungen werden (wobei auch deren Indizes übersprungen werden). Experimente werden es zeigen. Ein entscheidender Vorbehalt dieses Ansatzes besteht darin, dass die indexbasierte Suche mit ugrep --index
nicht mehr sicher ist: Neue und geänderte Dateien, die noch nicht indiziert sind, werden nicht durchsucht.
Jeder N-Gramm-Bloom-Filter verfügt über eine eigene „Bit-Ebene“ in der Hash-Tabelle, um Hash-Konflikte zu vermeiden. Beispielsweise teilen sich 2-Gramm-Blöcke keine Bits mit 3-Gramm-Blöcken. Dadurch wird sichergestellt, dass es nie zu Fehlalarmen mit fälschlicherweise übereinstimmenden Zeichen kommt, die eigentlich nicht Teil des Musters sind. Allerdings ist der 1-Gramm-Bitraum (einzelnes Zeichen) klein (höchstens 256 Bit). Daher verschwenden wir einige Bits, wenn die Hash-Tabellen größer sind. Ein möglicher Ansatz zur Reduzierung von Verschwendung besteht darin, 1 Gramm mit 2 Gramm zu kombinieren, um den gleichen Bitraum zu teilen. Dies ist einfach, wenn wir davon ausgehen, dass ein 1-Gramm einem 2-Gramm entspricht und das zweite Zeichen auf