5 Punkte von GN⁺ 2023-12-01 | 1 Kommentare | Auf WhatsApp teilen
  • ripgrep (rg) ist ein in Rust geschriebenes Kommandozeilen-Suchwerkzeug, das den Komfort der Code-Suche im Stil von The Silver Searcher mit der Rohleistung auf dem Niveau von GNU grep verbindet und Binärdateien für Linux, Mac und Windows bereitstellt.
  • In 25 Benchmarks gab es kein Werkzeug, das ripgrep sowohl bei einzelnen großen Dateien als auch bei der Suche in großen Verzeichnissen in Performance und Genauigkeit klar überlegen war; auch die Kosten für Unicode-Unterstützung blieben gering.
  • Mit .gitignore-Verarbeitung, standardmäßigem Ausschluss versteckter und binärer Dateien, Dateityp-Filtern, optionaler PCRE2-Unterstützung, Suche in mehreren Encodings und komprimierten Dateien sowie Preprocessing-Filtern erweitert es den praktischen Einsatzbereich von Code-Suchwerkzeugen.
  • Die Unterschiede zwischen den Experimenten mit dem Linux-Kernel-Repository und OpenSubtitles2016 hängen stark von Literal-Optimierungen, der SIMD-Mehrmustersuche Teddy, Aho-Corasick, der Art der UTF-8-Decodierung, dem Zeilenzählen und den Kosten der .gitignore-Verarbeitung ab.
  • Beim parallelen Durchsuchen vieler kleiner Dateien können Memory Maps langsamer sein, während sie bei einer einzelnen großen Datei vorteilhaft sein können; ripgrep nutzt daher je nach Situation entweder die Suche über Zwischenpuffer oder über Memory Maps.

Die Position, auf die ripgrep abzielte

  • ripgrep ist ein Kommandozeilen-Suchwerkzeug, das sowohl den Komfort von Code-Suchwerkzeugen als auch die Performance von grep-artigen Werkzeugen anstrebt.
  • Verglichen werden GNU grep, git grep, The Silver Searcher (ag), Universal Code Grep (ucg), The Platinum Searcher (pt) und sift.
  • Die Benchmarks sollten im Kern drei Punkte überprüfen:
    • Es gibt kein Werkzeug, das ripgrep sowohl bei der Suche in einzelnen Dateien als auch in großen Verzeichnissen klar überlegen ist.
    • Es bietet vollständige Unicode-Unterstützung, ohne dafür hohe Performance-Kosten zu verlangen.
    • Beim gleichzeitigen Durchsuchen vieler Dateien können Memory Maps im Allgemeinen eher langsamer als schneller sein.
  • Der Autor ist der Entwickler von ripgrep und der zugrunde liegenden Regex-Engine und weist darauf hin, dass die Benchmarks ausgewählt und damit voreingenommen sein können.

Funktionen und Standardverhalten

  • Der Name der ausführbaren Datei von ripgrep lautet rg.
  • Die Standardsuche durchsucht das aktuelle Verzeichnis rekursiv, respektiert .gitignore und überspringt versteckte sowie binäre Dateien.
  • Auch .rgignore wird unterstützt; Muster in .rgignore haben Vorrang vor .gitignore.
  • Mit -u, -uu und -uuu lässt sich der Umfang erweitern: Ignore-Dateien ignorieren, versteckte Dateien einbeziehen und binäre Dateien einbeziehen.
    • rg -uuu ähnelt grep -a -r.
  • Dateityp-Filter werden unterstützt.
    • rg -tpy foo: nur Python-Dateien durchsuchen
    • rg -Tjs foo: JavaScript-Dateien ausschließen
    • Mit --type-add können neue Dateityp-Regeln hinzugefügt werden.
  • Außerdem bietet es viele Funktionen von grep.
    • Kontextausgabe
    • Suche nach mehreren Mustern
    • farbliche Hervorhebung
    • vollständige Unicode-Unterstützung
  • Die Standard-Regex-Engine unterstützt kein Look-around und keine Backreferences; wählt man mit -P jedoch die PCRE2-Engine, stehen diese Funktionen zur Verfügung.
  • Unterstützt werden auch eine teilweise automatische Erkennung von UTF-16 sowie die Angabe des Encodings über -E/--encoding.
    • Dazu gehören UTF-16, latin-1, GBK, EUC-JP, Shift_JIS usw.
  • Mit -z/--search-zip wird die Suche in komprimierten Dateien wie gzip, xz, lzma, bzip2 und lz4 unterstützt.
  • Auch beliebige Preprocessing-Filter wie PDF-Textextraktion, zusätzliche Dekomprimierung, Entschlüsselung und automatische Encoding-Erkennung werden unterstützt.

Gründe, es nicht zu verwenden

  • Wenn Portabilität und die Verfügbarkeit überall oberste Priorität haben, ist das standardkonforme und weit verbreitet installierte grep passend.
  • Wenn man von einer bestimmten Funktion oder einem Bug in einem anderen Werkzeug abhängt, kann ripgrep ungeeignet sein.
  • In einigen Performance-Edge-Cases können andere Werkzeuge besser funktionieren.
  • Wenn es nicht installiert werden kann oder die Plattform nicht unterstützt wird, lässt es sich ebenfalls nicht verwenden.

Arbeitsweise von grep-artigen Werkzeugen

  • Suchwerkzeuge durchlaufen grob drei Schritte:
    • zu durchsuchende Dateien sammeln
    • eigentliche Suche
    • Ergebnisse ausgeben
  • Da grep-artige Werkzeuge große Dateien gut durchsuchen müssen, ist die Performance der Regex-Engine wichtig.
  • ack-artige Werkzeuge müssen rekursive Verzeichnissuche und die Anwendung von Ignore-Regeln wie .gitignore schnell verarbeiten.
  • ripgrep versucht, beide Ansätze zu kombinieren:
    • schnelle Regex-Engine
    • parallele Suche
    • Filterung der Suchziele

Dateisammlung und Ignore-Verarbeitung

  • Bei ack-artigen Werkzeugen ist es wichtig, schnell zu entscheiden, welche Dateien im aktuellen Verzeichnis durchsucht werden sollen.
  • Die Performance beim Durchlaufen von Verzeichnissen hängt von der Zahl unnötiger stat-Aufrufe ab.
  • ripgrep verwendet einen rekursiven Verzeichnis-Iterator, der auf ein Minimum an Systemaufrufen abzielt.
  • Die Verarbeitung von .gitignore verursacht Kosten.
    • In jedem Verzeichnis müssen Ignore-Dateien gesucht werden.
    • Ignore-Muster müssen kompiliert werden.
    • Die Muster müssen auf alle Kandidatenpfade angewendet werden.
  • Im Linux-Kernel-Repository gab es 4.640 Verzeichnisse und 178 .gitignore-Dateien.
  • ripgrep versucht, die Semantik von .gitignore vollständiger zu unterstützen, und gibt dem zuletzt definierten passenden Muster Priorität.
  • ucg kann schneller sein, weil es statt .gitignore whitelist-basierte Glob-Regeln verwendet, kann dadurch aber Dateien mit unbekannten Erweiterungen übersehen.

Unterschiede bei Regex-Engines

  • Regex-Engines lassen sich im Allgemeinen in zwei Gruppen einteilen:
    • Backtracking-basiert: funktionsreich, kann bei bestimmten Eingaben aber auf exponentielle Laufzeit zurückfallen.
    • endliche Automaten-basiert: Funktionsumfang kann eingeschränkt sein, garantiert aber lineare Laufzeit bezogen auf die Länge des Suchtexts.
  • Die Engines der Werkzeuge sind wie folgt:
    • GNU grep, git grep: eigene Engines auf Basis endlicher Automaten
    • ripgrep: Rust-regex-Bibliothek, auf Basis endlicher Automaten
    • ag, ucg: PCRE-basiertes Backtracking
    • pt, sift: Go-regex-Bibliothek, auf Basis endlicher Automaten
  • ag und ucg können durch die Nutzung von PCRE dem schlechtesten Backtracking-Verhalten ausgesetzt sein.
  • Das Beispielmuster (a*)* c kann bei PCRE-basierten Werkzeugen Probleme verursachen, während die anderen in den Benchmarks betrachteten Werkzeuge es problemlos verarbeiten.

Literal-Optimierung und SIMD

  • Bei der Suche nach einfachen Zeichenketten kann Literal-Suchoptimierung wichtiger werden als die Regex-Engine.
  • Boyer-Moore ist ein klassischer Algorithmus für die Teilzeichenkettensuche und kann Routinen wie memchr nutzen, um Kandidatenpositionen schnell zu finden.
  • memchr-Implementierungen prüfen häufig mithilfe von SIMD-Instruktionen 16 Byte auf einmal und können einen Durchsatz von mehreren GB/s erreichen.
  • Die Rust-regex-Bibliothek extrahiert aggressiv Prefix- und Suffix-Literale aus Mustern.
    • foo|bar
    • (a|b)c
    • [ab]foo[yz]
    • (foo)?bar
    • (foo)*bar
    • (foo){3,6}
  • Wenn der gesamte reguläre Ausdruck in ein einzelnes Literal oder eine Literal-Alternation zerlegt werden kann, muss die zentrale Regex-Engine unter Umständen gar nicht verwendet werden.
  • ripgrep nutzt die Eigenschaft der zeilenweisen Ergebnisausgabe und extrahiert auch innere Literale.
    • Beispiel: Bei \w+foo\d+ wird zuerst foo gesucht, und nur die Kandidatenzeilen werden mit der Regex verifiziert.
  • Für die Suche nach mehreren Literalen verwendet GNU grep einen Commentz-Walter-ähnlichen Algorithmus, während Rust regex Aho-Corasick oder den Teddy-SIMD-Algorithmus nutzt.
  • Teddy ist ein SIMD-basierter Mehrmuster-Suchalgorithmus aus Intel Hyperscan und eine der zentralen Optimierungen, mit denen ripgrep GNU grep übertrifft.

Suchmethode: zeilenweise Suche vermeiden

  • Eine naive Implementierung liest eine Datei zeilenweise und wendet das Muster auf jede Zeile an, doch da Treffer bei den meisten Suchen selten sind, ist das ineffizient.
  • Suchwerkzeuge durchsuchen üblicherweise große Byte-Puffer auf einmal.
    • Datei per Memory Map abbilden
    • gesamte Datei in den Speicher lesen
    • schrittweise Suche mit einem Zwischenpuffer fester Größe
  • ripgrep, GNU grep und git grep unterstützen schrittweise Suche und sind damit sowohl auf Dateien als auch auf Streams anwendbar.
  • Schrittweise Suche ist schwer zu implementieren.
    • Berechnung von Zeilennummern
    • Behandlung von Fällen, in denen ein Puffer mitten in einer Zeile endet
    • Behandlung langer Zeilen
    • Behandlung von invert match
    • Ausgabe von Kontext um Treffer herum
  • ripgrep nimmt die Implementierungskomplexität in Kauf und nutzt schrittweise Suche; in Benchmarks zeigte es beim Durchsuchen vieler kleiner Dateien schnellere Ergebnisse als mit Memory Maps.

Ausgabe und Parallelität

  • Wenn bei paralleler Suche jeder Thread sofort ausgibt, können sich Ergebnisse aus unterschiedlichen Dateien vermischen
  • Alle parallelen Code-Suchwerkzeuge schreiben Suchergebnisse in einen Zwischenpuffer im Speicher und serialisieren nur die Ausgabephase
  • Dieser Ansatz ermöglicht es den Such-Threads, die eigentliche Suche parallel auszuführen
  • Der Nachteil ist, dass der Speicherverbrauch stark steigen kann, etwa bei einer 2-GB-Datei, in der jede Zeile matcht
  • ripgrep schreibt bei Suchen über stdin oder in einer einzelnen Datei ohne Zwischenpuffer direkt nach stdout

Benchmark-Methodik

  • Die Benchmarks sind nach Problemen aus Sicht der Endnutzer unterteilt
    • Suche in großen Code-Repositories
    • Suche in einer einzelnen großen Datei
  • Die Suchmuster sind auf einfache Literale, Alternation und leichte reguläre Ausdrücke ausgerichtet
  • Da sich das Standardverhalten der Tools unterscheidet, wurde für einen fairen Vergleich versucht, Bedingungen wie Zeilennummern, Unicode, .gitignore und Whitelists anzugleichen
  • Die Benchmark-Versionen waren folgende
    • ripgrep v0.1.2
    • GNU grep v2.25
    • git grep v2.7.4
    • ag Commit cda635, PCRE 8.38
    • ucg Commit 487bfb, PCRE 10.21 JIT
    • pt Commit 509368
    • sift Commit 2d175c
  • ack wurde ausgeschlossen, weil es damals deutlich langsamer war als die anderen Tools
  • Der Benchmark-Runner ist benchsuite, das Python 3.5 oder höher benötigt, und ist im ripgrep-Repository enthalten
  • Jeder Befehl wurde vor der Messung dreimal als Warm-up ausgeführt, damit der Korpus im OS Page Cache liegt
  • Jeder Befehl wurde zehnmal gemessen; Mittelwert und Standardabweichung wurden aufgezeichnet
  • Die Laufzeitumgebung war Amazon EC2 c3.2xlarge, Ubuntu 16.04, Xeon E5-2680 2,8 GHz, 16 GB Arbeitsspeicher und 80 GB SSD
  • Konfigurationslogs, zusammengefasste Ergebnisse und rohe CSV-Daten wurden ebenfalls veröffentlicht

Ergebnisse der Suche im Linux-Kernel-Code

  • Der Code-Suchbenchmark wurde in einem gebauten Linux-Kernel-Repository mit Commit d0acc7 ausgeführt
  • Der Grund für die Verwendung eines gebauten Kernel-Repositories war, dass Build-Artefakte im Repository verbleiben und Relevanz sowie Performance der Suchergebnisse beeinflussen können
  • In linux_literal_default zeigt die Suche nach dem einfachen Literal PM_RESUME die Unterschiede im Standardverhalten der einzelnen Tools
    • rg respektiert .gitignore und überspringt versteckte sowie binäre Dateien
    • ag und pt verhalten sich ähnlich, zählen aber Zeilen
    • ucg liest .gitignore nicht und sucht whitelist-basiert
    • sift durchsucht standardmäßig nahezu alles
    • git grep hat den Vorteil, die Menge der zu durchsuchenden Dateien aus dem Git-Index zu beziehen
  • Das Respektieren von .gitignore erhöht die Relevanz der Ergebnisse, kann aber Performance kosten
  • In linux_literal zeigte rg (whitelist) nahezu die gleiche Performance wie ucg, während rg (ignore) etwa auf dem Niveau von git grep lag
  • rg (ignore) (mmap) und ag (ignore) (mmap) wurden durch die Nutzung von Memory Mapping langsamer; unter denselben Bedingungen war rg (ignore) deutlich schneller
  • Auch auf einem lokalen Rechner waren die Memory-Map-Versionen langsamer, der Unterschied war jedoch geringer als auf EC2

Unicode und Suche ohne Beachtung der Groß-/Kleinschreibung

  • In linux_literal_casei wurde pt deutlich langsamer, weil es -i als (?i) der Go-regexp verarbeitete
  • sift wurde weniger stark langsamer, weil es Muster und Suchblöcke in Kleinbuchstaben umwandelt; diese Optimierung behandelt jedoch nur ASCII-Groß-/Kleinschreibung und ist für Unicode-Groß-/Kleinschreibung nicht korrekt
  • ripgrep wandelt case-insensitive Suche in mögliche Literal-Kombinationen um und findet Kandidatenpositionen schnell mit Teddy
  • Die Suche \wAh in linux_unicode_word prüft, ob ein Unicode-bewusstes \w Ergebnisse wie µAh findet
  • Nur rg und git grep konnten Unicode umschalten; ag, pt, sift und ucg verwendeten ein reines ASCII-\w
  • git grep verursachte bei aktiviertem Unicode-Support hohe Performance-Kosten, während ripgrep kaum langsamer wurde
  • ripgrep integriert die UTF-8-Dekodierung in die endliche Zustandsmaschine und matcht direkt auf UTF-8-Byte-Strings, ohne separaten Dekodierungsschritt

Unterschiede nach Regex-Komplexität

  • Bei regulären Ausdrücken mit Literal-Suffix wie [A-Z]+_RESUME nutzen rg und ucg _RESUME, um schnell Kandidaten zu finden
  • Bei Literal-Alternation wie ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT verwendet ripgrep Teddy und kann die zentrale Regex-Engine unter Umständen gar nicht einsetzen
  • Auch bei case-insensitiver Alternation erzeugt ripgrep Präfixe aus Groß-/Kleinschreibungs-Kombinationen, findet Kandidaten mit Teddy und verifiziert nur diese Kandidaten mit der vollständigen Regex
  • Bei der Suche nach \p{Greek} unterstützten nur Rust regex und Go regex diese Unicode-Eigenschaft; rg war deutlich schneller als pt und sift
  • Bei case-insensitiver Suche nach \p{Greek} meldete sift keine Treffer, und pt behandelte Unicode-Groß-/Kleinschreibung nicht korrekt
  • Bei Mustern ohne Literale wie \w{5}\s+... zeigt sich die Performance der Regex-Engine direkt
    • rg war auch mit aktiviertem Unicode-Support vergleichsweise schnell
    • git grep verursachte mit Unicode-Support hohe Kosten
    • Ein Unicode-DFA verarbeitet eine deutlich größere Menge von NFA-Zuständen als ein ASCII-DFA; die Beispielzahlen liegen bei etwa 250 NFA-Zuständen für ASCII und etwa 77.000 für Unicode

Suche in einer einzelnen großen Datei

  • Der Benchmark für einzelne Dateien verwendete Samples aus OpenSubtitles2016
    • Das englische Sample war etwa 1 GB groß
    • Das russische Sample war etwa 1,6 GB groß
  • In diesem Bereich werden die Performance der Regex-Engine und Literal-Optimierungen wichtiger
  • In subtitles_literal war rg sowohl bei der Suche nach Sherlock Holmes als auch nach Шерлок Холмс am schnellsten
  • ripgrep versucht, bei Literalen seltene Bytes auszuwählen und sie für memchr zu verwenden
    • Eine Standardimplementierung von Boyer-Moore verwendet für die Kandidatensuche normalerweise das letzte Byte
    • rg versucht, ein selteneres Byte auszuwählen, um in der SIMD-optimierten Schleife länger überspringen zu können
  • Bei russischen Mustern beginnen in UTF-8 viele Zeichen mit \xD0 oder \xD1, sodass die Suche nach dem ersten Byte ineffizient sein kann
  • rg nutzt eine vorberechnete 256-Byte-Häufigkeitstabelle und bevorzugt seltenere Bytes statt \xD0 oder \xD1
  • Bei einer einzelnen großen Datei muss die Memory Map nur einmal erstellt werden; daher war die Memory-Map-Suche von rg etwa 25 % schneller als rg (no mmap)

Unicode und Alternation in einzelnen Dateien

  • In subtitles_literal_casei verarbeitet rg Unicode-Suche ohne Beachtung der Groß-/Kleinschreibung korrekt und bleibt zugleich schnell
  • GNU grep verursacht bei Unicode-Suche ohne Beachtung der Groß-/Kleinschreibung hohe Kosten
  • Bei der russischen case-insensitiven Suche scheint grep (ASCII) -i faktisch zu ignorieren, und ag meldet 0 Treffer
  • In subtitles_alternate war rg bei der Alternation-Suche nach mehreren Personennamen sowohl auf Englisch als auch auf Russisch am schnellsten
  • Bei englischer Alternation war rg etwa um eine Größenordnung schneller als GNU grep
  • In subtitles_alternate_casei wurde rg deutlich langsamer als zuvor, lag im Englischen aber weiterhin vor den anderen Tools
  • In diesem Fall wurden die Literal-Kandidaten zu zahlreich für Teddy, sodass rg statt Teddy auf Aho-Corasick umschaltet
  • ripgrep verwendet das transition-table-basierte „advanced“ Aho-Corasick und führt pro Eingabebyte eine Transition aus

Inner Literals und Muster ohne Literale

  • Muster wie \w+\s+Holmes\s+\w+ wurden so konstruiert, dass sie Prefix- und Suffix-Literal-Optimierungen umgehen, können aber das interne Literal Holmes nutzen
  • ripgrep und GNU grep führen eine Inner-Literal-Optimierung durch
  • ripgrep nutzt regex-syntax der Rust-Regex-Engine, um Literale aus dem Pattern-AST zu extrahieren
  • Bei der russischen Version \w+\s+Холмс\s+\w+ konnten nur Tools mit korrekter Unicode-Unterstützung sinnvolle Ergebnisse liefern
  • Bei einem langen Muster ganz ohne Literale wie \w{5}\s+... gehörte rg im Englischen zu den schnellsten; die Version von GNU grep mit Unicode-Unterstützung brauchte im Englischen über 90 Sekunden und im Russischen über 4 Minuten und wurde daher ausgeschlossen
  • ripgrep erhält Unicode-Unterstützung und Performance, indem es die UTF-8-Dekodierung in den DFA integriert

Zusätzliche Benchmarks

  • everything ist ein unrealistischer Test, der im Linux-Repository mit .* alle Zeilen matcht
    • rg meldete 22.065.361 Zeilen in 1,081 Sekunden
    • ag und pt meldeten nicht alle Zeilen, daher scheint es ein Match-Limit zu geben
  • nothing ist ein Test, der auf .* invert match anwendet und dadurch keine Zeilen meldet
    • rg kam auf 0,302 Sekunden, git grep auf 0,905 Sekunden
    • pt und ucg unterstützen invert search nicht
  • context gibt im englischen Untertitelkorpus 2 Zeilen Kontext rund um Sherlock Holmes aus
    • rg lag bei 0,612 Sekunden, sift mit 0,717 Sekunden ähnlich
    • ucg unterstützt diese Funktion nicht
  • huge durchsucht die gesamten 9,3 GB englischer Untertitel nach Sherlock Holmes
    • rg lag bei 1,786 Sekunden, GNU grep bei 5,119 Sekunden, sift bei 3,047 Sekunden
    • ucg meldete unter der Line-Counting-Bedingung nur 1.543 Zeilen und lieferte damit ein falsches Ergebnis; vermutet wurde ein Problem bei der Suche in Dateien über 2 GB

Fazit

  • ripgrep gewann bei der Suche im Linux-Kernel-Repository nicht immer jeden Benchmark, doch bei Performance und Korrektheit ließ sich schwer sagen, dass andere Tools klar überlegen waren
  • git grep konnte in einigen einfachen Fällen um wenige Millisekunden vorn liegen, doch wenn Muster komplexer wurden oder Unicode erforderlich war, lag ripgrep teils deutlich vorn
  • Zur Code-Suchleistung von ripgrep tragen folgende Faktoren bei
    • Schnelle Verzeichnisdurchläufe mit dem Ziel, stat-Aufrufe zu minimieren
    • .gitignore-Glob-Matching mit RegexSet
    • Arbeitsverteilung über eine Chase-Lev-Work-Stealing-Queue
    • Die Entscheidung, bei der Suche in vielen kleinen Dateien keine Memory Maps zu verwenden
    • Eine schnelle Regex-Engine
  • Bei der Suche in einzelnen Dateien war ripgrep in allen wichtigen Benchmarks entweder am schnellsten oder mit großem Abstand vorn
  • Die Performance bei einzelnen Dateien wird durch sparsity-byte-basiertes memchr, Teddy SIMD, Aho-Corasick und einen DFA mit integrierter UTF-8-Dekodierung beeinflusst
  • In Benchmarks, die Unicode-Funktionalität erforderten, zeigten nur rg, GNU grep und git grep sinnvolle Unterstützung; GNU grep und git grep zahlten dafür meist einen hohen Performance-Preis
  • Memory Maps waren unter Linux x86_64 bei der parallelen Suche in vielen kleinen Dateien nachteilig, bei der Suche in einer einzelnen großen Datei vorteilhaft und können in VM-Umgebungen zusätzliche Einbußen verursachen

1 Kommentare

 
GN⁺ 2023-12-01
Hacker-News-Kommentare
  • Definitiv schnell, und ich empfehle weiterhin die Kombination mit fzf
    Ich nutze eine PowerShell-Funktion, die zuerst mit ripgrep sucht, dann eine Fuzzy-Suche über Ergebnisdateien und Text legt und mit bat den Kontext anzeigt.
    In Projekten, in denen mehrere Repositories vermischt sind, kann man damit sehr schnell eingrenzen, wenn man „weiß, dass es irgendwo ist, aber weder den genauen Ort noch den Namen kennt“.
    Dieser Ansatz stammt aus https://github.com/junegunn/fzf/blob/master/ADVANCED.md, und auch wenn man nicht alles davon nutzt, lohnt sich ein Überfliegen, um Ideen mitzunehmen.

    • Einen Schritt weiter würde ich empfehlen, ripgrep-all (rga) mit fzf zu integrieren.
      Damit kann man nicht nur Textdateien, sondern auch viele andere Dateiformate wie PDF oder ZIP per Fuzzy-Suche durchsuchen.
      Details stehen hier: https://github.com/phiresky/ripgrep-all/wiki/fzf-Integration
    • Ich habe das auch als bash-Version umgesetzt.
      Dabei wählt man die rg-Ergebnisse mit fzf aus, parst die ausgewählte Datei und Zeilennummer und öffnet dann mit $EDITOR +"${linenumber}" "$file".
    • Ohne fzf+rg in Vim fühlt es sich fast kaputt an.
      Wie Kaffee von Hand zu mahlen statt mit einer elektrischen Mühle.
    • Mit fzf kann man aus vielen Dateien auswählen, die zu Git hinzugefügt werden sollen, und einzelne überspringen.
      Wenn man in der gitconfig unter [alias] fza = "!git ls-files -m -o --exclude-standard | fzf -m --print0 | xargs -0 git add" einträgt, zeigt git fza eine Liste geänderter oder noch nicht hinzugefügter Dateien an, und man kann mit der Leertaste Einträge umschalten und zum nächsten gehen.
      Dieser Alias und fzf+fd beschleunigen Teile des Workflows ziemlich deutlich.
      Es gibt auch einen Leitfaden dazu, was man unter macOS in die zsh-Konfiguration aufnehmen kann: https://gist.github.com/aclarknexient/0ffcb98aa262c585c49d4b...
    • Ich nutze ripgrep fast auf die gleiche Weise.
      In einer Codebasis mit Hunderten von Repositories ist es mein Ausgangspunkt, um Dateien oder Projekte einzugrenzen, bevor ich tiefer einsteige.
  • In Emacs verwende ich ripgrep mit project.el und dem Paket dumb-jump.
    Vielleicht ist das nicht die populärste Methode, aber das Gesamterlebnis ist ziemlich zufriedenstellend.
    Man muss nur dumb-jump per package-install installieren und (add-hook 'xref-backend-functions #'dumb-jump-xref-activate) setzen.
    Wenn man in Python-Projekten mit M-. oder C-u M-. die Definition eines Bezeichners sucht, führt dumb-jump passend zum aktuellen Projekt und Dateityp einen rg-Befehl aus und zeigt die Ergebnisse im Xref-Puffer an.
    ag wird ebenfalls unterstützt, und wenn weder ag noch rg vorhanden ist, fällt es auf grep zurück, was erwartungsgemäß langsam sein kann, wenn das gesamte Home-Verzeichnis durchsucht wird.

    • Auch nur mit dem in Emacs enthaltenen project.el lässt sich ripgrep ziemlich einfach nutzen.
      Externe Pakete sind nicht zwingend nötig; wenn man in großen Verzeichnissen statt des langsamen grep lieber ripgrep verwenden möchte, setzt man einfach (setq xref-search-program 'ripgrep).
      Dann wird eine Projektsuche wie C-x p g foo RET im aktuellen Projekt in der Form rg -i --null -nH --no-heading --no-messages -g '!*/' -e foo ausgeführt.
      Die Ergebnisse erscheinen im Xref-Puffer, sodass man mit Tasten wie n, p, RET und C-o bequem zum nächsten oder vorherigen Treffer springen, zur Quelle wechseln oder sie in einem geteilten Fenster anzeigen kann.
    • Aus Sicht des ripgrep-Autors würde ich sagen: Ich habe diesen regulären Ausdruck nicht direkt ausprobiert, aber die --pcre2-Option könnte vermutlich weggelassen werden.
      Die zweite und dritte \b-Assertion könnten wohl auch entfallen, die erste könnte jedoch nötig sein.
    • Deadgrep nutzt ripgrep und hat auch Bindings für evil-collection, damit lässt es sich gut verwenden: https://github.com/Wilfred/deadgrep
    • Dieser Ansatz ist ebenfalls gut, aber wenn ich mehrere Projekte gleichzeitig durchsuchen oder nur Unterordner innerhalb eines Projekts durchsuchen will, nutze ich weiterhin rg.el.
      Das sind die Fälle, in denen ich früher rgrep verwendet hätte.
  • Interessant ist, dass auch die VS-Code-Suche inzwischen über einen Node.js-Wrapper mit ripgrep arbeitet.
    https://www.npmjs.com/package/@vscode/ripgrep

    • Das ist besonders praktisch in Umgebungen, in denen man VS Code anfordern oder installieren kann, aber ripgrep nicht installieren darf.
      Im VS-Installationspfad kann man die rg-Binärdatei finden. Zumindest in meiner Windows-Arbeitsumgebung war das möglich.
    • Ich habe mich immer gefragt, warum die Suche in VS Code als Electron-App so schnell ist, und jetzt kenne ich den Grund.
    • Das ist keine neue Funktion; VS Code hat das schon seit 7 Jahren integriert.
  • Ich benutze ripgrep seit etwa zwei Jahren, und es ist inzwischen ein unverzichtbares Werkzeug.
    Der Hauptgrund für den Umstieg von grep war die Benutzerfreundlichkeit.
    Standardmäßig beachtet es .gitignore-Regeln und überspringt versteckte Dateien/Verzeichnisse sowie Binärdateien, daher ist rg suchbegriff verzeichnis dem entsprechenden grep-Befehl weit überlegen; der Geschwindigkeitsgewinn ist ein zusätzlicher Bonus.
    Wenn Treffer zu lang sind und das Terminal dadurch unübersichtlich wird, nutze ich oft die Option -M, etwa als -M 1000.

    • -M ist wirklich großartig.
      Besonders praktisch ist es, um Ergebnisse aus minifizierten Dateien zu ignorieren, die man nicht sehen will, und auch die Option -g ist nützlich, um nur Dateien mit einer bestimmten Erweiterung zu durchsuchen, etwa mit -g *.cs.
      Hilfreich ist außerdem, dass es sich um eine eigenständige, portable Binärdatei handelt: Wenn ich auf einem neuen Rechner arbeite, lege ich die ausführbare Datei ab und setze den grep-Alias auf rg, sodass beim gewohnten Tippen von grep trotzdem rg ausgeführt wird.
  • Das mag auch 2023 noch stimmen, aber das Problem ist, dass parallelisierte grep-Alternativen wie ripgrep oder ag im Vergleich zu klassischem grep so schnell sind, dass kleine Geschwindigkeitsunterschiede untereinander kaum noch als Unterscheidungsmerkmal taugen.
    Ich nutze ag in Emacs auf einer Codebasis mit 900.000 Zeilen, und auf einem 16-Core Ryzen Threadripper 2950X ist es praktisch sofort fertig.
    Ich habe nicht das Bedürfnis, „unter 1 Sekunde“ auf „noch ein bisschen mehr unter 1 Sekunde“ zu verkürzen.
    Die entscheidende Eigenschaft neuer grep-artigen Tools ist nicht die Geschwindigkeit; man sollte sie nach anderen Kriterien bewerten und vergleichen.

    • 2016 war Geschwindigkeit meiner Meinung nach eindeutig die entscheidende Eigenschaft.
      ag hat einen ziemlich starken Performance-Einbruch, und das sieht man auch im Blogpost.
      Die Workloads unterscheiden sich aber von Person zu Person, deshalb ist der Performance-Unterschied in manchen Fällen möglicherweise nicht wichtig.
      900.000 Zeilen sind nicht besonders viel, und bei einfachen Anfragen sind die meisten nicht-naiven grep-artigen Tools sowieso sehr schnell.
      Nach anderen Vergleichskriterien ist ag fast nur noch auf lebenserhaltenden Maßnahmen, und es wäre in Debian beinahe entfernt worden, wurde aber offenbar von jemandem gerettet: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=999962
      Der Blogpost vergleicht auch Unicode-Unterstützung, und ag hat praktisch keine Unicode-Unterstützung. Das ist nicht für alle wichtig, reicht aber als nicht-performanzbezogenes Vergleichskriterium völlig aus.
    • Meiner Erfahrung nach sind diese Tools alle stark durch I/O-Engpässe begrenzt.
      Die Suchzeit entspricht ungefähr der Zeit, die das Laden der Dateien von der Festplatte dauert, und Unterschiede danach sind kaum noch bedeutsam.
      Wenn die Dateien im Cache sind, dominiert eher die Zeit, durchs Dateisystem zu navigieren und den Befehl zu tippen, statt der Suchzeit selbst, sodass Performance-Unterschiede auch dann kaum relevant sind.
  • (2016) gehört in den Titel.
    Das ist ursprünglich ein Showcase-Post und keine neue Information.

  • Schneller als qgrep ist es nicht.
    Beide funktionieren grundsätzlich sehr unterschiedlich; qgrep basiert auf re2, aber seine Geschwindigkeit kommt durch den Index.
    Bei großen Dateirepositories ist es sinnvoller, qgrep mit Index zu verwenden, statt jedes Mal alle Dateien zu durchsuchen, und ich frage mich, warum Leute diese qgrep-Option vergessen.
    Allerdings halte ich ripgrep nicht für besonders schnell, wenn man mehrzeilige Treffer in UTF-8 braucht, weil es dann auf eine andere PCRE2-Bibliothek zurückgreifen muss.

    • Als Autor von ripgrep kann ich bestätigen, dass qgrep durch Indexierung einen Vorteil gegenüber nicht indexierenden Tools hat.
      Dafür muss man den Index aber einrichten und pflegen, weshalb die UX nicht so einfach ist wie „einfach die Suche ausführen“.
      Warum Leute qgrep nicht nutzen, ähnelt dem Grund, warum sie ripgrep nicht nutzen: „Für mich ist grep schon schnell genug.“
      Bei kleinen Suchräumen spüren viele den Geschwindigkeitsunterschied zwischen ripgrep und grep oder zwischen qgrep und ripgrep gar nicht.
      Wenn ripgrep eine Linux-Kernel-Suche in unter 100 ms abschließt, ist der Wechsel zu einem indexierenden Tool für den üblichen interaktiven Einsatz je nach Situation vielleicht den Zusatzaufwand nicht wert — meistens aber wohl nicht.
      Ich habe schon einmal über die Idee nachgedacht, ripgrep um Indexierung zu erweitern: https://github.com/BurntSushi/ripgrep/issues/1497
      Und für mehrzeilige Suche ist PCRE2 nicht erforderlich. Die Standard-RegEx-Engine unterstützt ebenfalls Unicode, und die Unterstützung für mehrzeilige Suche bleibt auch erhalten, wenn ohne PCRE2 gebaut wird.
  • Ich bin von ripgrep zu ugrep gewechselt und habe nicht zurückgeblickt.
    Es ist ähnlich schnell, hat aber Fuzzy Matching, eine TUI, die sich für Code-Reviews eignet, und kann auch in PDFs oder komprimierten Dateien suchen.
    Praktisch ist auch, dass man optional die Google-Suchsyntax verwenden kann.
    https://ugrep.com

    • Ich bin ein großer ripgrep-Fan, bin aber vor Kurzem wegen einer Funktion zu ugrep gekommen, die ripgrep nicht hat: Suche innerhalb von ZIP-Archiven.
      Man kann suchen, ohne die Dateien auf die Festplatte zu entpacken.
      Ich arbeite mit komprimierten Korpora aus Millionen kleiner Textdateien, und es ist großartig, nicht alles erst ins Dateisystem entpacken zu müssen. Manche Dateisysteme haben bei dieser Größenordnung Schwierigkeiten.
      Ich bin für beide Tools dankbar und danke ihren jeweiligen Autoren.
    • Ich habe Angst, dass, sobald man Google-Suchsyntax in grep zu benutzen beginnt, die meisten Ergebnisse versuchen werden, mir etwas zu verkaufen.
    • Ich habe nur kurz nach Artikeln zu „ugrep vs ripgrep“ gesucht und Beiträge gesehen, in denen sich die Autoren von ugrep und ripgrep auf Reddit über Jahre hinweg gestritten zu haben scheinen.
      Zum Beispiel https://www.reddit.com/r/programming/comments/120wqvr/ripgre...
      Es geht doch nur um Open-Source-Tools, deshalb wirkt das etwas seltsam.
    • Ich frage mich, ob die TUI besser ist, als die Ergebnisse an fzf weiterzureichen.
      Für mich scheint es schwer zu sein, die Konfigurierbarkeit und Flexibilität von fzf zu übertreffen.
    • Danke für den Hinweis darauf.
      Das Killer-Feature scheint mir die bestehende Kompatibilität mit den grep-Kommandozeilenoptionen zu sein.
      Es ist ziemlich gut, nicht einen komplett neuen Satz an Optionen lernen zu müssen.
  • Ich frage mich, warum grep nicht ersetzt oder verbessert wird
    Dieses Thema wirkt inzwischen auch schon etwas alt

    • Dafür gibt es viele mögliche Erklärungen
      Trägheit, Kompatibilität, Widerstand gegen Veränderungen, das Innovator’s Dilemma und Ähnliches. Das ist nicht negativ gemeint; auf mich trifft das alles ebenfalls zu
      Zur Kompatibilität siehe die FAQ: https://github.com/BurntSushi/ripgrep/blob/master/FAQ.md#pos...
    • Das ist ähnlich wie der Grund, warum ich den 40 Jahre alten Stuhl, auf dem ich gerade sitze, nicht durch einen Razer UltraSeat XR3000-A ersetze
      Er ist bequem, passt gut zur übrigen Arbeitsumgebung, und es gibt keinen besonderen Grund, ihn zu ersetzen und alles neu darauf abzustimmen
      Die Analogie geht nur so weit, dass zufällig ein Stuhl wie von Razer in der Nähe steht und als Kleiderablage dient
    • Jemand, der Unix entworfen hat, machte einige Systemfunktionen zugleich zu zentralen OS-Funktionen und zu Werkzeugen für Menschen; das führte Jahrzehnte später zu seltsamen Ergebnissen wie: „Es muss unbedingt ein Programm namens xyz geben, das dieses Argument akzeptiert und sich exakt so verhält.“
    • Man kann bereits mehrere Alternativen wie ripgrep verwenden
      Wenn es darum geht, den grep-Befehl selbst durch ein anderes Utility zu ersetzen, scheint dafür im Verhältnis zum Nutzen zu viel kaputtzugehen
      Wer ein schnelleres grep will, kann ein anderes Tool verwenden, und wer das bestehende grep nutzt, kann dabei bleiben — das kommt dem Idealzustand bereits sehr nahe
    • grep ist ein Allzweckwerkzeug, um Text in allen möglichen Dateitypen zu finden, und es ist in den UNIX-Standards verankert
      Einige Programmierer verwenden es für die Suche in Quellcode, aber andere nutzen es für Textsuche ohne Bezug zu Quellcode oder in Skripten und erwarten, dass es niemals abstürzt
      ripgrep dagegen ist ein spezialisiertes, meinungsstarkes Tool, das hauptsächlich für die Suche in Quellcode-Repositories entwickelt wurde
      Es gibt nicht viel Spielraum, allgemeine Textsuche noch schneller zu machen. Mit mmap() gibt es bei abgeschnittenen Dateien ein Absturzrisiko, mit weniger Ausdrucksstärke bei regulären Ausdrücken könnte es schneller werden, und man könnte die Unterstützung für alle Locales und Zeichensätze aufgeben und nur UTF-8/UTF-16 fest einkodieren, aber das sollte man nicht tun
  • Ich habe in Portage nachgesehen, und es scheint auch eine Version zu geben, die andere Dokumente wie PDF und doc verarbeitet
    https://github.com/phiresky/ripgrep-all