Làm thế nào để grep chạy nhanh như vậy?


113

Tôi thực sự ngạc nhiên bởi chức năng của GREP trong shell, trước đây tôi thường sử dụng phương thức chuỗi con trong java nhưng bây giờ tôi sử dụng GREP cho nó và nó thực thi chỉ trong vài giây, nó nhanh hơn rất nhiều so với mã java mà tôi đã từng viết. (theo kinh nghiệm của tôi, tôi có thể sai mặc dù)

Điều đó đang được nói rằng tôi đã không thể hiểu nó đang xảy ra như thế nào? cũng không có nhiều trên web.

Bất cứ ai có thể giúp tôi với điều này?


5
Nó là mã nguồn mở nên bạn có thể tự tìm hiểu. gnu.org/software/grep/devel.html
driis

6
Nực cười Cá có một writeup lớn trả lời chính xác câu hỏi của bạn: ridiculousfish.com/blog/posts/old-age-and-treachery.html
David Wolever

@WilliamPursell Khi thời gian thực thi diễn ra trong vài giây, JIT có thể đã nóng lên và sự khác biệt gây nhức nhối là do (1) grep cực kỳ thông minh về những gì nó làm và (2) mã Java đưa ra một lựa chọn thuật toán khá tệ cho vấn đề cụ thể mà grep tập trung vào.

3
Việc triển khai Java của bạn dành bao nhiêu thời gian để khởi động JVM và nó dành bao nhiêu thời gian để thực thi mã của bạn? Hoặc nó có thể là vấn đề của thuật toán bạn đã sử dụng trong mã Java của mình; thuật toán O (N ^ 2) có thể chậm ở bất kỳ ngôn ngữ nào.
Keith Thompson

Câu trả lời:


169

Giả sử câu hỏi của bạn liên quan đến GNU grepcụ thể. Đây là ghi chú của tác giả, Mike Haertel:

GNU grep nhanh vì nó TRÁNH NHÌN VÀO MỌI ĐẦU VÀO BYTE.

GNU grep là nhanh vì nó thực hiện các lệnh rất ít cho mỗi byte mà nó không nhìn vào.

GNU grep sử dụng thuật toán Boyer-Moore nổi tiếng, thuật toán này tìm kiếm chữ cái cuối cùng của chuỗi mục tiêu trước tiên và sử dụng bảng tra cứu để cho biết nó có thể bỏ qua bao xa trong đầu vào bất cứ khi nào nó tìm thấy một ký tự không khớp.

GNU grep cũng giải phóng vòng lặp bên trong của Boyer-Moore và thiết lập các mục nhập bảng delta Boyer-Moore theo cách mà nó không cần thực hiện kiểm tra thoát vòng lặp ở mỗi bước chưa được cuộn. Kết quả của việc này là, trong giới hạn, GNU grep trung bình có ít hơn 3 lệnh x86 được thực thi cho mỗi byte đầu vào mà nó thực sự xem xét (và nó hoàn toàn bỏ qua nhiều byte).

GNU grep sử dụng các lệnh gọi hệ thống đầu vào Unix thô và tránh sao chép dữ liệu sau khi đọc. Hơn nữa, GNU grep TRÁNH BẬT ĐẦU VÀO VÀO LINES. Việc tìm kiếm các dòng mới sẽ làm chậm tốc độ đi vài lần, bởi vì để tìm các dòng mới, nó sẽ phải xem xét từng byte!

Vì vậy, thay vì sử dụng đầu vào theo hướng dòng, GNU grep đọc dữ liệu thô vào một bộ đệm lớn, tìm kiếm bộ đệm bằng Boyer-Moore và chỉ khi tìm thấy kết quả phù hợp thì nó mới đi và tìm các dòng mới bị ràng buộc (Một số tùy chọn dòng lệnh như - n vô hiệu hóa tối ưu hóa này.)

Câu trả lời này là một tập hợp con của thông tin được lấy từ đây .


41

Để thêm vào câu trả lời xuất sắc của Steve.

Nó có thể không được nhiều người biết đến nhưng grep hầu như luôn nhanh hơn khi ghi xám cho một chuỗi mẫu dài hơn một chuỗi ngắn, bởi vì trong một mẫu dài hơn, Boyer-Moore có thể bỏ qua về phía trước trong những bước dài hơn để đạt được tốc độ tuyến tính con tốt hơn :

Thí dụ:

# 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

Dạng dài hơn nhanh hơn 35%!

Làm thế nào mà? Boyer-Moore cấu trúc một bảng chuyển tiếp từ chuỗi mẫu và bất cứ khi nào có sự không khớp, nó sẽ chọn lần bỏ qua dài nhất có thể (từ ký tự cuối đến ký tự đầu tiên) trước khi so sánh một ký tự trong đầu vào với ký tự trong bảng bỏ qua.

Đây là video giải thích về Boyer Moore (Tín dụng cho kommradHomer)

Một quan niệm sai lầm phổ biến khác (đối với GNU grep) fgreplà nhanh hơn grep. fin fgrepkhông có nghĩa là "nhanh", nó là viết tắt của "fixed" (xem trang người đàn ông), và vì cả hai đều là cùng một chương trình và cả hai đều sử dụng Boyer-Moore , không có sự khác biệt về tốc độ giữa chúng khi tìm kiếm fixed- chuỗi không có ký tự đặc biệt regexp. Lý do duy nhất mà tôi sử dụng fgreplà khi có một regexp đặc biệt char (như ., []hoặc *) Tôi không muốn nó được giải thích như vậy. Và thậm chí sau đó, hình thức di động / tiêu chuẩn grep -Fhơn được ưa thích hơn fgrep.


3
Thật trực quan rằng các mẫu dài hơn nhanh hơn. Nếu mẫu là một byte thì grep sẽ phải kiểm tra từng byte. Nếu mẫu là 4 byte thì nó có thể bỏ qua 4 byte. Nếu mẫu dài bằng văn bản thì grep sẽ chỉ thực hiện một bước.
noel

12
Vâng, nó là trực quan - nếu bạn hiểu cách Boyer-Moore hoạt động.
mình

2
Ngay cả khi khác nó trực quan. Sẽ dễ dàng tìm thấy một cây kim dài trong đống cỏ khô hơn là một cây kim ngắn hơn
RajatJ

2
Ví dụ ngược lại cho "nhanh hơn khi lâu hơn" là các trường hợp bạn phải làm rất nhiều bài kiểm tra trước khi thất bại và bạn không thể tiến lên phía trước. Giả sử tệp xs.txtchứa 100000000 'x's, và bạn làm vậy grep yx xs.txt, thì nó thực sự không tìm thấy kết quả phù hợp sớm hơn nếu bạn làm grep yxxxxxxxxxxxxxxxxxxx xs.txt. Việc cải tiến Boyer-Moore-Horspool cho Boyer-Moore cải thiện phần bỏ qua trong trường hợp đó, nhưng có lẽ nó sẽ không chỉ là ba lệnh máy trong trường hợp chung.
vào

2
@Tino cảm ơn. Vâng, có vẻ như những ngày của (GNU) grep/fgrep/egreplà tất cả các liên kết cứng đến cùng một tệp thực thi đã không còn nữa. Chúng (và các phần mở rộng khác như z*grep bz*greputils được giải nén khi đang bay), giờ đây là những lớp bao bọc nhỏ xung quanh grep. Bạn có thể tìm thấy một số nhận xét lịch sử thú vị về việc chuyển đổi giữa một trình bao bọc trình bao và tệp thực thi duy nhất trong cam kết này: git.savannah.gnu.org/cgit/grep.git/commit/…
thân
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.