Il s'agit de ma propre version expérimentale et parallèle de grep qui me permet de tester diverses stratégies pour accélérer l'accès aux grandes arborescences de répertoires. Sur le stockage Flash ou les SSD, vous pouvez facilement déjouer les greps courants jusqu'à un facteur 8.
Possibilités :
Usage: ./greppin [-rIOLlsSH] [-n <cores>] <regex> <path>
-2 -- use PCRE2 instead of PCRE
-O -- print file offset of match
-l -- do not print the matching line (Useful if you want
to see _all_ offsets; if you also print the line, only
the first match in the line counts)
-s -- single match; dont search file further after first match
(similar to grep on a binary)
-H -- use hyperscan lib for scanning
-S -- only for hyperscan: interpret pattern as string literal instead of regex
-L -- machine has low mem; half chunk-size (default 2GB)
may be used multiple times
-I -- enable highlighting of matches (useful)
-n -- Use multiple cores in parallel (omit for single core)
-r -- recurse on directory
grab utilise la bibliothèque pcre , donc fondamentalement, c'est l'équivalent d'un grep -P -a
. Le -P
est important, car les expressions régulières compatibles Perl ont des caractéristiques différentes de celles des expressions rationnelles de base.
Il y a deux branches. master
et greppin
. Master est le grab « traditionnel » qui devrait être compilé et exécuté sur la plupart des systèmes POSIX. greppin
est livré avec sa propre version optimisée et parallélisée de nftw()
et readdir()
, qui double encore une fois la vitesse en plus de l'accélération déjà fournie par la branche master
. La branche greppin
fonctionne sous Linux, BSD et OSX. greppin
prend également en charge les bibliothèques hyperscan d'Intel qui tentent d'exploiter si possible les instructions SIMD du processeur (AVX2, AVX512, etc.) lors de la compilation du modèle d'expression régulière en code JIT.
Vous souhaiterez probablement créer la branche greppin
:
$ git checkout greppin
[...]
$ cd src; make
[...]
Assurez-vous que les packages de bibliothèque pcre et pcre2 sont installés. Sur les systèmes BSD, vous avez besoin gmake
au lieu de make
. Si vous souhaitez utiliser une technologie de pointe avec le moteur d'expressions régulières multiples de greppin et la prise en charge de l'hyperscan, vous devez d'abord l'obtenir et le construire :
$ git clone https://github.com/intel/hyperscan
[...]
$ cd hyperscan
$ mkdir build; cd build
$ cmake -DFAT_RUNTIME=1 -DBUILD_STATIC_AND_SHARED=1 ..
[...]
$ make
[...]
Cela créera ce qu'on appelle un gros runtime des bibliothèques hyperscan qui prennent en charge toutes les familles de processeurs afin de sélectionner le bon modèle de compilation au moment de l'exécution pour obtenir la plupart des performances. Une fois la construction terminée, vous construisez greppin contre cela :
(à l'intérieur du dépôt cloné)
$ cd src
$ HYPERSCAN_BUILD=/path/to/hyperscan/build make -f Makefile.hs
[...]
Cela produira un binaire greppin
qui permettra à l'option -H
de charger un moteur différent au moment de l'exécution, en essayant d'exploiter tous les bits de performances possibles.
Vous pouvez le lier à des bibliothèques déjà installées, mais l'API a récemment ajouté certaines fonctions dans la version 5.x et la plupart des distributions sont livrées avec 4.x.
grab utilise mmap(2)
et correspond à l'ensemble du fichier blob sans compter les nouvelles lignes (ce que grep fait même s'il n'y a pas de correspondance [d'après une de mes révisions de code grep en 2012 ; les choses peuvent être différentes aujourd'hui]), ce qui est beaucoup plus rapide que read(2)
-ing le fichier en petits morceaux et en comptant les nouvelles lignes. Si disponible, grab utilise également la fonctionnalité PCRE JIT. Cependant, les accélérations ne sont mesurables que sur les arborescences de fichiers volumineuses ou sur les disques durs ou SSD rapides. Dans ce dernier cas, l'accélération peut être vraiment drastique (jusqu'à 3 fois plus rapide) si elle correspond de manière récursive et parallèle. Étant donné que le stockage constitue le goulot d'étranglement, paralléliser la recherche sur les disques durs n'a aucun sens, car la recherche prend plus de temps que de simplement effectuer des tâches de manière linéaire.
De plus, grab ignore les fichiers trop petits pour contenir l’expression régulière. Pour les expressions régulières plus volumineuses dans une recherche récursive, cela peut ignorer une assez bonne quantité de fichiers sans même les ouvrir.
Une toute nouvelle bibliothèque pcre est requise, sur certains systèmes plus anciens, la construction peut échouer en raison de PCRE_INFO_MINLENGTH
et pcre_study()
manquants.
Les fichiers sont mappés et mis en correspondance en morceaux de 1 Go. Pour les fichiers plus volumineux, les 4 096 derniers octets (1 page) d'un bloc se chevauchent, de sorte que les correspondances sur une limite de 1 Go puissent être trouvées. Dans ce cas, vous voyez le match doublé (mais avec le même décalage).
Si vous mesurez grep par rapport à grab , n'oubliez pas de supprimer les caches dentry et de page entre chaque exécution : echo 3 > /proc/sys/vm/drop_caches
Notez que grep n'imprimera que des "correspondances de fichiers binaires", s'il détecte des fichiers binaires, tandis que grab imprimera toutes les correspondances, à moins que -s
ne soit indiqué. Ainsi, pour un test de vitesse, vous devez rechercher une expression qui n'existe pas dans les données, afin d'imposer la recherche dans l'ensemble des fichiers.
grab a été conçu pour parcourir rapidement de grandes arborescences de répertoires sans indexation. Le grep d'origine a de loin un ensemble d'options plus complet. L'accélération d'une correspondance avec un seul fichier est très faible, voire pas du tout mesurable.
Pour les SSD, l'option multicœur est logique. Pour les disques durs, ce n'est pas le cas, car la tête doit être positionnée d'avant en arrière entre les threads, ce qui risque de détruire le principe de localité et de tuer les performances.
La branche greppin
dispose de sa propre version parallèle sans verrouillage de nftw()
, de sorte que le temps d'inactivité de N - 1 cœurs lorsque le 1er cœur construit l'arborescence de répertoires peut également être utilisé pour travailler.
Ce qu'il reste à noter : grab traversera physiquement les répertoires, c'est-à-dire qu'il ne suivra pas les liens symboliques.
spot
est la version parallèle de find
. Il prend en charge les options les plus fréquemment utilisées telles que vous les connaissez. Il n'y a pas grand chose à dire, il suffit de l'essayer.
Ceci montre l'accélération sur une machine à 4 cœurs avec une recherche sur un SSD :
root@linux:~# echo 3 > /proc/sys/vm/drop_caches
root@linux:~# time grep -r foobardoesnotexist /source/linux
real 0m34.811s
user 0m3.710s
sys 0m10.936s
root@linux:~# echo 3 > /proc/sys/vm/drop_caches
root@linux:~# time grab -r foobardoesnotexist /source/linux
real 0m31.629s
user 0m4.984s
sys 0m8.690s
root@linux:~# echo 3 > /proc/sys/vm/drop_caches
root@linux:~# time grab -n 2 -r foobardoesnotexist /source/linux
real 0m15.203s
user 0m3.689s
sys 0m4.665s
root@linux:~# echo 3 > /proc/sys/vm/drop_caches
root@linux:~# time grab -n 4 -r foobardoesnotexist /source/linux
real 0m13.135s
user 0m4.023s
sys 0m5.581s
Avec branche greppin
:
root@linux:~# echo 3 > /proc/sys/vm/drop_caches
root@linux:~# time grep -a -P -r linus /source/linux/|wc -l
16918
real 1m12.470s
user 0m49.548s
sys 0m6.162s
root@linux:~# echo 3 > /proc/sys/vm/drop_caches
root@linux:~# time greppin -n 4 -r linus /source/linux/|wc -l
16918
real 0m8.773s
user 0m4.670s
sys 0m5.837s
root@linux:~#
Oui! ~ 9s contre ~ 72s ! C'est 8 fois plus rapide sur une machine SSD à 4 cœurs que le grep traditionnel.
Juste pour prouver que cela a abouti au même résultat :
root@linux:~# echo 3 > /proc/sys/vm/drop_caches
root@linux:~# greppin -n 4 -r linus /source/linux/|sort|md5sum
a1f9fe635bd22575a4cce851e79d26a0 -
root@linux:~# echo 3 > /proc/sys/vm/drop_caches
root@linux:~# grep -P -a -r linus /source/linux/|sort|md5sum
a1f9fe635bd22575a4cce851e79d26a0 -
root@linux:~#
Dans la comparaison à un seul cœur, l'accélération dépend également du processeur sur lequel le noyau planifie réellement le grep , donc une capture peut ou non être plus rapide (la plupart du temps, elle l'est). Si la charge est égale entre les tests monocœur, grab verra une accélération lors de la recherche sur des arborescences de fichiers volumineuses. Sur les configurations multicœurs, grab peut bénéficier du corse.
Le projet peut être trouvé ici.
La principale accélération présente dans leurs tables de référence provient du fait que ripgrep ignore de nombreux fichiers (notamment les fichiers dot) lorsqu'il est invoqué sans options spéciales et traite également les fichiers binaires comme une cible à correspondance unique (similaire à grep ). Afin d'avoir des résultats comparables, gardez à l'esprit (4 est le nombre de cœurs) :
echo 3 > /proc/sys/vm/drop_caches
entre chaque exécution-j 4 -a --no-unicode --no-pcre2-unicode -uuu --mmap
à ripgrep , car il correspondra par défaut à Unicode qui est 3 fois plus lent et essaie de compenser la perte de vitesse en sautant "ignorer" -fichiers basés sur. -e
est plus rapide que -P
, donc mieux vaut choisir -e
, mais ce n'est pas aussi puissant qu'un PCRE/dev/null
pour éviter les effets basés sur tty-H -n 4
à greppin si vous voulez de meilleures performances. -H
est compatible PCRE à quelques exceptions près (selon le document hyperscan)setfattr -n user.pax.flags -v "m" /path/to/binary
si vous exécutez sur des systèmes grsec et avez besoin de mappages rwx JIT Alors allez-y et vérifiez les horaires. Même sans hyperscan, greppin
est nettement plus rapide que rg
lors de l'utilisation d'expressions PCRE2 (PCRE2 contre PCRE2) et encore plus rapide lors de la comparaison des expressions les plus rapides (-e contre hyperscan).