Bagaimana grep bekerja begitu cepat?

113

Saya sangat kagum dengan fungsi GREP di shell, sebelumnya saya menggunakan metode substring di java tapi sekarang saya menggunakan GREP untuk itu dan dijalankan dalam hitungan detik, itu lebih cepat dari kode java yang biasa saya tulis. (menurut pengalaman saya, saya mungkin salah)

Yang sedang berkata saya belum bisa mengetahui bagaimana itu terjadi? juga tidak banyak tersedia di web.

Adakah yang bisa membantu saya dengan ini?

Bung
sumber
5
Ini adalah open source sehingga Anda dapat melihatnya sendiri. gnu.org/software/grep/devel.html
driis
6
Ridiculous Fish memiliki artikel bagus yang menjawab pertanyaan Anda dengan tepat: ridiculousfish.com/blog/posts/old-age-and-treachery.html
David Wolever
@WilliamPursell Ketika waktu eksekusi berjalan dalam hitungan detik, JIT mungkin telah memanas dan perbedaan yang mencengangkan adalah karena (1) grep menjadi sangat cerdas tentang apa yang dilakukannya dan (2) kode Java membuat pilihan algoritma yang sangat buruk untuk masalah spesifik yang difokuskan grep.
3
Berapa banyak waktu yang dihabiskan implementasi Java Anda untuk memulai JVM, dan berapa banyak waktu yang dihabiskan untuk mengeksekusi kode Anda? Atau mungkin masalah algoritme yang Anda gunakan dalam kode Java; algoritma O (N ^ 2) cenderung lambat dalam bahasa apa pun.
Keith Thompson

Jawaban:

169

Dengan asumsi pertanyaan Anda berhubungan GNU grepsecara khusus. Berikut catatan dari penulis, Mike Haertel:

GNU grep cepat karena MENGHINDARI MENCARI SETIAP INPUT BYTE.

GNU grep cepat karena mengeksekusi instruksi SANGAT BEBERAPA UNTUK SETIAP BYTE bahwa itu tidak melihat.

GNU grep menggunakan algoritme Boyer-Moore yang terkenal, yang pertama mencari huruf terakhir dari string target, dan menggunakan tabel pencarian untuk memberi tahu seberapa jauh ia dapat melewati masukan setiap kali menemukan karakter yang tidak cocok.

GNU grep juga membuka gulungan loop dalam Boyer-Moore, dan mengatur entri tabel delta Boyer-Moore sedemikian rupa sehingga tidak perlu melakukan tes keluar loop pada setiap langkah yang dibuka. Hasilnya adalah, dalam batasnya, GNU grep rata-rata kurang dari 3 x86 instruksi yang dieksekusi untuk setiap byte input yang dilihatnya (dan melewatkan banyak byte seluruhnya).

GNU grep menggunakan panggilan sistem input Unix mentah dan menghindari penyalinan data setelah membacanya. Selain itu, GNU grep MENGHINDARI MEMUTUSKAN INPUT MENJADI GARIS. Mencari baris baru akan memperlambat grep beberapa kali lipat, karena untuk menemukan baris baru itu harus melihat setiap byte!

Jadi alih-alih menggunakan input berorientasi garis, GNU grep membaca data mentah menjadi buffer besar, mencari buffer menggunakan Boyer-Moore, dan hanya ketika menemukan kecocokan, ia akan pergi dan mencari baris baru yang terikat (Opsi baris perintah tertentu seperti - n nonaktifkan pengoptimalan ini.)

Jawaban ini adalah bagian dari informasi yang diambil dari sini .

Steve
sumber
41

Untuk menambah jawaban Steve yang luar biasa.

Ini mungkin tidak dikenal secara luas tetapi grep hampir selalu lebih cepat saat grep untuk string pola yang lebih panjang daripada yang pendek, karena dalam pola yang lebih panjang, Boyer-Moore dapat melompat maju dalam langkah yang lebih lama untuk mencapai kecepatan sublinear yang lebih baik :

Contoh:

# 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

Bentuk yang lebih panjang 35% lebih cepat!

Bagaimana bisa? Boyer-Moore menyusun tabel lewati maju dari string pola, dan setiap kali ada ketidakcocokan, ia memilih lompatan terpanjang yang mungkin (dari karakter terakhir ke karakter pertama) sebelum membandingkan satu karakter di input ke karakter di tabel lewati.

Berikut adalah video yang menjelaskan Boyer Moore (Penghargaan untuk kommradHomer)

Kesalahpahaman umum lainnya (untuk GNU grep) fgrepadalah lebih cepat dari grep. fin fgreptidak berarti 'cepat', itu singkatan dari 'tetap' (lihat halaman manual), dan karena keduanya adalah program yang sama, dan keduanya menggunakan Boyer-Moore , tidak ada perbedaan kecepatan di antara mereka saat mencari tetap- string tanpa karakter khusus regexp. Alasan saja aku digunakan fgrepadalah ketika ada char khusus regexp (seperti ., [], atau *) saya tidak ingin ditafsirkan seperti itu. Dan bahkan kemudian bentuk yang lebih portabel / standar grep -Flebih disukai fgrep.

arielf
sumber
3
Intuitif bahwa pola yang lebih panjang lebih cepat. Jika polanya satu byte maka grep harus memeriksa setiap byte. Jika polanya 4-byte maka itu bisa membuat lompatan 4-byte. Jika polanya sepanjang teks maka grep hanya akan melakukan satu langkah.
noel
12
Ya, ini intuitif - jika Anda memahami cara kerja Boyer-Moore.
arielf
2
Bahkan sebaliknya, itu intuitif. Akan lebih mudah untuk menemukan jarum panjang di tumpukan jerami daripada yang lebih pendek
RajatJ
2
Contoh kontra untuk "menjadi lebih cepat saat lebih lama" adalah kasus di mana Anda harus melakukan banyak tes sebelum gagal, dan Anda tetap tidak dapat melanjutkan. Katakanlah file tersebut xs.txtberisi 100000000 'x, dan Anda melakukannya grep yx xs.txt, maka sebenarnya gagal untuk menemukan kecocokan lebih cepat daripada jika Anda melakukannya grep yxxxxxxxxxxxxxxxxxxx xs.txt. Peningkatan Boyer-Moore-Horspool menjadi Boyer-Moore meningkatkan lompatan ke depan dalam kasus itu, tetapi mungkin tidak hanya tiga instruksi mesin dalam kasus umum.
lrn
2
@Terima kasih. Ya, tampaknya hari-hari (GNU) grep/fgrep/egrepmenjadi semua hardlink ke executable yang sama telah berlalu. Mereka (dan ekstensi lain seperti z*grep bz*greputilitas yang mendekompresi dengan cepat), sekarang menjadi pembungkus cangkang kecil grep. Beberapa komentar historis yang menarik tentang peralihan antara satu executable & pembungkus shell dapat ditemukan di komit ini: git.savannah.gnu.org/cgit/grep.git/commit/…
arielf