Ripgrep: Công cụ tìm kiếm nhanh hơn grep, ag, Git grep, v.v. (2016)
(blog.burntsushi.net)- ripgrep(
rg) là công cụ tìm kiếm dòng lệnh viết bằng Rust, kết hợp sự tiện lợi khi tìm kiếm mã kiểu The Silver Searcher với hiệu năng thô ở mức GNU grep, đồng thời cung cấp binary cho Linux, Mac và Windows - Trong 25 bài benchmark, không có công cụ nào vượt trội rõ ràng hơn
ripgrepcả về hiệu năng lẫn độ chính xác ở cả tìm kiếm trên một tệp lớn đơn lẻ và trong thư mục quy mô lớn, đồng thời chi phí cho hỗ trợ Unicode cũng được giữ ở mức thấp - Công cụ này mở rộng phạm vi sử dụng thực tế của công cụ tìm kiếm mã nhờ bao gồm xử lý
.gitignore, mặc định loại trừ tệp ẩn và tệp nhị phân, bộ lọc loại tệp, hỗ trợ PCRE2 tùy chọn, tìm kiếm trên nhiều bảng mã và tệp nén, cùng cả bộ lọc tiền xử lý - Sự khác biệt giữa thử nghiệm trên kho mã Linux kernel và OpenSubtitles2016 phụ thuộc lớn vào tối ưu hóa literal, tìm kiếm nhiều mẫu Teddy SIMD, Aho-Corasick, cách giải mã UTF-8, đếm dòng và chi phí xử lý
.gitignore - Khi tìm kiếm song song trên nhiều tệp nhỏ, memory map có thể chậm hơn, nhưng với một tệp lớn đơn lẻ thì có thể có lợi hơn, nên
ripgrepchia cách dùng giữa tìm kiếm bằng buffer trung gian và tìm kiếm bằng memory map tùy tình huống
Vị trí mà ripgrep hướng tới
ripgreplà công cụ tìm kiếm dòng lệnh nhắm tới việc kết hợp sự tiện lợi của công cụ tìm kiếm mã với hiệu năng của các công cụ họgrep- Đối tượng so sánh gồm
GNU grep,git grep,The Silver Searcher(ag),Universal Code Grep(ucg),The Platinum Searcher(pt),sift - Có ba điểm cốt lõi mà benchmark muốn kiểm chứng
- Không có công cụ nào vượt trội rõ ràng hơn
ripgrepở cả tìm kiếm trên một tệp đơn lẻ lẫn trong thư mục quy mô lớn - Có thể cung cấp hỗ trợ Unicode đúng nghĩa mà không đòi hỏi chi phí hiệu năng lớn
- Khi tìm kiếm nhiều tệp cùng lúc, memory map nhìn chung có thể chậm hơn thay vì nhanh hơn
- Không có công cụ nào vượt trội rõ ràng hơn
- Tác giả là người tạo ra
ripgrepvà cả regex engine nền tảng của nó, đồng thời thừa nhận benchmark có thể đã được chọn lọc và mang thiên lệch
Tính năng và hành vi mặc định
- Tên file thực thi của
ripgreplàrg - Theo mặc định, công cụ sẽ đệ quy duyệt thư mục hiện tại, tôn trọng
.gitignore, và bỏ qua tệp ẩn cũng như tệp nhị phân - Cũng hỗ trợ
.rgignore, và các mẫu trong.rgignoređược ưu tiên hơn.gitignore - Có thể mở rộng phạm vi bỏ qua ignore file, bao gồm tệp ẩn và tệp nhị phân bằng
-u,-uu,-uuurg -uuutương tựgrep -a -r
- Hỗ trợ bộ lọc loại tệp
rg -tpy foo: chỉ tìm trong tệp Pythonrg -Tjs foo: loại trừ tệp JavaScript- Có thể thêm quy tắc loại tệp mới bằng
--type-add
- Cũng cung cấp nhiều tính năng của
grep- xuất ngữ cảnh
- tìm kiếm nhiều mẫu
- tô sáng màu
- hỗ trợ Unicode đầy đủ
- Regex engine mặc định không hỗ trợ look-around và backreference, nhưng có thể dùng các tính năng đó khi chọn engine PCRE2 bằng
-P - Cũng hỗ trợ tự động nhận diện một phần UTF-16 và chỉ định bảng mã dựa trên
-E/--encoding- Bao gồm UTF-16, latin-1, GBK, EUC-JP, Shift_JIS v.v.
- Hỗ trợ tìm kiếm trong tệp nén như gzip, xz, lzma, bzip2, lz4 bằng
-z/--search-zip - Cũng hỗ trợ các bộ lọc tiền xử lý tùy ý như trích xuất văn bản từ PDF, giải nén bổ sung, giải mã và tự động phát hiện bảng mã
Lý do để không dùng
- Nếu ưu tiên cao nhất là tính di động và khả năng dùng ở mọi nơi, grep chuẩn hóa và được cài đặt rộng rãi sẽ phù hợp hơn
- Nếu phụ thuộc vào một tính năng hoặc lỗi cụ thể có ở công cụ khác thì
ripgrepcó thể không phù hợp - Ở một số trường hợp hiệu năng biên, công cụ khác có thể hoạt động tốt hơn
- Cũng có thể không dùng được nếu không thể cài đặt hoặc nền tảng không được hỗ trợ
Cấu trúc hoạt động của các công cụ họ grep
- Công cụ tìm kiếm nhìn chung trải qua ba bước lớn
- thu thập các tệp cần tìm
- thực hiện tìm kiếm thực tế
- xuất kết quả
- Các công cụ họ
grepcần tìm kiếm tốt trên tệp lớn nên hiệu năng của regex engine là yếu tố quan trọng - Các công cụ họ
ackphải xử lý nhanh việc duyệt thư mục đệ quy và áp dụng quy tắc ignore như.gitignore ripgrepcố gắng kết hợp hai cách tiếp cận này- regex engine nhanh
- tìm kiếm song song
- lọc đối tượng tìm kiếm
Thu thập tệp và xử lý ignore
- Với các công cụ họ
ack, điều quan trọng là nhanh chóng quyết định nên tìm trong tệp nào từ thư mục hiện tại - Hiệu năng duyệt thư mục bị ảnh hưởng bởi số lượng lệnh gọi
statkhông cần thiết ripgrepdùng bộ lặp thư mục đệ quy với mục tiêu tối thiểu hóa số system call- Việc xử lý
.gitignorecó chi phí- phải tìm ignore file trong từng thư mục
- phải biên dịch các mẫu ignore
- phải áp dụng các mẫu cho mọi đường dẫn ứng viên
- Kho mã Linux kernel có 4.640 thư mục và 178 tệp
.gitignore ripgrepcố gắng hỗ trợ đầy đủ hơn ngữ nghĩa của.gitignore, và ưu tiên mẫu khớp được định nghĩa gần đây nhấtucgcó thể nhanh hơn do dùng quy tắc glob dạng whitelist thay vì.gitignore, nhưng có thể bỏ sót tệp có phần mở rộng không được biết đến
Khác biệt giữa các regex engine
- Regex engine nhìn chung được chia thành hai nhóm
- dựa trên backtracking: giàu tính năng nhưng có thể chậm theo thời gian mũ với một số đầu vào
- dựa trên finite automata: có thể bị hạn chế tính năng nhưng bảo đảm thời gian tuyến tính theo độ dài văn bản tìm kiếm
- Engine của từng công cụ như sau
- GNU grep,
git grep: engine tự xây dựng dựa trên finite automata ripgrep: thư viện Rust regex, dựa trên finite automataag,ucg: backtracking dựa trên PCREpt,sift: thư viện Go regex, dựa trên finite automata
- GNU grep,
- Do dùng PCRE,
agvàucgcó thể gặp hành vi backtracking tệ nhất - Mẫu ví dụ
(a*)* ccó thể gây vấn đề với các công cụ dựa trên PCRE, nhưng các công cụ còn lại trong benchmark xử lý mà không gặp vấn đề
Tối ưu hóa literal và SIMD
- Trong tìm kiếm chuỗi đơn giản, tối ưu hóa tìm kiếm literal có thể còn quan trọng hơn cả regex engine
- Boyer-Moore là thuật toán tìm kiếm chuỗi con kinh điển, và có thể tận dụng các routine như
memchrđể nhanh chóng xác định vị trí ứng viên - Việc triển khai
memchrthường dùng lệnh SIMD để kiểm tra 16 byte cùng lúc, và có thể đạt thông lượng nhiều GB/s - Thư viện Rust regex tích cực trích xuất literal tiền tố và hậu tố từ mẫu
foo|bar(a|b)c[ab]foo[yz](foo)?bar(foo)*bar(foo){3,6}
- Nếu toàn bộ regex có thể được phân rã thành một literal đơn hoặc alternation của literal, có thể không cần dùng regex engine lõi
ripgreptận dụng đặc tính xuất kết quả theo dòng để trích xuất cả inner literal- Ví dụ: trong
\w+foo\d+, công cụ sẽ tìmfootrước rồi chỉ xác minh bằng regex trên các dòng ứng viên
- Ví dụ: trong
- Với tìm kiếm nhiều literal, GNU grep dùng thuật toán tương tự Commentz-Walter, còn Rust regex dùng Aho-Corasick hoặc thuật toán Teddy SIMD
- Teddy là thuật toán tìm kiếm nhiều mẫu dựa trên SIMD xuất phát từ Intel Hyperscan, và là một trong những tối ưu hóa cốt lõi giúp
ripgrepvượt GNU grep
Cách tìm kiếm: tránh tìm theo từng dòng
- Cách triển khai ngây thơ là đọc tệp từng dòng rồi áp dụng mẫu lên từng dòng, nhưng vì trong đa số trường hợp tìm kiếm thì match là hiếm nên cách này kém hiệu quả
- Công cụ tìm kiếm thường tìm trong các buffer byte lớn cùng lúc
- ánh xạ tệp bằng memory map
- đọc toàn bộ tệp vào bộ nhớ
- tìm kiếm tăng dần bằng buffer trung gian kích thước cố định
ripgrep, GNU grep vàgit grepđều hỗ trợ tìm kiếm tăng dần nên có thể áp dụng cho cả tệp lẫn stream- Tìm kiếm tăng dần khó triển khai
- tính số dòng
- xử lý khi buffer kết thúc giữa chừng của một dòng
- xử lý dòng dài
- xử lý invert match
- xử lý xuất ngữ cảnh quanh match
ripgrepchấp nhận độ phức tạp triển khai để dùng tìm kiếm tăng dần, và trong benchmark cho thấy kết quả nhanh hơn memory map khi tìm trên nhiều tệp nhỏ
Đầu ra và tính song song
- Trong tìm kiếm song song, nếu mỗi luồng xuất kết quả ngay lập tức thì kết quả từ các tệp khác nhau có thể bị trộn lẫn
- Mọi công cụ tìm kiếm mã song song đều ghi kết quả tìm kiếm vào bộ đệm trung gian trong bộ nhớ, rồi chỉ tuần tự hóa ở bước xuất ra
- Cách này cho phép các luồng tìm kiếm thực hiện việc tìm kiếm thực sự theo kiểu song song
- Nhược điểm là mức dùng bộ nhớ có thể tăng cao trong trường hợp như một tệp 2GB mà mọi dòng đều khớp
ripgrepghi trực tiếp rastdoutmà không dùng bộ đệm trung gian khi tìm kiếm trênstdinhoặc một tệp đơn
Phương pháp benchmark
- Benchmark được chia theo bài toán của người dùng cuối
- Tìm kiếm trong kho mã nguồn quy mô lớn
- Tìm kiếm trong một tệp lớn duy nhất
- Mẫu tìm kiếm thiên về literal đơn giản, alternation và regex nhẹ
- Mỗi công cụ có hành vi mặc định khác nhau, nên để so sánh công bằng, bài thử cố gắng đồng bộ các điều kiện như số dòng, Unicode,
.gitignore, whitelist - Các phiên bản được benchmark như sau
ripgrepv0.1.2- GNU grep v2.25
git grepv2.7.4agcommitcda635, PCRE 8.38ucgcommit487bfb, PCRE 10.21 JITptcommit509368siftcommit2d175c
ackbị loại vì ở thời điểm đó chậm hơn hẳn các công cụ khác- Trình chạy benchmark là
benchsuite, yêu cầu Python 3.5 trở lên, và được bao gồm trong khoripgrep - Mỗi lệnh chạy warm-up 3 lần trước khi đo để corpus được nạp vào OS page cache
- Mỗi lệnh được đo 10 lần và ghi lại giá trị trung bình cùng độ lệch chuẩn
- Môi trường chạy là Amazon EC2
c3.2xlarge, Ubuntu 16.04, Xeon E5-2680 2.8GHz, RAM 16GB, SSD 80GB - Nhật ký cấu hình, kết quả tóm tắt và CSV thô cũng được công bố
Kết quả tìm kiếm mã trong Linux kernel
- Benchmark tìm kiếm mã được chạy trên kho Linux kernel đã build ở commit
d0acc7 - Lý do dùng kho kernel đã build là vì các tạo phẩm sinh ra từ quá trình build có thể còn lại trong kho và ảnh hưởng đến độ liên quan của kết quả cũng như hiệu năng
- Trong
linux_literal_default, việc tìm literal đơn giảnPM_RESUMEcho thấy sự khác biệt về hành vi mặc định của từng công cụrgtôn trọng.gitignorevà bỏ qua tệp ẩn, tệp nhị phânagvàptcũng tương tự nhưng có đếm số dòngucgkhông đọc.gitignorevà tìm kiếm theo whitelistsiftmặc định tìm gần như mọi thứgit grepcó lợi thế là lấy tập tệp cần tìm từ git index
- Việc tôn trọng
.gitignoregiúp tăng độ liên quan của kết quả nhưng có thể phải trả giá về hiệu năng - Trong
linux_literal,rg (whitelist)cho hiệu năng gần nhưucg, cònrg (ignore)ở mức tương đươnggit grep rg (ignore) (mmap)vàag (ignore) (mmap)chậm hơn do dùng memory map, và trong cùng điều kiện đórg (ignore)nhanh hơn nhiều- Trên máy cục bộ, các phiên bản dùng memory map cũng chậm hơn, nhưng chênh lệch nhỏ hơn so với trên EC2
Unicode và tìm kiếm phân biệt hoa thường
- Trong
linux_literal_casei,ptchậm đi đáng kể vì xử lý-ibằng(?i)của Go regexp siftchậm ít hơn vì dùng cách chuyển mẫu và khối tìm kiếm sang chữ thường, nhưng tối ưu này chỉ xử lý hoa thường ASCII nên không chính xác với Unicoderipgrepchuyển tìm kiếm không phân biệt hoa thường thành các tổ hợp literal nếu có thể, rồi dùng Teddy để tìm nhanh các vị trí ứng viên- Tìm kiếm
\wAhtronglinux_unicode_worddùng để kiểm tra xem\wnhận biết Unicode có bắt được các kết quả nhưµAhhay không - Chỉ
rgvàgit grepcó thể bật/tắt Unicode;ag,pt,sift,ucgđều dùng\wchỉ hỗ trợ ASCII git grepchịu chi phí hiệu năng lớn khi bật hỗ trợ Unicode, cònripgrepgần như không suy giảm hiệu năngripgreptích hợp giải mã UTF-8 vào máy trạng thái hữu hạn, nên khớp trực tiếp trên chuỗi byte UTF-8 mà không cần bước giải mã riêng
Khác biệt theo độ phức tạp của regex
- Với regex có hậu tố literal như
[A-Z]+_RESUME,rgvàucgdùng_RESUMEđể nhanh chóng tìm vị trí ứng viên - Với alternation literal như
ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT,ripgrepdùng Teddy và thậm chí có thể không cần dùng đến regex engine cốt lõi - Ngay cả với alternation không phân biệt hoa thường,
ripgrepvẫn tạo các prefix tổ hợp hoa thường để Teddy tìm ứng viên, rồi chỉ xác thực các ứng viên đó bằng regex đầy đủ - Trong tìm kiếm
\p{Greek}, chỉ Rust regex và Go regex hỗ trợ thuộc tính Unicode đó, vàrgnhanh hơnpt,siftrất nhiều - Trong tìm kiếm
\p{Greek}không phân biệt hoa thường,siftkhông báo được kết quả khớp, cònptkhông xử lý đúng hoa thường Unicode - Với các mẫu không có literal như
\w{5}\s+..., hiệu năng của regex engine bộc lộ trực tiếprgvẫn thuộc nhóm nhanh ngay cả khi bật hỗ trợ Unicodegit grepchịu chi phí lớn khi bật hỗ trợ Unicode- Unicode DFA phải xử lý một tập trạng thái NFA lớn hơn rất nhiều so với ASCII DFA; con số ví dụ là khoảng 250 trạng thái NFA cho ASCII và khoảng 77.000 cho Unicode
Tìm kiếm trong một tệp lớn duy nhất
- Benchmark tệp đơn dùng mẫu OpenSubtitles2016
- Mẫu tiếng Anh khoảng 1GB
- Mẫu tiếng Nga khoảng 1,6GB
- Trong mảng này, hiệu năng của regex engine và tối ưu literal trở nên quan trọng hơn
- Trong
subtitles_literal, cả tìm kiếmSherlock HolmeslẫnШерлок Холмсđều cho thấyrglà nhanh nhất ripgrepcố chọn byte hiếm trong literal để dùng vớimemchr- Cài đặt Boyer-Moore tiêu chuẩn thường dùng byte cuối cùng để tìm vị trí ứng viên
rgchọn byte hiếm hơn để bỏ qua được xa hơn trong vòng lặp tối ưu SIMD
- Với mẫu tiếng Nga, nhiều ký tự trong UTF-8 bắt đầu bằng
\xD0hoặc\xD1, nên tìm theo byte đầu tiên có thể kém hiệu quả rgdùng bảng tần suất 256 byte được tính sẵn để ưu tiên các byte hiếm hơn thay vì\xD0,\xD1- Với một tệp lớn duy nhất, memory map chỉ cần được tạo một lần, vì vậy tìm kiếm bằng memory map của
rgnhanh hơnrg (no mmap)khoảng 25%
Unicode và alternation trong một tệp đơn
- Trong
subtitles_literal_casei,rgvừa xử lý đúng tìm kiếm Unicode không phân biệt hoa thường vừa giữ được tốc độ cao - GNU grep chịu chi phí lớn với tìm kiếm Unicode không phân biệt hoa thường
- Trong tìm kiếm tiếng Nga không phân biệt hoa thường,
grep (ASCII)có vẻ thực chất bỏ qua-i, cònagbáo 0 kết quả khớp - Trong
subtitles_alternate, tìm kiếm alternation với nhiều tên nhân vật cho thấyrgnhanh nhất ở cả tiếng Anh lẫn tiếng Nga - Với alternation tiếng Anh,
rgnhanh hơn GNU grep khoảng một bậc độ lớn - Trong
subtitles_alternate_casei,rgchậm hơn nhiều so với trước đó nhưng vẫn vượt các công cụ khác ở tiếng Anh - Trong trường hợp này, số lượng literal ứng viên quá nhiều để Teddy xử lý, nên
rgchuyển từ Teddy sang Aho-Corasick ripgrepdùng Aho-Corasick “nâng cao” dựa trên bảng chuyển trạng thái, thực hiện một lần chuyển cho mỗi byte đầu vào
Inner literal và mẫu không có literal
- Các mẫu như
\w+\s+Holmes\s+\w+được cấu tạo để tránh tối ưu hóa literal ở tiền tố/hậu tố, nhưng vẫn có thể tận dụng inner literalHolmes ripgrepvà GNU grep đều thực hiện tối ưu hóa inner literalripgrepsử dụngregex-syntaxcủa Rust regex để trích xuất literal từ AST của mẫu- Với phiên bản tiếng Nga
\w+\s+Холмс\s+\w+, chỉ những công cụ hỗ trợ Unicode đúng cách mới có thể cho ra kết quả có ý nghĩa - Với mẫu dài
\w{5}\s+...hoàn toàn không có literal,rgthuộc nhóm nhanh nhất trên tiếng Anh, còn bản GNU grep có hỗ trợ Unicode bị loại vì mất hơn 90 giây trên tiếng Anh và hơn 4 phút trên tiếng Nga ripgrepduy trì hỗ trợ Unicode mà vẫn đảm bảo hiệu năng bằng cách đưa giải mã UTF-8 vào trong DFA
Benchmark bổ sung
everythinglà bài kiểm thử phi thực tế, khớp mọi dòng bằng.*trong kho mã Linuxrgbáo cáo 22,065,361 dòng trong 1.081 giâyagvàptkhông báo cáo tất cả các dòng, có vẻ như có giới hạn số lượng match
nothinglà bài kiểm thử áp dụng invert match cho.*để không báo cáo dòng nàorgđạt 0.302 giây,git grepđạt 0.905 giâyptvàucgkhông hỗ trợ tìm kiếm đảo ngược
contextin ra 2 dòng ngữ cảnh xung quanhSherlock Holmestrong corpus phụ đề tiếng Anhrgđạt 0.612 giây,siftđạt 0.717 giây, khá tương đươngucgkhông hỗ trợ tính năng này
hugetìm kiếmSherlock Holmestrong toàn bộ 9.3GB phụ đề tiếng Anhrgđạt 1.786 giây, GNU grep đạt 5.119 giây,siftđạt 3.047 giâyucgchỉ báo cáo 1.543 dòng trong điều kiện đếm dòng nên cho ra kết quả sai, và bị nghi có vấn đề khi tìm trong tệp lớn hơn 2GB
Kết luận
ripgrepkhông phải lúc nào cũng thắng mọi benchmark khi tìm kiếm trong kho mã Linux kernel, nhưng cũng khó nói công cụ nào khác vượt trội rõ ràng hơn về cả hiệu năng lẫn độ chính xácgit grepcó thể nhanh hơn vài mili giây trong một số trường hợp đơn giản, nhưng khi mẫu phức tạp hơn hoặc cần Unicode thì có những lúcripgrepvượt lên đáng kể- Các yếu tố sau góp phần vào hiệu năng tìm kiếm mã của
ripgrep- Duyệt thư mục nhanh với mục tiêu giảm tối thiểu số lần gọi
stat - Khớp glob
.gitignorebằngRegexSet - Phân phối công việc qua Chase-Lev work stealing queue
- Lựa chọn không dùng memory map khi tìm trong nhiều tệp nhỏ
- Regex engine nhanh
- Duyệt thư mục nhanh với mục tiêu giảm tối thiểu số lần gọi
- Khi tìm kiếm trên một tệp đơn,
ripgreplà nhanh nhất trong tất cả các benchmark chính hoặc dẫn trước với khoảng cách lớn - Hiệu năng trên tệp đơn chịu ảnh hưởng bởi
memchrdựa trên sparse byte, Teddy SIMD, Aho-Corasick và DFA tích hợp giải mã UTF-8 - Trong các benchmark yêu cầu tính năng Unicode, chỉ
rg, GNU grep vàgit grepcho thấy mức hỗ trợ có ý nghĩa, nhưng GNU grep vàgit grepthường phải trả giá hiệu năng lớn - Memory map bất lợi khi tìm song song qua nhiều tệp nhỏ trên Linux x86_64, có lợi khi tìm trong một tệp lớn, và có thể chịu thêm penalty trong môi trường VM
1 bình luận
Các ý kiến trên Hacker News
Rõ ràng là nhanh, và tôi cứ tiếp tục muốn khuyên dùng kết hợp với fzf
Tôi đang dùng một hàm PowerShell: trước hết tìm bằng
ripgrep, rồi phủ tìm kiếm mờ lên trên các file+kết quả văn bản, và dùngbatđể hiển thị ngữ cảnhTrong các dự án trộn nhiều repository, khi “biết là nó nằm đâu đó nhưng không biết chính xác vị trí hay tên”, có thể thu hẹp phạm vi rất nhanh
Cách này lấy từ https://github.com/junegunn/fzf/blob/master/ADVANCED.md, và dù không dùng hết thì cũng đáng lướt qua để lấy ý tưởng
fzfCó thể tìm kiếm mờ không chỉ trong file văn bản mà cả nhiều định dạng file như PDF, zip
Chi tiết có tại https://github.com/phiresky/ripgrep-all/wiki/fzf-Integration
Cách làm là chọn kết quả
rgbằngfzf, parse file và số dòng đã chọn, rồi mở bằng$EDITOR +"${linenumber}" "$file"Giống như xay cà phê bằng tay thay vì dùng máy xay điện
fzfcó thể chọn nhiều file để thêm vào Git đồng thời bỏ qua một số fileNếu thêm
fza = "!git ls-files -m -o --exclude-standard | fzf -m --print0 | xargs -0 git add"vào[alias]tronggitconfig, chạygit fzasẽ hiện danh sách các file đã sửa hoặc chưa được thêm, rồi dùng phím cách để bật/tắt từng mục và chuyển sang mục tiếp theoAlias này cùng với fzf+fd giúp tăng tốc khá nhiều cho một phần quy trình làm việc
Cũng có một hướng dẫn tổng hợp những thứ nên đưa vào cấu hình zsh trên macOS: https://gist.github.com/aclarknexient/0ffcb98aa262c585c49d4b...
ripgrepgần như theo cùng cáchDùng nó làm điểm xuất phát để thu hẹp file hoặc dự án trong một codebase có hàng trăm repository, rồi sau đó đào sâu tiếp
Trong Emacs, tôi dùng
ripgrepvới các package project.el và dumb-jumpCó thể không phải cách phổ biến nhất, nhưng trải nghiệm tổng thể khá hài lòng
Chỉ cần cài
dumb-jumpbằngpackage-installvà cấu hình(add-hook 'xref-backend-functions #'dumb-jump-xref-activate)Trong dự án Python, khi tìm định nghĩa của identifier bằng
M-.hoặcC-u M-.,dumb-jumpsẽ chạy lệnhrgphù hợp với dự án hiện tại và loại file, rồi hiển thị kết quả trong buffer XrefNó cũng hỗ trợ
ag, và nếu không cóaghayrgthì fallback vềgrep, nhưng khi tìm toàn bộ thư mục home thì có thể chậm như dự đoánripgrepkhá dễKhông nhất thiết cần package bên ngoài; nếu muốn dùng thay cho
grepchậm trong thư mục lớn, hãy đặt(setq xref-search-program 'ripgrep)Khi đó tìm kiếm trong project như
C-x p g foo RETsẽ chạy trong project hiện tại dưới dạngrg -i --null -nH --no-heading --no-messages -g '!*/' -e fooKết quả hiện trong buffer Xref, nên rất tiện dùng các phím như
n,p,RET,C-ođể chuyển tới match tiếp theo/trước đó, nhảy tới source, hoặc hiển thị trong cửa sổ chia đôiripgrep, theo tôi thấy, dù chưa chạy thử trực tiếp regex đó, có vẻ có thể bỏ flag --pcre2Có lẽ cũng có thể bỏ assertion
\bthứ hai và thứ ba; cái đầu tiên có thể cần thiếtripgrepvà cũng có bindingevil-collection, nên dùng khá hài lòng: https://github.com/Wilfred/deadgrepĐó là những lúc trước đây vốn sẽ dùng
rgrepĐiều thú vị là tìm kiếm của VS Code giờ cũng hoạt động bằng
ripgrepthông qua wrapper Node.jshttps://www.npmjs.com/package/@vscode/ripgrep
ripgrep, thì cái này rất hữu íchCó thể tìm thấy binary
rgtrong thư mục cài đặt VS. Ít nhất trong môi trường làm việc Windows của tôi là đượcTôi đã dùng
ripgrepkhoảng 2 năm và giờ nó trở thành công cụ không thể thiếuLý do chính tôi chuyển từ
grepsang là tính dễ dùngMặc định nó tôn trọng quy tắc
.gitignorevà bỏ qua file/thư mục ẩn cũng như file nhị phân, nênrg search_term directorytốt hơn nhiều so với lệnhgreptương ứng; tốc độ nhanh hơn chỉ là phần thưởng thêmKhi match quá dài khiến terminal trở nên hỗn loạn, tôi thường dùng tùy chọn -M như
-M 1000-Mthật sự tuyệt vờiNó đặc biệt tiện khi bỏ qua kết quả từ các file minified không muốn xem, và việc dùng tùy chọn -g như
-g *.csđể chỉ tìm trong file có phần mở rộng nhất định cũng rất tốtViệc nó là một binary độc lập, portable cũng hữu ích: khi làm việc trên máy mới, chỉ cần đặt file thực thi vào và alias
grepthànhrg, thì dù gõgreptheo thói quen,rgvẫn được chạyĐiều này có thể vẫn đúng vào năm 2023, nhưng vấn đề là các công cụ thay thế
grepđã được song song hóa, chẳng hạn nhưripgrephayag, nhanh hơngreptruyền thống đến mức những chênh lệch tốc độ nhỏ giữa chúng khó trở thành tiêu chí phân biệtTôi dùng
agtrong Emacs trên một codebase 900 nghìn dòng, và trên Ryzen Threadripper 2950X 16 nhân thì về cơ bản là xong ngay lập tứcTôi không thấy cần phải giảm mức dưới 1 giây xuống thành “dưới 1 giây thêm một chút”
Thuộc tính cốt lõi của các công cụ kiểu
grepmới không phải là tốc độ, mà cần được đánh giá và so sánh theo cách khácagcó một vách hiệu năng khá lớn, và có thể thấy cả trong bài blogTuy nhiên tải công việc của mỗi người khác nhau, nên trong một số trường hợp khác biệt hiệu năng có thể không quan trọng
900 nghìn dòng không phải là quá lớn, và với truy vấn đơn giản thì hầu hết công cụ kiểu
grepkhông ngây thơ đều xử lý rất nhanhNếu xét theo các tiêu chí so sánh khác,
aggần như đang trong tình trạng duy trì sự sống, và có vẻ từng suýt bị loại khỏi Debian rồi được ai đó cứu lại: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=999962Bài blog cũng so sánh hỗ trợ Unicode, và
agvề cơ bản không có hỗ trợ Unicode. Điều này không quan trọng với tất cả mọi người, nhưng là một tiêu chí so sánh phi hiệu năng đủ đáng kểThời gian tìm kiếm mất đúng bằng thời gian tải tệp từ đĩa, và khác biệt sau đó khó có ý nghĩa
Nếu tệp đã nằm trong cache, thì thời gian di chuyển trong hệ thống tệp và gõ lệnh lại chi phối nhiều hơn thời gian tìm kiếm, nên khác biệt hiệu năng cũng khó có ý nghĩa
Tiêu đề cần có (2016)
Đây là bài công bố ban đầu, không phải thông tin mới
“Ripgrep – A new command line search tool” https://news.ycombinator.com/item?id=12564442 (740 điểm | 23/09/2016 | 209 bình luận) — cũng có thảo luận liên quan đến tốc độ
“Ripgrep is faster (2016)” https://news.ycombinator.com/item?id=17941319 (98 điểm | 08/09/2018 | 40 bình luận)
Không nhanh hơn
qgrepCách hoạt động của hai công cụ này rất khác nhau, và
qgrepdựa trênre2, nhưng tốc độ có được là nhờ có chỉ mụcVới các kho tệp lớn, dùng
qgrepcùng chỉ mục hợp lý hơn việc quét toàn bộ tệp mỗi lần, nên tôi tò mò vì sao mọi người lại quên mất lựa chọnqgrepTuy nhiên nếu cần khớp nhiều dòng trong UTF-8, tôi nghĩ
ripgrepphải quay sang thư viện PCRE2 khác nên không nhanh như vậyripgrep, đúng là vìqgrepdùng lập chỉ mục nên có lợi thế so với các công cụ không lập chỉ mụcĐổi lại, bạn phải thiết lập và duy trì chỉ mục, nên UX không đơn giản như “cứ chạy tìm kiếm”
Lý do mọi người không dùng
qgrepcũng giống như lý do không dùngripgrepvì “với tôi grep cũng đủ nhanh rồi”Với phạm vi tìm kiếm nhỏ, nhiều khi bạn không cảm nhận được khác biệt tốc độ giữa
ripgrepvàgrep, hoặc giữaqgrepvàripgrepNếu
ripgreptìm kiếm kernel Linux xong trong dưới 100ms, thì trong cách dùng tương tác tiêu chuẩn, việc đổi sang công cụ lập chỉ mục có đủ đáng phiền hay không còn tùy hoàn cảnh, nhưng thường là khôngTôi từng nghĩ đến ý tưởng thêm lập chỉ mục vào
ripgrep: https://github.com/BurntSushi/ripgrep/issues/1497Và tìm kiếm nhiều dòng không yêu cầu PCRE2. Engine regex mặc định cũng có hỗ trợ Unicode, và ngay cả khi build không có PCRE2 thì hỗ trợ tìm kiếm nhiều dòng vẫn được giữ
Sau khi chuyển từ
ripgrepsang ugrep thì tôi không ngoái lại nữaTốc độ cũng tương tự, nhưng có fuzzy matching, có TUI dùng được cho code review, và có thể tìm kiếm cả bên trong PDF hay tệp nén
Việc có thể tùy chọn dùng cú pháp tìm kiếm Google cũng tiện
https://ugrep.com
ripgrep, nhưng gần đây tôi tìm đếnugrepvì một tính năng màripgrepkhông có: tìm kiếm bên trong file nén zipCó thể tìm kiếm mà không cần giải nén ra đĩa
Tôi xử lý các corpus nén gồm hàng triệu tệp văn bản nhỏ, nên việc không cần bung toàn bộ ra hệ thống tệp là rất tốt. Một số hệ thống tệp chật vật ở quy mô này
Tôi biết ơn cả hai công cụ, và cảm ơn tác giả của từng công cụ
grep, phần lớn kết quả sẽ cố bán thứ gì đó cho mìnhugrepvàripgrepđã tranh cãi trên Reddit trong vài nămVí dụ https://www.reddit.com/r/programming/comments/120wqvr/ripgre...
Chỉ là chuyện về công cụ mã nguồn mở thôi mà tôi thấy hơi kỳ lạ
fzfkhôngVới tôi, có vẻ khó vượt qua khả năng cấu hình và độ linh hoạt của
fzfCó vẻ tính năng sát thủ là khả năng tương thích với các tùy chọn dòng lệnh grep hiện có
Việc không phải học một tập tùy chọn hoàn toàn mới là khá hay
Tôi thắc mắc vì sao
grepkhông bị thay thế hoặc cải tiếnChủ đề này giờ có vẻ cũng đã hơi cũ
Quán tính, tính tương thích, sự kháng cự trước thay đổi, kiểu tiến thoái lưỡng nan của nhà đổi mới. Tôi không nói theo nghĩa tiêu cực, và tất cả cũng đều áp dụng với tôi
Về tính tương thích thì có thể xem FAQ: https://github.com/BurntSushi/ripgrep/blob/master/FAQ.md#pos...
Nó thoải mái, hợp với môi trường làm việc xung quanh, và không có lý do gì phải đổi rồi căn chỉnh lại mọi thứ
Phép so sánh chỉ tiếp tục đến mức là gần đó đã có một chiếc ghế kiểu Razer và nó đang được dùng để treo quần áo
xyz, nhận tham số này và hoạt động chính xác như thế này”ripgrepNếu ý là thay chính lệnh
grepbằng một tiện ích khác, thì có vẻ sẽ làm hỏng quá nhiều thứ so với giá trị thu đượcNgười muốn
grepnhanh hơn thì dùng công cụ khác, còn người dùnggrephiện có thì cứ tiếp tục dùng, nên tình trạng hiện tại đã gần với lý tưởnggreplà công cụ đa dụng để tìm văn bản trong mọi loại tệp, và đã được đóng vào chuẩn UNIXMột số lập trình viên dùng nó để tìm trong mã nguồn, nhưng những người khác dùng để tìm văn bản không liên quan đến mã nguồn hoặc trong script, và kỳ vọng nó tuyệt đối không crash
Ngược lại,
ripgreplà công cụ chuyên biệt, có chính kiến rõ, chủ yếu được thiết kế để tìm kiếm trong kho mã nguồnKhông còn nhiều dư địa để làm tìm kiếm văn bản đa dụng nhanh hơn. Dùng
mmap()thì có nguy cơ crash với tệp bị cắt ngắn; giảm sức biểu đạt của biểu thức chính quy thì có thể nhanh hơn; cũng có thể bỏ hỗ trợ mọi locale và bộ ký tự rồi hard-code chỉ UTF-8/UTF-16, nhưng không nên làm vậyTìm trong Portage thì có vẻ cũng có phiên bản xử lý cả các tài liệu khác như PDF và doc
https://github.com/phiresky/ripgrep-all