5 points par GN⁺ 2023-12-01 | 1 commentaires | Partager sur WhatsApp
  • ripgrep (rg) est un outil de recherche en ligne de commande basé sur Rust qui combine la commodité de recherche dans le code à la The Silver Searcher avec des performances brutes du niveau de GNU grep, et propose des binaires pour Linux, Mac et Windows
  • Sur 25 benchmarks, aucun outil ne devançait clairement ripgrep en performances et en précision, aussi bien sur un gros fichier unique que sur la recherche dans de grands répertoires, et le coût de la prise en charge d’Unicode restait faible
  • Avec la gestion de .gitignore, l’exclusion par défaut des fichiers cachés et binaires, les filtres par type de fichier, la prise en charge optionnelle de PCRE2, la recherche dans plusieurs encodages et fichiers compressés, ainsi que les filtres de prétraitement, il élargit le champ d’usage réel des outils de recherche dans le code
  • Les écarts entre les expériences sur le dépôt du noyau Linux et OpenSubtitles2016 dépendent fortement de l’optimisation des littéraux, de la recherche multi-motifs SIMD Teddy, d’Aho-Corasick, de la méthode de décodage UTF-8, du comptage des lignes et du coût de traitement de .gitignore
  • Lors d’une recherche parallèle dans de nombreux petits fichiers, le mapping mémoire peut être plus lent, tandis qu’il peut être avantageux sur un seul gros fichier ; ripgrep utilise donc selon le contexte soit une recherche avec tampon intermédiaire, soit une recherche par mapping mémoire

La position visée par ripgrep

  • ripgrep est un outil de recherche en ligne de commande qui vise à réunir la commodité des outils de recherche dans le code et les performances des outils de type grep
  • Les outils comparés sont GNU grep, git grep, The Silver Searcher (ag), Universal Code Grep (ucg), The Platinum Searcher (pt) et sift
  • Les benchmarks cherchent à vérifier trois points essentiels
    • Aucun outil n’est clairement supérieur à ripgrep, que ce soit pour rechercher dans un fichier unique ou dans un grand répertoire
    • Il offre une vraie prise en charge d’Unicode sans imposer un coût important en performances
    • Lorsqu’on recherche dans plusieurs fichiers à la fois, le mapping mémoire peut généralement être plus lent plutôt que plus rapide
  • L’auteur est aussi le créateur de ripgrep et du moteur d’expressions régulières sous-jacent, et indique que les benchmarks peuvent être sélectionnés et donc biaisés

Fonctionnalités et comportement par défaut

  • Le nom de l’exécutable de ripgrep est rg
  • Par défaut, la recherche parcourt récursivement le répertoire courant, respecte .gitignore et ignore les fichiers cachés et binaires
  • .rgignore est également pris en charge, et les motifs de .rgignore sont prioritaires sur ceux de .gitignore
  • -u, -uu et -uuu permettent d’élargir progressivement le périmètre en ignorant les fichiers ignore, en incluant les fichiers cachés puis en incluant les fichiers binaires
    • rg -uuu est similaire à grep -a -r
  • Les filtres par type de fichier sont pris en charge
    • rg -tpy foo : rechercher uniquement dans les fichiers Python
    • rg -Tjs foo : exclure les fichiers JavaScript
    • --type-add permet d’ajouter de nouvelles règles de type de fichier
  • Plusieurs fonctionnalités de grep sont aussi disponibles
    • Affichage du contexte
    • Recherche de plusieurs motifs
    • Coloration des correspondances
    • Prise en charge complète d’Unicode
  • Le moteur d’expressions régulières par défaut ne prend pas en charge les assertions look-around ni les backreferences, mais ces fonctionnalités deviennent disponibles en sélectionnant le moteur PCRE2 avec -P
  • Une détection automatique partielle de l’UTF-16 et la spécification de l’encodage via -E/--encoding sont également prises en charge
    • Cela inclut UTF-16, latin-1, GBK, EUC-JP, Shift_JIS, etc.
  • -z/--search-zip permet de rechercher dans des fichiers compressés comme gzip, xz, lzma, bzip2 ou lz4
  • Des filtres de prétraitement arbitraires sont aussi pris en charge, par exemple l’extraction de texte depuis des PDF, la décompression supplémentaire, le déchiffrement ou la détection automatique de l’encodage

Raisons de ne pas l’utiliser

  • Si la portabilité et la disponibilité partout sont la priorité absolue, grep, standard et largement installé, est le choix approprié
  • Si l’on dépend d’une fonctionnalité particulière ou d’un bug présent dans un autre outil, ripgrep peut ne pas convenir
  • Dans certains cas limites de performance, d’autres outils peuvent mieux se comporter
  • Il peut aussi être inutilisable s’il ne peut pas être installé ou si la plateforme n’est pas prise en charge

Structure de fonctionnement des outils de type grep

  • Un outil de recherche passe grosso modo par trois étapes
    • Collecte des fichiers à rechercher
    • Recherche proprement dite
    • Affichage des résultats
  • Les outils de type grep doivent bien rechercher dans de gros fichiers, ce qui rend les performances du moteur d’expressions régulières importantes
  • Les outils de type ack doivent traiter rapidement le parcours récursif des répertoires et l’application de règles d’ignore comme .gitignore
  • ripgrep cherche à combiner les deux approches
    • Moteur d’expressions régulières rapide
    • Recherche parallèle
    • Filtrage des cibles de recherche

Collecte des fichiers et traitement des ignore

  • Dans les outils de type ack, il est important de décider rapidement quels fichiers rechercher à partir du répertoire courant
  • Les performances du parcours des répertoires dépendent du nombre d’appels stat inutiles
  • ripgrep utilise un itérateur récursif de répertoires qui vise un nombre minimal d’appels système
  • Le traitement de .gitignore a un coût
    • Il faut chercher les fichiers ignore dans chaque répertoire
    • Il faut compiler les motifs d’ignore
    • Il faut appliquer les motifs à tous les chemins candidats
  • Le dépôt du noyau Linux contenait 4 640 répertoires et 178 fichiers .gitignore
  • ripgrep cherche à prendre en charge plus complètement la sémantique de .gitignore, et donne la priorité au motif correspondant défini le plus récemment
  • ucg peut être rapide parce qu’il utilise des règles glob fondées sur une liste blanche plutôt que .gitignore, mais il risque de manquer des fichiers dont l’extension lui est inconnue

Différences entre moteurs d’expressions régulières

  • Les moteurs d’expressions régulières se divisent généralement en deux catégories
    • Basés sur le backtracking : riches en fonctionnalités, mais peuvent devenir exponentiellement lents sur certaines entrées
    • Basés sur les automates finis : fonctionnalités parfois limitées, mais garantissent un temps linéaire par rapport à la longueur du texte recherché
  • Les moteurs par outil sont les suivants
    • GNU grep, git grep : moteurs internes basés sur des automates finis
    • ripgrep : bibliothèque regex de Rust, basée sur des automates finis
    • ag, ucg : backtracking basé sur PCRE
    • pt, sift : bibliothèque regex de Go, basée sur des automates finis
  • ag et ucg, du fait de leur utilisation de PCRE, peuvent être exposés aux pires comportements du backtracking
  • Le motif d’exemple (a*)* c peut poser problème aux outils basés sur PCRE, alors que les autres outils du benchmark le traitent sans difficulté

Optimisation des littéraux et SIMD

  • Dans une recherche de chaîne simple, l’optimisation de la recherche de littéraux peut devenir plus importante que le moteur d’expressions régulières
  • Boyer-Moore est un algorithme classique de recherche de sous-chaîne, qui peut exploiter des routines comme memchr pour trouver rapidement les positions candidates
  • Les implémentations de memchr examinent souvent 16 octets à la fois avec des instructions SIMD, et peuvent atteindre un débit de plusieurs Go/s
  • La bibliothèque regex de Rust extrait activement les littéraux de préfixe et de suffixe dans les motifs
    • foo|bar
    • (a|b)c
    • [ab]foo[yz]
    • (foo)?bar
    • (foo)*bar
    • (foo){3,6}
  • Si l’expression régulière complète peut être décomposée en un seul littéral ou en une alternance de littéraux, le moteur principal d’expressions régulières peut ne pas être utilisé du tout
  • ripgrep exploite la nature de l’affichage des résultats ligne par ligne pour extraire aussi des littéraux internes
    • Exemple : dans \w+foo\d+, il cherche d’abord foo, puis vérifie uniquement les lignes candidates avec l’expression régulière
  • Pour la recherche de plusieurs littéraux, GNU grep utilise un algorithme proche de Commentz-Walter, tandis que regex de Rust utilise Aho-Corasick ou l’algorithme SIMD Teddy
  • Teddy est un algorithme de recherche multi-motifs basé sur SIMD issu d’Intel Hyperscan, et constitue l’une des optimisations clés qui permettent à ripgrep de devancer GNU grep

Méthode de recherche : éviter la recherche ligne par ligne

  • Une implémentation naïve lit le fichier ligne par ligne et applique le motif à chaque ligne, mais c’est inefficace dans la plupart des recherches, car les correspondances sont rares
  • Les outils de recherche parcourent généralement de grands tampons d’octets d’un coup
    • Mapping du fichier en mémoire
    • Lecture du fichier entier en mémoire
    • Recherche progressive avec un tampon intermédiaire de taille fixe
  • ripgrep, GNU grep et git grep prennent en charge la recherche progressive, applicable aussi bien aux fichiers qu’aux flux
  • La recherche progressive est difficile à implémenter
    • Calcul des numéros de ligne
    • Gestion des cas où le tampon se termine au milieu d’une ligne
    • Gestion des lignes longues
    • Traitement d’invert match
    • Affichage du contexte autour des correspondances
  • ripgrep accepte cette complexité d’implémentation et utilise la recherche progressive ; dans les benchmarks, elle se montre plus rapide que le mapping mémoire lors de la recherche dans de nombreux petits fichiers

Sortie et parallélisme

  • Dans une recherche parallèle, si chaque thread écrit directement sa sortie, les résultats provenant de fichiers différents peuvent se mélanger
  • Tous les outils de recherche de code parallèles écrivent les résultats de recherche dans un tampon intermédiaire en mémoire, et ne sérialisent que l’étape de sortie
  • Cette approche permet aux threads de recherche d’effectuer la recherche proprement dite en parallèle
  • Son inconvénient est qu’elle peut entraîner une forte consommation mémoire dans des cas comme un fichier de 2 Go où toutes les lignes correspondent
  • ripgrep écrit directement dans stdout, sans tampon intermédiaire, lors d’une recherche sur stdin ou sur un seul fichier

Méthodologie des benchmarks

  • Les benchmarks sont répartis selon les problèmes rencontrés par l’utilisateur final
    • Recherche dans de grands dépôts de code
    • Recherche dans un seul gros fichier
  • Les motifs de recherche privilégient les littéraux simples, les alternations et les expressions régulières légères
  • Comme le comportement par défaut varie selon les outils, les conditions telles que les numéros de ligne, Unicode, .gitignore et les listes blanches ont été alignées autant que possible pour une comparaison équitable
  • Les versions testées sont les suivantes
    • 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 a été exclu, car il était alors nettement plus lent que les autres outils
  • Le runner de benchmark est benchsuite, qui nécessite Python 3.5 ou ultérieur, et il est inclus dans le dépôt ripgrep
  • Chaque commande exécute 3 warm-ups avant la mesure afin que le corpus soit chargé dans le page cache de l’OS
  • Chaque commande est mesurée 10 fois, puis la moyenne et l’écart type sont enregistrés
  • L’environnement d’exécution est Amazon EC2 c3.2xlarge, Ubuntu 16.04, Xeon E5-2680 2,8 GHz, 16 Go de mémoire et un SSD de 80 Go
  • Les logs de configuration, les résultats récapitulatifs et les CSV bruts ont également été publiés

Résultats de recherche dans le code du noyau Linux

  • Le benchmark de recherche de code a été exécuté sur le dépôt du noyau Linux compilé, au commit d0acc7
  • Le dépôt du noyau compilé a été utilisé parce que les artefacts de build restant dans le dépôt peuvent affecter la pertinence des résultats et les performances
  • Dans linux_literal_default, la recherche du littéral simple PM_RESUME met en évidence les différences de comportement par défaut de chaque outil
    • rg respecte .gitignore et ignore les fichiers cachés et binaires
    • ag et pt sont similaires, mais comptent le nombre de lignes
    • ucg ne lit pas .gitignore et effectue une recherche fondée sur une liste blanche
    • sift recherche par défaut presque partout
    • git grep bénéficie de l’obtention de l’ensemble des fichiers à rechercher depuis l’index git
  • Le respect de .gitignore améliore la pertinence des résultats, mais peut avoir un coût en performances
  • Dans linux_literal, rg (whitelist) a affiché des performances presque similaires à ucg, tandis que rg (ignore) était à un niveau comparable à git grep
  • rg (ignore) (mmap) et ag (ignore) (mmap) ont été ralentis par l’utilisation du memory mapping, et, à conditions identiques, rg (ignore) était beaucoup plus rapide
  • Sur une machine locale également, les versions utilisant le memory mapping étaient plus lentes, mais l’écart était moindre que sur EC2

Unicode et recherche insensible à la casse

  • Dans linux_literal_casei, pt ralentit fortement en traitant -i comme (?i) de la regexp Go
  • sift ralentit moins grâce à une approche consistant à convertir le motif et les blocs de recherche en minuscules, mais cette optimisation ne gère que la casse ASCII et est donc incorrecte pour la casse Unicode
  • ripgrep transforme les recherches insensibles à la casse en combinaisons de littéraux possibles, puis utilise Teddy pour trouver rapidement les positions candidates
  • La recherche \wAh dans linux_unicode_word vérifie si \w, avec prise en charge Unicode, capture des résultats comme µAh
  • Seuls rg et git grep permettaient de basculer Unicode ; ag, pt, sift et ucg utilisaient un \w limité à l’ASCII
  • git grep subissait un coût de performance important lorsque la prise en charge Unicode était activée, tandis que ripgrep ne connaissait presque aucune baisse de performances
  • ripgrep intègre le décodage UTF-8 dans une machine à états finis et effectue la correspondance directement sur les chaînes d’octets UTF-8, sans étape de décodage séparée

Écarts selon la complexité des expressions régulières

  • Pour les expressions régulières avec un suffixe littéral, comme [A-Z]+_RESUME, rg et ucg utilisent _RESUME pour trouver rapidement les candidats
  • Pour les alternations de littéraux comme ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT, ripgrep utilise Teddy et peut ne pas utiliser du tout le moteur regex principal
  • Même pour les alternations insensibles à la casse, ripgrep génère des préfixes combinant les variantes de casse, trouve les candidats avec Teddy, puis ne valide que ces candidats avec l’expression régulière complète
  • Pour la recherche \p{Greek}, seuls Rust regex et Go regex prenaient en charge cette propriété Unicode, et rg était beaucoup plus rapide que pt et sift
  • Dans la recherche insensible à la casse \p{Greek}, sift n’a signalé aucune correspondance, et pt ne gérait pas correctement la casse Unicode
  • Pour les motifs sans littéral, comme \w{5}\s+..., les performances du moteur regex apparaissent directement
    • rg restait plutôt rapide même avec la prise en charge Unicode activée
    • git grep subissait un coût important avec la prise en charge Unicode
    • Le DFA Unicode manipule un ensemble d’états NFA bien plus grand que le DFA ASCII ; les chiffres d’exemple sont d’environ 250 états NFA pour l’ASCII contre environ 77 000 pour Unicode

Recherche dans un seul gros fichier

  • Le benchmark sur fichier unique utilise un échantillon d’OpenSubtitles2016
    • L’échantillon anglais fait environ 1 Go
    • L’échantillon russe fait environ 1,6 Go
  • Dans ce domaine, les performances du moteur regex et les optimisations de littéraux deviennent plus importantes
  • Dans subtitles_literal, rg était le plus rapide pour les recherches Sherlock Holmes comme Шерлок Холмс
  • ripgrep essaie de choisir des octets rares dans les littéraux pour les utiliser avec memchr
    • Une implémentation standard de Boyer-Moore utilise généralement le dernier octet pour rechercher les candidats
    • rg essaie de choisir un octet plus rare afin de sauter plus longtemps dans la boucle optimisée SIMD
  • Pour les motifs russes, de nombreux caractères commencent en UTF-8 par \xD0 ou \xD1, ce qui peut rendre la recherche sur le premier octet inefficace
  • rg utilise une table de fréquences précalculée de 256 octets pour privilégier des octets plus rares que \xD0 et \xD1
  • Dans un seul gros fichier, comme il suffit de créer le memory mapping une seule fois, la recherche avec memory mapping de rg était environ 25 % plus rapide que rg (no mmap)

Unicode et alternation dans un fichier unique

  • Dans subtitles_literal_casei, rg reste rapide tout en gérant correctement la recherche Unicode insensible à la casse
  • GNU grep subit un coût important dans les recherches Unicode insensibles à la casse
  • Dans la recherche russe insensible à la casse, grep (ASCII) semble ignorer -i dans les faits, et ag signale 0 correspondance
  • Dans subtitles_alternate, la recherche par alternation de plusieurs noms de personnages était la plus rapide avec rg, aussi bien en anglais qu’en russe
  • Pour l’alternation en anglais, rg était environ un ordre de grandeur plus rapide que GNU grep
  • Dans subtitles_alternate_casei, rg était beaucoup plus lent qu’auparavant, mais devançait les autres outils en anglais
  • Dans ce cas, le nombre de candidats littéraux devient trop élevé pour Teddy, et rg bascule donc vers Aho-Corasick
  • ripgrep utilise l’Aho-Corasick « advanced » basé sur une table de transitions, qui effectue une transition par octet d’entrée

Littéral interne et motifs sans littéral

  • Un motif comme \w+\s+Holmes\s+\w+ est conçu pour éviter les optimisations par littéral de préfixe ou de suffixe, mais il peut exploiter le littéral interne Holmes
  • ripgrep et GNU grep effectuent une optimisation par littéral interne
  • ripgrep utilise regex-syntax de Rust regex pour extraire les littéraux de l’AST du motif
  • Dans la version russe \w+\s+Холмс\s+\w+, seuls les outils prenant correctement en charge Unicode pouvaient produire des résultats pertinents
  • Pour un long motif sans aucun littéral, du type \w{5}\s+..., rg faisait partie des plus rapides en anglais, tandis que la version de GNU grep avec prise en charge d’Unicode a été exclue, car elle prenait plus de 90 secondes en anglais et plus de 4 minutes en russe
  • ripgrep maintient la prise en charge d’Unicode tout en conservant de bonnes performances en intégrant le décodage UTF-8 dans le DFA

Benchmarks supplémentaires

  • everything est un test peu réaliste qui fait correspondre toutes les lignes du dépôt Linux avec .*
    • rg signale 22 065 361 lignes en 1,081 seconde
    • ag et pt ne signalent pas toutes les lignes, ce qui laisse penser qu’ils appliquent une limite de correspondances
  • nothing est un test qui applique un invert match à .* afin de ne signaler aucune ligne
    • rg obtient 0,302 seconde, contre 0,905 seconde pour git grep
    • pt et ucg ne prennent pas en charge la recherche inversée
  • context affiche 2 lignes de contexte autour de Sherlock Holmes dans le corpus de sous-titres anglais
    • rg obtient 0,612 seconde, un résultat proche de sift avec 0,717 seconde
    • ucg ne prend pas cette fonctionnalité en charge
  • huge recherche Sherlock Holmes dans l’ensemble des sous-titres anglais, soit 9,3 Go
    • rg obtient 1,786 seconde, GNU grep 5,119 secondes et sift 3,047 secondes
    • ucg n’a signalé que 1 543 lignes avec l’option de comptage des lignes, produisant un résultat incorrect ; on soupçonne un problème lors de la recherche dans des fichiers de plus de 2 Go

Conclusion

  • ripgrep n’a pas remporté systématiquement tous les benchmarks de recherche dans le dépôt du noyau Linux, mais il était difficile d’affirmer qu’un autre outil lui était clairement supérieur en performances et en exactitude
  • git grep pouvait devancer de quelques millisecondes dans certains cas simples, mais lorsque les motifs devenaient plus complexes ou qu’Unicode était requis, ripgrep pouvait prendre une avance importante
  • Les éléments suivants contribuent aux performances de ripgrep dans la recherche de code
    • Parcours rapide des répertoires visant à minimiser les appels stat
    • Correspondance des globs .gitignore avec RegexSet
    • Répartition du travail via une file Chase-Lev de work stealing
    • Choix de ne pas utiliser de mémoire mappée pour rechercher dans de nombreux petits fichiers
    • Moteur d’expressions régulières rapide
  • Pour la recherche dans un fichier unique, ripgrep est le plus rapide ou prend une avance nette dans tous les principaux benchmarks
  • Les performances sur fichier unique sont influencées par memchr basé sur des octets rares, Teddy SIMD, Aho-Corasick et le DFA intégrant le décodage UTF-8
  • Dans les benchmarks nécessitant des fonctionnalités Unicode, seuls rg, GNU grep et git grep offraient une prise en charge significative ; GNU grep et git grep payaient généralement un coût de performance élevé
  • Sur Linux x86_64, la mémoire mappée était défavorable pour la recherche parallèle dans de nombreux petits fichiers, avantageuse pour la recherche dans un seul gros fichier, et pouvait entraîner une pénalité supplémentaire dans les environnements VM

1 commentaires

 
GN⁺ 2023-12-01
Avis de Hacker News
  • C’est clairement rapide, et je continue de recommander la combinaison avec fzf
    Je m’en sers via une fonction PowerShell qui cherche d’abord avec ripgrep, ajoute ensuite une recherche floue sur les fichiers+textes des résultats, puis affiche le contexte avec bat
    Dans des projets mélangeant plusieurs dépôts, ça permet de réduire très vite le champ quand « on sait que c’est quelque part, mais sans connaître l’emplacement exact ni le nom »
    Cette méthode vient de https://github.com/junegunn/fzf/blob/master/ADVANCED.md et, même sans tout utiliser, ça vaut le coup de la parcourir pour s’en inspirer

    • Pour aller plus loin, je recommande d’intégrer ripgrep-all(rga) avec fzf
      On peut faire de la recherche floue non seulement dans les fichiers texte, mais aussi dans divers formats comme les PDF ou les zip
      Les détails sont ici : https://github.com/phiresky/ripgrep-all/wiki/fzf-Integration
    • J’ai aussi écrit une version bash de ça
      Elle consiste à sélectionner un résultat de rg avec fzf, puis à analyser le fichier et le numéro de ligne choisis pour l’ouvrir avec $EDITOR +"${linenumber}" "$file"
    • Dans Vim, sans fzf+rg, j’ai presque l’impression que tout est cassé
      C’est comme moudre son café à la main au lieu d’utiliser un moulin électrique
    • Avec fzf, on peut sélectionner beaucoup de fichiers à ajouter à Git tout en en ignorant certains
      En mettant fza = "!git ls-files -m -o --exclude-standard | fzf -m --print0 | xargs -0 git add" dans [alias] de gitconfig, git fza affiche la liste des fichiers modifiés ou pas encore ajoutés, et on peut basculer les éléments avec Espace en passant au suivant
      Cet alias et fzf+fd accélèrent pas mal une partie du flux de travail
      Il existe aussi un guide qui récapitule des éléments à mettre dans la configuration zsh sur macOS : https://gist.github.com/aclarknexient/0ffcb98aa262c585c49d4b...
    • Moi aussi, j’utilise ripgrep presque de la même manière
      Je m’en sers comme point de départ pour réduire la recherche à un fichier ou un projet dans une base de code contenant des centaines de dépôts, puis je creuse davantage
  • Dans Emacs, j’utilise ripgrep avec project.el et le paquet dumb-jump
    Ce n’est peut-être pas l’approche la plus populaire, mais l’expérience globale est assez satisfaisante
    Il suffit d’installer dumb-jump avec package-install et de configurer seulement (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
    Dans un projet Python, quand on cherche la définition d’un identifiant avec M-. ou C-u M-., dumb-jump exécute une commande rg adaptée au projet courant et au type de fichier, puis affiche les résultats dans le tampon Xref
    ag est aussi pris en charge et, s’il n’y a ni ag ni rg, il retombe sur grep, ce qui peut être lent comme prévu quand on cherche dans tout le répertoire personnel

    • Même avec le seul project.el fourni par défaut dans Emacs, on peut utiliser ripgrep assez facilement
      Un paquet externe n’est pas indispensable ; pour l’utiliser à la place du lent grep dans de grands répertoires, il suffit de définir (setq xref-search-program 'ripgrep)
      Une recherche de projet comme C-x p g foo RET s’exécute alors dans le projet courant sous une forme du genre rg -i --null -nH --no-heading --no-messages -g '!*/' -e foo
      Les résultats s’affichent dans le tampon Xref, ce qui rend pratiques les touches comme n, p, RET, C-o pour passer au résultat suivant/précédent, sauter vers la source ou afficher dans une fenêtre divisée
    • En tant qu’auteur de ripgrep, à première vue, je n’ai pas exécuté cette expression régulière moi-même, mais il me semble qu’on peut retirer le drapeau --pcre2
      Les deuxième et troisième assertions \b peuvent aussi probablement être retirées, la première pouvant être nécessaire
    • Deadgrep utilise ripgrep et propose aussi des raccourcis evil-collection, ce qui le rend agréable à utiliser : https://github.com/Wilfred/deadgrep
    • Cette approche est bien aussi, mais quand je veux chercher dans plusieurs projets à la fois, ou seulement dans un sous-dossier d’un projet, j’utilise encore rg.el
      C’est le genre de situation où j’aurais auparavant utilisé rgrep
  • Ce qui est intéressant, c’est que la recherche de VS Code fonctionne désormais elle aussi avec ripgrep via un wrapper Node.js
    https://www.npmjs.com/package/@vscode/ripgrep

    • Si vous êtes dans un environnement où vous pouvez demander ou installer VS Code, mais pas installer ripgrep, c’est très pratique
      On peut trouver le binaire rg dans le chemin d’installation de VS. En tout cas, c’était possible dans mon environnement Windows au travail
    • Je me suis toujours demandé pourquoi la recherche était aussi rapide dans VS Code alors que c’est une application Electron ; maintenant je sais pourquoi
    • Ce n’est pas une nouveauté : VS Code l’intègre depuis 7 ans
  • J’utilise ripgrep depuis environ deux ans, et c’est devenu un outil indispensable
    La principale raison pour laquelle j’ai quitté grep était la facilité d’utilisation
    Par défaut, il respecte les règles de .gitignore et ignore les fichiers/répertoires cachés ainsi que les fichiers binaires ; rg search_term directory est donc bien meilleur que la commande grep équivalente, et le gain de vitesse est un bonus
    Quand une correspondance est trop longue et met le bazar dans le terminal, j’utilise souvent l’option -M, par exemple -M 1000

    • -M est vraiment excellent
      C’est particulièrement pratique pour ignorer les résultats de fichiers minifiés qu’on ne veut pas voir, et l’option -g pour ne chercher que dans les fichiers d’une certaine extension, comme -g *.cs, est très bien aussi
      Le fait que ce soit un binaire autonome et portable est également utile : quand je travaille sur une nouvelle machine, je mets l’exécutable et je définis l’alias grep vers rg, comme ça même si je tape grep par habitude, c’est rg qui s’exécute
  • C’est peut-être toujours vrai en 2023, mais le problème est que les remplaçants parallélisés de grep, comme ripgrep ou ag, sont tellement plus rapides que le grep classique que les petits écarts de vitesse entre eux deviennent difficiles à prendre comme critère de distinction.
    J’utilise ag dans Emacs sur une base de code de 900 000 lignes, et sur un Ryzen Threadripper 2950X à 16 cœurs, ça se termine quasiment instantanément.
    Je ne ressens pas le besoin de réduire un temps inférieur à 1 seconde à « un peu plus inférieur à 1 seconde ».
    La caractéristique essentielle des nouveaux outils de type grep n’est pas la vitesse ; il faut les évaluer et les comparer autrement.

    • En 2016, je pense que la vitesse était clairement une caractéristique essentielle.
      ag a une falaise de performance assez nette, et on le voit aussi dans l’article de blog.
      Cela dit, les charges de travail varient selon les personnes, donc dans certains cas les différences de performance peuvent ne pas compter.
      900 000 lignes, ce n’est pas particulièrement gros, et pour une requête simple, la plupart des outils de type grep qui ne sont pas naïfs traitent ça très vite.
      Si l’on regarde d’autres critères de comparaison, ag est pratiquement sous assistance respiratoire ; il a failli être retiré de Debian avant que quelqu’un ne le sauve, semble-t-il : https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=999962
      L’article de blog compare aussi la prise en charge d’Unicode ; ag, en pratique, n’a pas vraiment de prise en charge d’Unicode. Ce ne sera pas important pour tout le monde, mais c’est un critère non lié à la performance tout à fait valable.
    • D’après mon expérience, tous ces outils sont fortement limités par les entrées/sorties.
      Le temps de recherche correspond au temps nécessaire pour charger les fichiers depuis le disque, et les différences après ça ont peu de chances d’être significatives.
      Si les fichiers sont en cache, le temps passé à parcourir le système de fichiers et à taper la commande domine davantage que le temps de recherche, donc là encore les écarts de performance ont peu de chances d’être significatifs.
  • Le titre a besoin de (2016).
    C’est le billet d’annonce d’origine, pas une nouvelle information.

  • Ce n’est pas plus rapide que qgrep.
    Les deux fonctionnent très différemment, et qgrep est basé sur re2, mais sa vitesse vient de l’index.
    Sur de gros dépôts de fichiers, utiliser qgrep et un index plutôt que de parcourir tous les fichiers à chaque fois paraît plus logique ; je me demande pourquoi les gens oublient l’option qgrep.
    Cela dit, si vous avez besoin de correspondances multilignes en UTF-8, ripgrep doit basculer sur une autre bibliothèque PCRE2, donc je ne pense pas que ce soit aussi rapide.

    • En tant qu’auteur de ripgrep, oui, qgrep a un avantage sur les outils qui n’indexent pas, puisqu’il utilise l’indexation.
      En contrepartie, il faut configurer et maintenir l’index, donc l’UX n’est pas aussi simple que « lancer simplement une recherche ».
      La raison pour laquelle les gens n’utilisent pas qgrep ressemble à celle pour laquelle ils n’utilisent pas ripgrep en se disant « grep est déjà assez rapide pour moi ».
      Sur de petites cibles de recherche, on ne perçoit souvent pas la différence de vitesse entre ripgrep et grep, ou entre qgrep et ripgrep.
      Si ripgrep termine une recherche dans le noyau Linux en moins de 100 ms, il est discutable, mais généralement peu probable, que ce soit assez pénible dans un usage interactif standard pour passer à un outil indexé.
      J’ai déjà réfléchi à l’idée d’ajouter l’indexation à ripgrep : https://github.com/BurntSushi/ripgrep/issues/1497
      Et la recherche multilignes n’exige pas PCRE2. Le moteur d’expressions régulières par défaut prend aussi en charge Unicode, et même une compilation sans PCRE2 conserve la prise en charge de la recherche multilignes.
  • Depuis que je suis passé de ripgrep à ugrep, je n’ai pas fait marche arrière.
    La vitesse est comparable, mais il y a la correspondance approximative, une TUI utilisable pour les revues de code, et la possibilité de chercher dans les PDF ou les fichiers compressés.
    Le fait de pouvoir utiliser en option la syntaxe de recherche Google est aussi pratique.
    https://ugrep.com

    • Je suis un grand fan de ripgrep, mais récemment j’ai découvert ugrep à cause d’une fonctionnalité absente de ripgrep : la recherche à l’intérieur des archives zip.
      On peut chercher sans décompresser sur le disque.
      Je travaille avec des corpus compressés composés de millions de petits fichiers texte, et c’est appréciable de ne plus avoir à tout extraire dans le système de fichiers. Certains systèmes de fichiers souffrent à cette échelle.
      Je suis reconnaissant envers les deux outils, et envers chacun de leurs auteurs.
    • J’ai peur que, si grep commence à utiliser la syntaxe de recherche Google, la plupart des résultats essaient de me vendre quelque chose.
    • En cherchant rapidement des articles « ugrep vs ripgrep », je suis tombé sur des échanges qui donnent l’impression que les auteurs de ugrep et ripgrep se sont disputés pendant des années sur Reddit.
      Par exemple https://www.reddit.com/r/programming/comments/120wqvr/ripgre...
      C’est juste une discussion sur des outils open source, mais ça m’a paru un peu bizarre.
    • Je me demande si la TUI est meilleure que d’envoyer les résultats dans fzf.
      Pour moi, il paraît difficile de battre la configurabilité et la souplesse de fzf.
    • Merci de me l’avoir signalé.
      La fonctionnalité décisive semble être la compatibilité avec les options en ligne de commande de grep existantes.
      C’est plutôt appréciable de ne pas avoir à apprendre un tout nouvel ensemble d’options.
  • Je me demande pourquoi grep n’est pas remplacé ou amélioré
    Ce sujet commence lui aussi à dater un peu

    • On peut l’expliquer de nombreuses façons
      L’inertie, la compatibilité, la résistance au changement, le dilemme de l’innovateur, etc. Je ne dis pas ça de manière négative, tout cela s’applique aussi à moi
      Pour la compatibilité, voir la FAQ : https://github.com/BurntSushi/ripgrep/blob/master/FAQ.md#pos...
    • C’est un peu comme la raison pour laquelle je ne remplace pas la chaise de 40 ans sur laquelle je suis assis par une Razer UltraSeat XR3000-A
      Elle est confortable, s’intègre bien à mon environnement de travail, et je n’ai pas de raison de la remplacer puis de tout réadapter
      La comparaison ne va pas plus loin que le fait qu’une chaise comme la Razer est déjà dans les parages et sert à poser des vêtements
    • Quelqu’un qui a conçu Unix a fait de certaines fonctionnalités système des éléments centraux de l’OS tout en étant aussi des outils utilisés par les humains, ce qui a produit, des décennies plus tard, des conséquences étranges du genre : « il doit absolument exister un programme nommé xyz, qui accepte tel argument et se comporte exactement de cette façon »
    • On peut déjà utiliser plusieurs alternatives comme ripgrep
      S’il s’agit de remplacer la commande grep elle-même par un autre utilitaire, il semble qu’on casserait trop de choses pour le bénéfice obtenu
      Les personnes qui veulent un grep plus rapide peuvent utiliser un autre outil, et celles qui utilisent le grep existant peuvent continuer à le faire ; on est donc déjà proche d’un état idéal
    • grep est un outil généraliste pour trouver du texte dans toutes sortes de fichiers, et il est inscrit dans le standard UNIX
      Certains programmeurs l’utilisent pour chercher dans du code source, mais d’autres s’en servent pour des recherches de texte sans rapport avec le code source ou dans des scripts, et s’attendent à ce qu’il ne plante jamais
      À l’inverse, ripgrep est un outil spécialisé et très orienté, principalement conçu pour rechercher dans des dépôts de code source
      Il n’y a pas énormément de marge pour accélérer une recherche de texte généraliste. Utiliser mmap() expose à un risque de crash sur des fichiers tronqués ; réduire l’expressivité des expressions régulières pourrait accélérer les choses ; on pourrait aussi abandonner la prise en charge de toutes les locales et jeux de caractères pour ne coder en dur qu’UTF-8/UTF-16, mais ce ne serait pas acceptable
  • En regardant dans Portage, il semble qu’il existe aussi une version qui traite d’autres types de documents comme les PDF et les fichiers doc
    https://github.com/phiresky/ripgrep-all