Come funziona grep così velocemente?


113

Sono davvero stupito dalla funzionalità di GREP in shell, prima usavo il metodo della sottostringa in java ma ora uso GREP per questo e viene eseguito in pochi secondi, è incredibilmente più veloce del codice java che ho usato per scrivere. (secondo la mia esperienza potrei sbagliarmi però)

Detto questo non sono riuscito a capire come stia succedendo? inoltre non c'è molto disponibile sul web.

Qualcuno mi può aiutare con questo?


5
È open source quindi puoi dare un'occhiata da solo. gnu.org/software/grep/devel.html
driis

6
Ridiculous Fish ha un ottimo articolo che risponde esattamente alla tua domanda: ridiculousfish.com/blog/posts/old-age-and-treachery.html
David Wolever,

@WilliamPursell Quando il tempo di esecuzione è di pochi secondi, il JIT si è probabilmente riscaldato e la differenza sconvolgente è dovuta al fatto che (1) grep è incredibilmente intelligente su ciò che fa e (2) il codice Java fa una scelta piuttosto sbagliata dell'algoritmo per il problema specifico su cui si concentra grep.

3
Quanto tempo impiega la tua implementazione Java per avviare la JVM e quanto tempo impiega effettivamente per eseguire il tuo codice? Oppure potrebbe essere una questione dell'algoritmo che hai utilizzato nel tuo codice Java; è probabile che un algoritmo O (N ^ 2) sia lento in qualsiasi lingua.
Keith Thompson,

Risposte:


169

Supponendo che la tua domanda riguardi GNU grepspecificamente. Ecco una nota dell'autore, Mike Haertel:

GNU grep è veloce perché EVITA DI GUARDARE OGNI INPUT BYTE.

GNU grep è veloce perché esegue le istruzioni POCHISSIME per ogni byte che fa guardare.

GNU grep utilizza il noto algoritmo di Boyer-Moore, che cerca prima la lettera finale della stringa di destinazione e utilizza una tabella di ricerca per dirgli quanto avanti può saltare nell'input ogni volta che trova un carattere non corrispondente.

GNU grep srotola anche il ciclo interno di Boyer-Moore e imposta le voci della tabella delta Boyer-Moore in modo tale che non sia necessario eseguire il test di uscita dal ciclo ad ogni passaggio svolto. Il risultato di ciò è che, nel limite, GNU grep media meno di 3 istruzioni x86 eseguite per ogni byte di input che effettivamente guarda (e salta completamente molti byte).

GNU grep utilizza chiamate di sistema di input Unix non elaborate ed evita di copiare i dati dopo averli letti. Inoltre, GNU grep EVITA DI INTERROMPERE L'INPUT IN LINEE. La ricerca di newline rallenterebbe grep di un fattore di parecchie volte, perché per trovare le newline dovrebbe guardare ogni byte!

Quindi, invece di usare l'input orientato alla riga, GNU grep legge i dati grezzi in un buffer di grandi dimensioni, cerca nel buffer usando Boyer-Moore, e solo quando trova una corrispondenza va a cercare le nuove righe di delimitazione (alcune opzioni della riga di comando come - n disabilitare questa ottimizzazione.)

Questa risposta è un sottoinsieme delle informazioni prese da qui .


41

Da aggiungere all'eccellente risposta di Steve.

Potrebbe non essere molto conosciuto, ma grep è quasi sempre più veloce quando si cerca una stringa di pattern più lunga di una corta, perché in un pattern più lungo, Boyer-Moore può saltare in avanti a passi più lunghi per ottenere velocità sublineari ancora migliori :

Esempio:

# after running these twice to ensure apples-to-apples comparison
# (everything is in the buffer cache) 

$ time grep -c 'tg=f_c' 20140910.log
28
0.168u 0.068s 0:00.26

$ time grep -c ' /cc/merchant.json tg=f_c' 20140910.log
28
0.100u 0.056s 0:00.17

La forma più lunga è il 35% più veloce!

Come mai? Boyer-Moore costruisce una tabella di salto in avanti dalla stringa del modello e ogni volta che c'è una mancata corrispondenza, seleziona il salto più lungo possibile (dall'ultimo carattere al primo) prima di confrontare un singolo carattere nell'input con il carattere nella tabella di salto.

Ecco un video che spiega Boyer Moore (credito a kommradHomer)

Un altro malinteso comune (per GNU grep) è che fgrepsia più veloce di grep. fin fgrepnon sta per 'fast', sta per 'fixed' (vedere la pagina man), e poiché entrambi sono lo stesso programma, ed entrambi usano Boyer-Moore , non c'è differenza di velocità tra loro quando si cerca fixed- stringhe senza caratteri speciali regexp. L'unica ragione per cui uso fgrepè quando c'è un carattere speciale regexp (come ., []o *) io non voglio che sia interpretato come tale. E anche in questo caso grep -Fsi preferisce la forma più portatile / standard di fgrep.


3
È intuitivo che i modelli più lunghi siano più veloci. Se il pattern fosse un byte, grep dovrebbe controllare ogni byte. Se il modello è di 4 byte, allora potrebbe fare salti di 4 byte. Se il pattern fosse lungo quanto il testo, grep farebbe solo un passaggio.
noel

12
Sì, è intuitivo, se capisci come funziona Boyer-Moore.
arielf il

2
Anche altrimenti è intuitivo. Sarebbe più facile trovare un ago lungo in un pagliaio che uno più corto
RajatJ

2
Il contro esempio di "essere più veloci quando più a lungo" sono i casi in cui devi fare molti test prima di fallire, e comunque non puoi andare avanti. Supponiamo che il file xs.txtcontenga 100000000 'x, e lo fai grep yx xs.txt, quindi in realtà non riesce a trovare una corrispondenza prima che se lo fai grep yxxxxxxxxxxxxxxxxxxx xs.txt. Il miglioramento di Boyer-Moore-Horspool rispetto a Boyer-Moore migliora il salto in avanti in quel caso, ma probabilmente non saranno solo tre istruzioni macchina nel caso generale.
lrn

2
@Tino grazie. Sì, sembra che i giorni in cui (GNU) grep/fgrep/egreperano tutti i collegamenti fisici allo stesso eseguibile siano finiti. Loro (e altre estensioni come le z*grep bz*greputilità che si decomprimono al volo), ora sono piccoli involucri di shell grep. Alcuni commenti storici interessanti sul passaggio tra un singolo eseguibile e wrapper di shell possono essere trovati in questo commit: git.savannah.gnu.org/cgit/grep.git/commit/…
arielf
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.