5 điểm bởi GN⁺ 2023-12-01 | 1 bình luận | Chia sẻ qua WhatsApp
  • 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 ripgrep cả 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 ripgrep chia 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

  • ripgrep là 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
  • Tác giả là người tạo ra ripgrep và 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 ripgreprg
  • 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, -uuu
    • rg -uuu tương tự grep -a -r
  • Hỗ trợ bộ lọc loại tệp
    • rg -tpy foo: chỉ tìm trong tệp Python
    • rg -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ì ripgrep có 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ọ grep cầ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ọ ack phải xử lý nhanh việc duyệt thư mục đệ quy và áp dụng quy tắc ignore như .gitignore
  • ripgrep cố 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 stat không cần thiết
  • ripgrep dù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ý .gitignore có 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
  • ripgrep cố 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ất
  • ucg có 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 automata
    • ag, ucg: backtracking dựa trên PCRE
    • pt, sift: thư viện Go regex, dựa trên finite automata
  • Do dùng PCRE, agucg có thể gặp hành vi backtracking tệ nhất
  • Mẫu ví dụ (a*)* c có 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 memchr thườ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
  • ripgrep tậ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ìm foo trước rồi chỉ xác minh bằng regex trên các dòng ứng viên
  • 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 ripgrep vượ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
  • ripgrep chấ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
  • ripgrep ghi trực tiếp ra stdout mà không dùng bộ đệm trung gian khi tìm kiếm trên stdin hoặ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
    • ripgrep v0.1.2
    • GNU grep v2.25
    • git grep v2.7.4
    • ag commit cda635, PCRE 8.38
    • ucg commit 487bfb, PCRE 10.21 JIT
    • pt commit 509368
    • sift commit 2d175c
  • ack bị 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 kho ripgrep
  • 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ản PM_RESUME cho thấy sự khác biệt về hành vi mặc định của từng công cụ
    • rg tôn trọng .gitignore và bỏ qua tệp ẩn, tệp nhị phân
    • agpt cũng tương tự nhưng có đếm số dòng
    • ucg không đọc .gitignore và tìm kiếm theo whitelist
    • sift mặc định tìm gần như mọi thứ
    • git grep có lợi thế là lấy tập tệp cần tìm từ git index
  • Việc tôn trọng .gitignore giú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òn rg (ignore) ở mức tương đương git grep
  • rg (ignore) (mmap)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, pt chậm đi đáng kể vì xử lý -i bằng (?i) của Go regexp
  • sift chậ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 Unicode
  • ripgrep chuyể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 \wAh trong linux_unicode_word dùng để kiểm tra xem \w nhận biết Unicode có bắt được các kết quả như µAh hay không
  • Chỉ rggit grep có thể bật/tắt Unicode; ag, pt, sift, ucg đều dùng \w chỉ hỗ trợ ASCII
  • git grep chịu chi phí hiệu năng lớn khi bật hỗ trợ Unicode, còn ripgrep gần như không suy giảm hiệu năng
  • ripgrep tí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, rgucg dù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, ripgrep dù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, ripgrep vẫ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à rg nhanh hơn pt, sift rất nhiều
  • Trong tìm kiếm \p{Greek} không phân biệt hoa thường, sift không báo được kết quả khớp, còn pt khô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ếp
    • rg vẫn thuộc nhóm nhanh ngay cả khi bật hỗ trợ Unicode
    • git grep chị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ếm Sherlock Holmes lẫn Шерлок Холмс đều cho thấy rg là nhanh nhất
  • ripgrep cố chọn byte hiếm trong literal để dùng với memchr
    • 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
    • rg chọ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 \xD0 hoặc \xD1, nên tìm theo byte đầu tiên có thể kém hiệu quả
  • rg dù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 rg nhanh hơn rg (no mmap) khoảng 25%

Unicode và alternation trong một tệp đơn

  • Trong subtitles_literal_casei, rg vừ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òn ag bá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ấy rg nhanh nhất ở cả tiếng Anh lẫn tiếng Nga
  • Với alternation tiếng Anh, rg nhanh hơn GNU grep khoảng một bậc độ lớn
  • Trong subtitles_alternate_casei, rg chậ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 rg chuyển từ Teddy sang Aho-Corasick
  • ripgrep dù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 literal Holmes
  • ripgrep và GNU grep đều thực hiện tối ưu hóa inner literal
  • ripgrep sử dụng regex-syntax củ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, rg thuộ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
  • ripgrep duy 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

  • everything là bài kiểm thử phi thực tế, khớp mọi dòng bằng .* trong kho mã Linux
    • rg báo cáo 22,065,361 dòng trong 1.081 giây
    • agpt không báo cáo tất cả các dòng, có vẻ như có giới hạn số lượng match
  • nothing là bài kiểm thử áp dụng invert match cho .* để không báo cáo dòng nào
    • rg đạt 0.302 giây, git grep đạt 0.905 giây
    • ptucg không hỗ trợ tìm kiếm đảo ngược
  • context in ra 2 dòng ngữ cảnh xung quanh Sherlock Holmes trong corpus phụ đề tiếng Anh
    • rg đạt 0.612 giây, sift đạt 0.717 giây, khá tương đương
    • ucg không hỗ trợ tính năng này
  • huge tìm kiếm Sherlock Holmes trong toàn bộ 9.3GB phụ đề tiếng Anh
    • rg đạt 1.786 giây, GNU grep đạt 5.119 giây, sift đạt 3.047 giây
    • ucg chỉ 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

  • ripgrep khô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ác
  • git grep có 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úc ripgrep vượ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 .gitignore bằng RegexSet
    • 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
  • Khi tìm kiếm trên một tệp đơn, ripgrep là 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 memchr dự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 grep cho thấy mức hỗ trợ có ý nghĩa, nhưng GNU grep và git grep thườ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

 
GN⁺ 2023-12-01
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ùng bat để hiển thị ngữ cảnh
    Trong 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

    • Khuyên nên tiến thêm một bước nữa là tích hợp ripgrep-all(rga) với fzf
      Có 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
    • Tôi cũng đã viết cách này dưới dạng phiên bản bash
      Cách làm là chọn kết quả rg bằng fzf, parse file và số dòng đã chọn, rồi mở bằng $EDITOR +"${linenumber}" "$file"
    • Trong Vim mà không có fzf+rg thì cảm giác gần như hỏng hẳn
      Giống như xay cà phê bằng tay thay vì dùng máy xay điện
    • Dùng fzf có thể chọn nhiều file để thêm vào Git đồng thời bỏ qua một số file
      Nếu thêm fza = "!git ls-files -m -o --exclude-standard | fzf -m --print0 | xargs -0 git add" vào [alias] trong gitconfig, chạy git fza sẽ 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 theo
      Alias 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...
    • Tôi cũng dùng ripgrep gần như theo cùng cách
      Dù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 ripgrep với các package project.eldumb-jump
    Có 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-jump bằng package-install và 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ặc C-u M-., dumb-jump sẽ chạy lệnh rg phù hợp với dự án hiện tại và loại file, rồi hiển thị kết quả trong buffer Xref
    Nó cũng hỗ trợ ag, và nếu không có ag hay rg thì fallback về grep, nhưng khi tìm toàn bộ thư mục home thì có thể chậm như dự đoán

    • Chỉ với project.el tích hợp sẵn trong Emacs cũng có thể dùng ripgrep khá dễ
      Không nhất thiết cần package bên ngoài; nếu muốn dùng thay cho grep chậ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 RET sẽ chạy trong project hiện tại dưới dạng rg -i --null -nH --no-heading --no-messages -g '!*/' -e foo
      Kế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 đôi
    • Với tư cách tác giả của ripgrep, theo tôi thấy, dù chưa chạy thử trực tiếp regex đó, có vẻ có thể bỏ flag --pcre2
      Có lẽ cũng có thể bỏ assertion \b thứ hai và thứ ba; cái đầu tiên có thể cần thiết
    • Deadgrep dùng ripgrep và cũng có binding evil-collection, nên dùng khá hài lòng: https://github.com/Wilfred/deadgrep
    • Cách này cũng tốt, nhưng khi muốn tìm trong nhiều project cùng lúc hoặc chỉ trong một thư mục con của project, tôi vẫn dùng rg.el
      Đó 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 ripgrep thông qua wrapper Node.js
    https://www.npmjs.com/package/@vscode/ripgrep

    • Nếu ở trong môi trường có thể yêu cầu hoặc cài VS Code nhưng không thể cài ripgrep, thì cái này rất hữu ích
      Có thể tìm thấy binary rg trong 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à được
    • Tôi vẫn luôn thắc mắc vì sao VS Code là ứng dụng Electron mà tìm kiếm lại nhanh đến vậy; giờ thì biết lý do rồi
    • Đây không phải tính năng mới; nó đã có trong VS Code từ 7 năm trước
  • Tôi đã dùng ripgrep khoảng 2 năm và giờ nó trở thành công cụ không thể thiếu
    Lý do chính tôi chuyển từ grep sang là tính dễ dùng
    Mặc định nó tôn trọng quy tắc .gitignore và bỏ qua file/thư mục ẩn cũng như file nhị phân, nên rg search_term directory tốt hơn nhiều so với lệnh grep tương ứng; tốc độ nhanh hơn chỉ là phần thưởng thêm
    Khi 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

    • -M thật sự tuyệt vời
      Nó đặ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ốt
      Việ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 grep thành rg, thì dù gõ grep theo thói quen, rg vẫ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ư ripgrep hay ag, nhanh hơn grep truyề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ệt
    Tôi dùng ag trong 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ức
    Tô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 grep mới không phải là tốc độ, mà cần được đánh giá và so sánh theo cách khác

    • Tôi nghĩ vào năm 2016, tốc độ chắc chắn là thuộc tính cốt lõi
      ag có một vách hiệu năng khá lớn, và có thể thấy cả trong bài blog
      Tuy 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 grep không ngây thơ đều xử lý rất nhanh
      Nếu xét theo các tiêu chí so sánh khác, ag gầ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=999962
      Bài blog cũng so sánh hỗ trợ Unicode, và ag về 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ể
    • Theo kinh nghiệm của tôi, tất cả các công cụ này đều bị ràng buộc nặng bởi nghẽn cổ chai I/O
      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

  • Không nhanh hơn qgrep
    Cách hoạt động của hai công cụ này rất khác nhau, và qgrep dựa trên re2, nhưng tốc độ có được là nhờ có chỉ mục
    Với các kho tệp lớn, dùng qgrep cù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ọn qgrep
    Tuy nhiên nếu cần khớp nhiều dòng trong UTF-8, tôi nghĩ ripgrep phải quay sang thư viện PCRE2 khác nên không nhanh như vậy

    • Với tư cách là tác giả ripgrep, đúng là vì qgrep dù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 qgrep cũng giống như lý do không dùng ripgrep vì “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 ripgrepgrep, hoặc giữa qgrepripgrep
      Nếu ripgrep tì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ông
      Tôi từng nghĩ đến ý tưởng thêm lập chỉ mục vào ripgrep: https://github.com/BurntSushi/ripgrep/issues/1497
      Và 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ừ ripgrep sang ugrep thì tôi không ngoái lại nữa
    Tố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

    • Tôi là fan nhiệt thành của ripgrep, nhưng gần đây tôi tìm đến ugrep vì một tính năng mà ripgrep không có: tìm kiếm bên trong file nén zip
      Có 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ụ
    • Tôi sợ rằng nếu bắt đầu dùng cú pháp tìm kiếm Google trong grep, phần lớn kết quả sẽ cố bán thứ gì đó cho mình
    • Khi tìm nhanh bài viết “ugrep vs ripgrep”, tôi thấy các bài có vẻ như tác giả của ugrepripgrep đã tranh cãi trên Reddit trong vài năm
      Ví 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ạ
    • Tôi tò mò liệu TUI có tốt hơn việc chuyển kết quả sang fzf không
      Với tôi, có vẻ khó vượt qua khả năng cấu hình và độ linh hoạt của fzf
    • Cảm ơn vì đã cho biết
      Có 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 grep không bị thay thế hoặc cải tiến
    Chủ đề này giờ có vẻ cũng đã hơi cũ

    • Có nhiều lý do có thể giải thích
      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ó giống lý do tôi không thay chiếc ghế 40 năm tuổi mình đang ngồi bằng Razer UltraSeat XR3000-A
      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
    • Một người nào đó thiết kế Unix đã biến một số chức năng hệ thống vừa thành chức năng OS cốt lõi, vừa thành công cụ cho con người dùng, và kết quả là vài chục năm sau xuất hiện tình huống kỳ lạ kiểu “bắt buộc phải có chương trình tên xyz, nhận tham số này và hoạt động chính xác như thế này”
    • Hiện đã có thể dùng nhiều công cụ thay thế như ripgrep
      Nếu ý là thay chính lệnh grep bằ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 được
      Người muốn grep nhanh hơn thì dùng công cụ khác, còn người dùng grep hiệ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ưởng
    • grepcô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 UNIX
      Mộ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, ripgrep là 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ồn
      Khô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ậy
  • Tì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