5 คะแนน โดย GN⁺ 2023-12-01 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ripgrep (rg) เป็นเครื่องมือค้นหาบนบรรทัดคำสั่งที่ใช้ Rust ซึ่งผสานความสะดวกในการค้นหาโค้ดแบบ The Silver Searcher เข้ากับประสิทธิภาพดิบระดับ GNU grep และมีไบนารีให้ใช้บน Linux, Mac และ Windows
  • จากเบนช์มาร์ก 25 รายการ ไม่พบเครื่องมือใดที่เหนือกว่า ripgrep อย่างชัดเจนทั้งด้านประสิทธิภาพและความถูกต้อง ทั้งในการค้นหาไฟล์ขนาดใหญ่ไฟล์เดียวและการค้นหาไดเรกทอรีขนาดใหญ่ อีกทั้งต้นทุนของการรองรับ Unicode ก็ยังคงต่ำ
  • ขยายขอบเขตการใช้งานจริงของเครื่องมือค้นหาโค้ด ด้วยการจัดการ .gitignore, การยกเว้นไฟล์ซ่อนและไฟล์ไบนารีโดยค่าเริ่มต้น, ตัวกรองชนิดไฟล์, การรองรับ PCRE2 แบบเลือกใช้ได้, การค้นหาในหลาย encoding และไฟล์บีบอัด รวมถึงตัวกรองก่อนประมวลผล
  • ความแตกต่างในการทดลองกับ repository ของ Linux kernel และ OpenSubtitles2016 ขึ้นอยู่กับ literal optimization, การค้นหาหลายแพตเทิร์นด้วย Teddy SIMD, Aho-Corasick, วิธีถอดรหัส UTF-8, การนับบรรทัด และต้นทุนการจัดการ .gitignore เป็นอย่างมาก
  • เมื่อค้นหาไฟล์ขนาดเล็กจำนวนมากแบบขนาน memory map อาจช้าลงได้ ส่วนกับไฟล์ขนาดใหญ่ไฟล์เดียวอาจได้เปรียบ ดังนั้น ripgrep จึงแยกใช้การค้นหาผ่านบัฟเฟอร์กลางกับการค้นหาผ่าน memory map ตามสถานการณ์

ตำแหน่งที่ ripgrep ตั้งเป้าไว้

  • ripgrep เป็นเครื่องมือค้นหาบนบรรทัดคำสั่งที่มุ่งรวมความสะดวกของเครื่องมือค้นหาโค้ดเข้ากับประสิทธิภาพของเครื่องมือตระกูล grep
  • เครื่องมือที่นำมาเปรียบเทียบคือ GNU grep, git grep, The Silver Searcher (ag), Universal Code Grep (ucg), The Platinum Searcher (pt) และ sift
  • ประเด็นหลักที่เบนช์มาร์กต้องการตรวจสอบมีสามข้อ
    • ไม่มีเครื่องมือใดที่เหนือกว่า ripgrep อย่างชัดเจนทั้งในการค้นหาไฟล์เดี่ยวและไดเรกทอรีขนาดใหญ่
    • ให้การรองรับ Unicode อย่างเหมาะสมโดยไม่ต้องเสียประสิทธิภาพมาก
    • เมื่อค้นหาหลายไฟล์พร้อมกัน memory map อาจช้ากว่ามากกว่าจะเร็วกว่าโดยทั่วไป
  • ผู้เขียนเป็นผู้สร้าง ripgrep และเอนจิน regular expression ที่เป็นพื้นฐาน และระบุไว้ว่าเบนช์มาร์กอาจถูกคัดเลือกจนเกิดอคติได้

ฟีเจอร์และพฤติกรรมเริ่มต้น

  • ชื่อไฟล์ executable ของ ripgrep คือ rg
  • การค้นหาโดยค่าเริ่มต้นจะสำรวจไดเรกทอรีปัจจุบันแบบ recursive, เคารพ .gitignore และข้ามไฟล์ซ่อนกับไฟล์ไบนารี
  • รองรับ .rgignore ด้วย และแพตเทิร์นใน .rgignore จะมีลำดับความสำคัญเหนือ .gitignore
  • สามารถใช้ -u, -uu, -uuu เพื่อเพิ่มขอบเขตเป็นการไม่สนใจไฟล์ ignore, รวมไฟล์ซ่อน และรวมไฟล์ไบนารี
    • rg -uuu คล้ายกับ grep -a -r
  • รองรับตัวกรองชนิดไฟล์
    • rg -tpy foo: ค้นหาเฉพาะไฟล์ Python
    • rg -Tjs foo: ยกเว้นไฟล์ JavaScript
    • สามารถเพิ่มกฎชนิดไฟล์ใหม่ได้ด้วย --type-add
  • ยังมีฟีเจอร์หลายอย่างของ grep
    • แสดงบริบทรอบผลลัพธ์
    • ค้นหาหลายแพตเทิร์น
    • ไฮไลต์สี
    • รองรับ Unicode เต็มรูปแบบ
  • เอนจิน regular expression ค่าเริ่มต้นไม่รองรับ look-around และ backreference แต่หากเลือกเอนจิน PCRE2 ด้วย -P ก็สามารถใช้ฟีเจอร์เหล่านี้ได้
  • รองรับการตรวจจับ UTF-16 บางส่วนอัตโนมัติ และการระบุ encoding ผ่าน -E/--encoding
    • รวมถึง UTF-16, latin-1, GBK, EUC-JP, Shift_JIS เป็นต้น
  • รองรับการค้นหาไฟล์บีบอัด เช่น gzip, xz, lzma, bzip2, lz4 ด้วย -z/--search-zip
  • ยังรองรับตัวกรองก่อนประมวลผลแบบกำหนดเอง เช่น การดึงข้อความจาก PDF, การแตกไฟล์บีบอัดเพิ่มเติม, การถอดรหัส และการตรวจจับ encoding อัตโนมัติ

เหตุผลที่อาจไม่ใช้

  • หากให้ความสำคัญสูงสุดกับ portability และการใช้งานได้ทุกที่ grep ที่เป็นไปตามมาตรฐานและติดตั้งแพร่หลายจะเหมาะกว่า
  • หากพึ่งพาฟีเจอร์เฉพาะหรือบั๊กของเครื่องมืออื่น ripgrep อาจไม่เหมาะ
  • ใน edge case ด้านประสิทธิภาพบางกรณี เครื่องมืออื่นอาจทำงานได้ดีกว่า
  • หากติดตั้งไม่ได้หรือไม่มีการรองรับแพลตฟอร์ม ก็ไม่สามารถใช้งานได้เช่นกัน

โครงสร้างการทำงานของเครื่องมือตระกูล grep

  • เครื่องมือค้นหาโดยกว้าง ๆ มีสามขั้นตอน
    • รวบรวมไฟล์ที่จะค้นหา
    • ค้นหาจริง
    • แสดงผลลัพธ์
  • เครื่องมือตระกูล grep ต้องค้นหาไฟล์ขนาดใหญ่ได้ดี ดังนั้น ประสิทธิภาพของเอนจิน regular expression จึงสำคัญ
  • เครื่องมือตระกูล ack ต้องจัดการการสำรวจไดเรกทอรีแบบ recursive และการใช้กฎ ignore เช่น .gitignore ได้อย่างรวดเร็ว
  • ripgrep พยายามผสานสองแนวทางเข้าด้วยกัน
    • เอนจิน regular expression ที่เร็ว
    • การค้นหาแบบขนาน
    • การกรองเป้าหมายการค้นหา

การรวบรวมไฟล์และการจัดการ ignore

  • ในเครื่องมือตระกูล ack สิ่งสำคัญคือการตัดสินใจอย่างรวดเร็วว่าจะค้นหาไฟล์ใดในไดเรกทอรีปัจจุบัน
  • ประสิทธิภาพของการเดินไดเรกทอรีได้รับผลจากจำนวนการเรียก stat ที่ไม่จำเป็น
  • ripgrep ใช้ iterator เดินไดเรกทอรีแบบ recursive ที่ตั้งเป้าให้เรียก system call น้อยที่สุด
  • การจัดการ .gitignore มีต้นทุน
    • ต้องหาไฟล์ ignore ในแต่ละไดเรกทอรี
    • ต้องคอมไพล์แพตเทิร์น ignore
    • ต้องนำแพตเทิร์นไปใช้กับพาธผู้สมัครทั้งหมด
  • repository ของ Linux kernel มีไดเรกทอรี 4,640 รายการ และไฟล์ .gitignore 178 ไฟล์
  • ripgrep พยายามรองรับความหมายของ .gitignore ให้สมบูรณ์ยิ่งขึ้น และให้ความสำคัญกับแพตเทิร์นที่แมตช์ซึ่งถูกนิยามล่าสุด
  • ucg ใช้กฎ glob แบบ whitelist แทน .gitignore จึงอาจเร็วกว่า แต่ก็อาจพลาดไฟล์ที่มีนามสกุลที่ไม่รู้จักได้

ความแตกต่างของเอนจิน regular expression

  • เอนจิน regular expression โดยทั่วไปแบ่งเป็นสองประเภท
    • แบบอิง backtracking: ฟีเจอร์ครบกว่า แต่อาจช้าลงเป็นเวลาเชิงเลขชี้กำลังกับอินพุตบางแบบ
    • แบบอิง finite automata: ฟีเจอร์อาจจำกัดกว่า แต่รับประกันเวลาเชิงเส้นตามความยาวข้อความที่ค้นหา
  • เอนจินของแต่ละเครื่องมือมีดังนี้
    • GNU grep, git grep: เอนจินของตัวเองที่อิง finite automata
    • ripgrep: ไลบรารี Rust regex ที่อิง finite automata
    • ag, ucg: backtracking ที่อิง PCRE
    • pt, sift: ไลบรารี Go regex ที่อิง finite automata
  • ag และ ucg อาจเจอกับพฤติกรรม backtracking กรณีเลวร้ายที่สุดจากการใช้ PCRE
  • แพตเทิร์นตัวอย่าง (a*)* c อาจก่อปัญหาในเครื่องมือที่อิง PCRE แต่เครื่องมืออื่น ๆ ที่อยู่ในเบนช์มาร์กจัดการได้โดยไม่มีปัญหา

Literal optimization และ SIMD

  • ในการค้นหาสตริงแบบง่าย การปรับแต่งการค้นหา literal อาจสำคัญกว่าเอนจิน regular expression
  • Boyer-Moore เป็นอัลกอริทึมค้นหาสตริงย่อยแบบคลาสสิก และสามารถใช้ routine อย่าง memchr เพื่อค้นหาตำแหน่งผู้สมัครอย่างรวดเร็ว
  • การใช้งาน memchr มักตรวจสอบครั้งละ 16 ไบต์ด้วยคำสั่ง SIMD และทำ throughput ได้หลาย GB/s
  • ไลบรารี Rust regex ดึง literal แบบ prefix และ suffix ออกจากแพตเทิร์นอย่างจริงจัง
    • foo|bar
    • (a|b)c
    • [ab]foo[yz]
    • (foo)?bar
    • (foo)*bar
    • (foo){3,6}
  • หาก regular expression ทั้งหมดถูกแยกย่อยเป็น literal เดี่ยวหรือ literal alternation ก็อาจไม่ต้องใช้เอนจิน regular expression หลักเลย
  • ripgrep ใช้ประโยชน์จากลักษณะการแสดงผลลัพธ์แบบรายบรรทัดเพื่อดึง inner literal ด้วย
    • ตัวอย่าง: ใน \w+foo\d+ จะหา foo ก่อน แล้วค่อยตรวจสอบเฉพาะบรรทัดผู้สมัครด้วย regular expression
  • สำหรับการค้นหา literal หลายรายการ GNU grep ใช้อัลกอริทึมคล้าย Commentz-Walter ส่วน Rust regex ใช้ Aho-Corasick หรืออัลกอริทึม Teddy SIMD
  • Teddy เป็นอัลกอริทึมค้นหาหลายแพตเทิร์นแบบ SIMD ที่มาจาก Intel Hyperscan และเป็นหนึ่งในการปรับแต่งสำคัญที่ทำให้ ripgrep แซง GNU grep

วิธีค้นหา: หลีกเลี่ยงการค้นหารายบรรทัด

  • การใช้งานแบบตรงไปตรงมาคืออ่านไฟล์ทีละบรรทัดแล้วนำแพตเทิร์นไปใช้กับแต่ละบรรทัด แต่ในการค้นหาส่วนใหญ่การแมตช์เกิดขึ้นไม่บ่อย จึงไม่มีประสิทธิภาพ
  • เครื่องมือค้นหามักค้นหาบัฟเฟอร์ไบต์ขนาดใหญ่ในคราวเดียว
    • map ไฟล์เข้าหน่วยความจำ
    • อ่านทั้งไฟล์เข้า memory
    • ค้นหาแบบค่อยเป็นค่อยไปด้วยบัฟเฟอร์กลางขนาดคงที่
  • ripgrep, GNU grep และ git grep รองรับการค้นหาแบบค่อยเป็นค่อยไป จึงใช้ได้ทั้งกับไฟล์และ stream
  • การค้นหาแบบค่อยเป็นค่อยไปทำได้ยาก
    • คำนวณเลขบรรทัด
    • จัดการกรณีที่บัฟเฟอร์จบกลางบรรทัด
    • จัดการบรรทัดยาว
    • จัดการ invert match
    • จัดการการแสดงบริบทรอบ match
  • ripgrep ยอมรับความซับซ้อนในการ implementation และใช้การค้นหาแบบค่อยเป็นค่อยไป โดยในเบนช์มาร์กให้ผลลัพธ์เร็วกว่า memory map เมื่อค้นหาไฟล์ขนาดเล็กจำนวนมาก

เอาต์พุตและการทำงานแบบขนาน

  • ในการค้นหาแบบขนาน หากแต่ละเธรดส่งเอาต์พุตทันที ผลลัพธ์จากไฟล์ต่าง ๆ อาจปะปนกันได้
  • เครื่องมือค้นหาโค้ดแบบขนานทั้งหมดจะเขียนผลการค้นหาลงในบัฟเฟอร์กลางในหน่วยความจำ และทำให้เฉพาะขั้นตอนการส่งออกเป็นแบบลำดับ
  • วิธีนี้ทำให้เธรดค้นหาสามารถทำการค้นหาจริงแบบขนานได้
  • ข้อเสียคือในกรณีอย่างไฟล์ขนาด 2GB ที่ทุกบรรทัดแมตช์กัน อาจใช้หน่วยความจำมากขึ้น
  • ripgrep จะเขียนตรงไปยัง stdout โดยไม่ใช้บัฟเฟอร์กลางเมื่อค้นหาจาก stdin หรือไฟล์เดี่ยว

ระเบียบวิธีเบนช์มาร์ก

  • เบนช์มาร์กแบ่งตามปัญหาของผู้ใช้ปลายทาง
    • การค้นหาในที่เก็บโค้ดขนาดใหญ่
    • การค้นหาในไฟล์ขนาดใหญ่ไฟล์เดียว
  • รูปแบบการค้นหาเอนเอียงไปทางลิเทอรัลแบบง่าย, alternation และเรกิวลาร์เอ็กซ์เพรสชันแบบเบา
  • เนื่องจากเครื่องมือแต่ละตัวมีพฤติกรรมเริ่มต้นต่างกัน จึงพยายามปรับเงื่อนไขอย่างหมายเลขบรรทัด, Unicode, .gitignore และ whitelist ให้ตรงกันเพื่อให้เปรียบเทียบอย่างเป็นธรรม
  • เวอร์ชันที่ใช้ในเบนช์มาร์กมีดังนี้
    • 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 ถูกตัดออกเพราะในตอนนั้นช้ากว่าเครื่องมืออื่นมาก
  • ตัวรันเบนช์มาร์กคือ benchsuite ซึ่งต้องใช้ Python 3.5 ขึ้นไป และรวมอยู่ในที่เก็บ ripgrep
  • แต่ละคำสั่งรัน warm-up 3 ครั้งก่อนการวัด เพื่อให้คอร์ปัสถูกโหลดขึ้น OS page cache
  • แต่ละคำสั่งวัด 10 ครั้ง และบันทึกค่าเฉลี่ยกับส่วนเบี่ยงเบนมาตรฐาน
  • สภาพแวดล้อมที่รันคือ Amazon EC2 c3.2xlarge, Ubuntu 16.04, Xeon E5-2680 2.8GHz, หน่วยความจำ 16GB, SSD 80GB
  • มีการเผยแพร่ล็อกการตั้งค่า ผลลัพธ์สรุป และ raw CSV ด้วย

ผลการค้นหาโค้ดเคอร์เนล Linux

  • เบนช์มาร์กการค้นหาโค้ดรันบนที่เก็บเคอร์เนล Linux ที่ build แล้ว commit d0acc7
  • เหตุผลที่ใช้ที่เก็บเคอร์เนลที่ build แล้ว เพราะผลลัพธ์จากการ build อาจคงอยู่ในที่เก็บและส่งผลต่อความเกี่ยวข้องของผลการค้นหาและประสิทธิภาพได้
  • ใน linux_literal_default การค้นหาลิเทอรัลแบบง่าย PM_RESUME เผยให้เห็นความแตกต่างของพฤติกรรมเริ่มต้นของแต่ละเครื่องมือ
    • rg เคารพ .gitignore และข้ามไฟล์ซ่อนกับไฟล์ไบนารี
    • ag และ pt ก็คล้ายกัน แต่จะนับจำนวนบรรทัดด้วย
    • ucg ไม่อ่าน .gitignore และค้นหาโดยอิง whitelist
    • sift โดยพื้นฐานค้นหาแทบทุกอย่าง
    • git grep มีข้อได้เปรียบจากการได้ชุดไฟล์ที่จะค้นหาจาก git index
  • การเคารพ .gitignore ช่วยเพิ่มความเกี่ยวข้องของผลลัพธ์ แต่อาจมีต้นทุนด้านประสิทธิภาพ
  • ใน linux_literal ค่า rg (whitelist) แสดงประสิทธิภาพแทบเท่ากับ ucg และ rg (ignore) อยู่ในระดับใกล้เคียงกับ git grep
  • rg (ignore) (mmap) และ ag (ignore) (mmap) ช้าลงจากการใช้ memory map และภายใต้เงื่อนไขเดียวกัน rg (ignore) เร็วกว่ามาก
  • บนเครื่องโลคัล เวอร์ชันที่ใช้ memory map ก็ช้ากว่าเช่นกัน แต่ส่วนต่างน้อยกว่าบน EC2

Unicode และการค้นหาแบบไม่แยกตัวพิมพ์ใหญ่เล็ก

  • ใน linux_literal_casei pt ช้าลงอย่างมากเพราะจัดการ -i เป็น (?i) ของ Go regexp
  • sift ช้าลงน้อยกว่าเพราะใช้วิธีแปลงแพตเทิร์นและบล็อกค้นหาเป็นตัวพิมพ์เล็ก แต่การปรับให้เหมาะสมนี้รองรับเฉพาะตัวพิมพ์ใหญ่เล็กแบบ ASCII จึงไม่ถูกต้องสำหรับการจัดการตัวพิมพ์ใหญ่เล็กของ Unicode
  • ripgrep แปลงการค้นหาแบบไม่สนตัวพิมพ์ใหญ่เล็กให้เป็นชุดลิเทอรัลที่เป็นไปได้ และใช้ Teddy เพื่อหาตำแหน่งผู้สมัครอย่างรวดเร็ว
  • การค้นหา \wAh ใน linux_unicode_word ตรวจสอบว่า \w ที่รับรู้ Unicode จับผลลัพธ์อย่าง µAh ได้หรือไม่
  • มีเพียง rg และ git grep ที่สลับ Unicode ได้ ส่วน ag, pt, sift, ucg ใช้ \w แบบ ASCII เท่านั้น
  • เมื่อเปิดการรองรับ Unicode git grep มีต้นทุนด้านประสิทธิภาพสูง แต่ ripgrep แทบไม่มีประสิทธิภาพลดลง
  • ripgrep รวมการถอดรหัส UTF-8 ไว้ใน finite state machine จึงแมตช์กับสตริงไบต์ UTF-8 โดยตรงโดยไม่ต้องมีขั้นตอนถอดรหัสแยกต่างหาก

ความแตกต่างตามความซับซ้อนของเรกิวลาร์เอ็กซ์เพรสชัน

  • สำหรับเรกิวลาร์เอ็กซ์เพรสชันที่มี suffix เป็นลิเทอรัลอย่าง [A-Z]+_RESUME rg และ ucg ใช้ _RESUME เพื่อหาผู้สมัครอย่างรวดเร็ว
  • สำหรับลิเทอรัล alternation อย่าง ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT ripgrep ใช้ Teddy และอาจไม่ใช้เอนจินเรกิวลาร์เอ็กซ์เพรสชันหลักเลย
  • ใน alternation แบบไม่สนตัวพิมพ์ใหญ่เล็ก ripgrep ก็สร้าง prefix ของชุดตัวพิมพ์ใหญ่เล็กเพื่อหา candidate ด้วย Teddy แล้วตรวจสอบเฉพาะ candidate ด้วยเรกิวลาร์เอ็กซ์เพรสชันเต็ม
  • ในการค้นหา \p{Greek} มีเพียง Rust regex และ Go regex ที่รองรับพร็อพเพอร์ตี Unicode นี้ และ rg เร็วกว่า pt, sift มาก
  • ในการค้นหา \p{Greek} แบบไม่สนตัวพิมพ์ใหญ่เล็ก sift รายงานแมตช์ไม่ได้ และ pt จัดการตัวพิมพ์ใหญ่เล็กของ Unicode ไม่ถูกต้อง
  • สำหรับแพตเทิร์นที่ไม่มีลิเทอรัลอย่าง \w{5}\s+... ประสิทธิภาพของ regex engine จะแสดงออกมาโดยตรง
    • rg ยังค่อนข้างเร็วแม้อยู่ในสถานะรองรับ Unicode
    • git grep มีต้นทุนสูงเมื่อรองรับ Unicode
    • Unicode DFA จัดการชุดสถานะ NFA ที่ใหญ่กว่า ASCII DFA มาก โดยตัวเลขตัวอย่างคือ ASCII ประมาณ 250 สถานะ NFA และ Unicode ประมาณ 77,000 สถานะ NFA

การค้นหาในไฟล์ขนาดใหญ่ไฟล์เดียว

  • เบนช์มาร์กไฟล์เดี่ยวใช้ตัวอย่าง OpenSubtitles2016
    • ตัวอย่างภาษาอังกฤษมีขนาดประมาณ 1GB
    • ตัวอย่างภาษารัสเซียมีขนาดประมาณ 1.6GB
  • ในพื้นที่นี้ ประสิทธิภาพของ regex engine และการปรับลิเทอรัลให้เหมาะสมมีความสำคัญมากขึ้น
  • ใน subtitles_literal การค้นหา Sherlock Holmes และ Шерлок Холмс ทั้งคู่ rg เร็วที่สุด
  • ripgrep พยายามเลือก ไบต์ที่พบได้เบาบาง จากลิเทอรัลเพื่อใช้กับ memchr
    • การใช้งาน Boyer-Moore มาตรฐานมักใช้ไบต์สุดท้ายในการค้นหาผู้สมัคร
    • rg พยายามเลือกไบต์ที่พบน้อยกว่า เพื่อให้ข้ามในลูปที่ปรับด้วย SIMD ได้นานขึ้น
  • แพตเทิร์นภาษารัสเซียใน UTF-8 มีอักขระจำนวนมากที่ขึ้นต้นด้วย \xD0 หรือ \xD1 ทำให้การค้นหาไบต์แรกอาจไม่มีประสิทธิภาพ
  • rg ใช้ตารางความถี่ 256 ไบต์ที่คำนวณไว้ล่วงหน้า เพื่อเลือกไบต์ที่พบน้อยกว่าแทน \xD0, \xD1
  • สำหรับไฟล์ใหญ่ไฟล์เดียว เพียงสร้าง memory map ครั้งเดียวก็พอ ดังนั้นการค้นหาแบบ memory map ของ rg จึงเร็วกว่าการใช้ rg (no mmap) ประมาณ 25%

Unicode และ alternation ในไฟล์เดี่ยว

  • ใน subtitles_literal_casei rg รวดเร็วแม้จะจัดการการค้นหาแบบไม่สนตัวพิมพ์ใหญ่เล็กของ Unicode ได้ถูกต้อง
  • GNU grep มีต้นทุนสูงในการค้นหาแบบไม่สนตัวพิมพ์ใหญ่เล็กของ Unicode
  • ในการค้นหาภาษารัสเซียแบบไม่สนตัวพิมพ์ใหญ่เล็ก grep (ASCII) ดูเหมือนจะเพิกเฉยต่อ -i ในทางปฏิบัติ และ ag รายงานแมตช์ 0 รายการ
  • ใน subtitles_alternate การค้นหา alternation ของชื่อตัวละครหลายชื่อ rg เร็วที่สุดทั้งภาษาอังกฤษและรัสเซีย
  • สำหรับ alternation ภาษาอังกฤษ rg เร็วกว่า GNU grep ประมาณหนึ่งหลักลำดับขนาด
  • ใน subtitles_alternate_casei rg ช้าลงมากกว่าเดิม แต่ยังนำหน้าเครื่องมืออื่นในภาษาอังกฤษ
  • ในกรณีนี้ candidate ลิเทอรัลมีจำนวนมากเกินกว่าที่ Teddy จะรับไหว rg จึงเปลี่ยนไปใช้ Aho-Corasick แทน Teddy
  • ripgrep ใช้ Aho-Corasick แบบ “advanced” ที่อิง transition table เพื่อทำหนึ่ง transition ต่อไบต์อินพุต

inner literal และแพตเทิร์นที่ไม่มี literal

  • แพตเทิร์นอย่าง \w+\s+Holmes\s+\w+ ถูกสร้างขึ้นเพื่อหลีกเลี่ยงการปรับแต่ง prefix/suffix literal แต่ยังสามารถใช้ประโยชน์จาก inner literal Holmes ได้
  • ripgrep และ GNU grep ทำการปรับแต่งแบบ inner literal
  • ripgrep ใช้ regex-syntax ของ Rust regex เพื่อดึง literal ออกจาก AST ของแพตเทิร์น
  • ในเวอร์ชันภาษารัสเซีย \w+\s+Холмс\s+\w+ มีเพียงเครื่องมือที่รองรับ Unicode อย่างถูกต้องเท่านั้นที่ให้ผลลัพธ์ได้อย่างมีความหมาย
  • สำหรับแพตเทิร์นยาวที่ไม่มี literal เลยอย่าง \w{5}\s+... นั้น rg อยู่ในกลุ่มที่เร็วที่สุดสำหรับภาษาอังกฤษ ส่วน GNU grep เวอร์ชันที่รองรับ Unicode ใช้เวลามากกว่า 90 วินาทีในภาษาอังกฤษ และมากกว่า 4 นาทีในภาษารัสเซีย จึงถูกตัดออก
  • ripgrep รักษาการรองรับ Unicode พร้อมคงประสิทธิภาพไว้ได้ด้วยการรวมการถอดรหัส UTF-8 เข้าไปใน DFA

เบนช์มาร์กเพิ่มเติม

  • everything เป็นการทดสอบที่ไม่สมจริง ซึ่งจับคู่ทุกบรรทัดในรีโพซิทอรี Linux ด้วย .*
    • rg รายงาน 22,065,361 บรรทัดภายใน 1.081 วินาที
    • ag และ pt ไม่ได้รายงานทุกบรรทัด จึงดูเหมือนว่ามีการจำกัดจำนวนแมตช์
  • nothing เป็นการทดสอบที่ใช้ invert match กับ .* เพื่อไม่รายงานบรรทัดใดเลย
    • rg ทำเวลาได้ 0.302 วินาที ส่วน git grep ทำได้ 0.905 วินาที
    • pt และ ucg ไม่รองรับ invert search
  • context พิมพ์บริบท 2 บรรทัดรอบ ๆ Sherlock Holmes จากคอร์ปัสซับไตเติลภาษาอังกฤษ
    • rg ทำเวลาได้ 0.612 วินาที ส่วน sift ทำได้ 0.717 วินาที ซึ่งใกล้เคียงกัน
    • ucg ไม่รองรับฟีเจอร์นี้
  • huge ค้นหา Sherlock Holmes ในซับไตเติลภาษาอังกฤษทั้งหมดขนาด 9.3GB
    • rg ทำเวลาได้ 1.786 วินาที, GNU grep 5.119 วินาที และ sift 3.047 วินาที
    • ucg รายงานเพียง 1,543 บรรทัดภายใต้เงื่อนไขการนับบรรทัด จึงให้ผลลัพธ์ผิดพลาด และคาดว่าเกิดปัญหาในการค้นหาไฟล์ที่มีขนาดเกิน 2GB

สรุป

  • ripgrep ไม่ได้ชนะทุกเบนช์มาร์กในการค้นหารีโพซิทอรีเคอร์เนล Linux เสมอไป แต่ในแง่ประสิทธิภาพและความถูกต้อง ก็ยากที่จะบอกว่าเครื่องมืออื่นเหนือกว่าอย่างชัดเจน
  • git grep อาจเร็วกว่าได้ไม่กี่มิลลิวินาทีในบางกรณีที่เรียบง่าย แต่เมื่อแพตเทิร์นซับซ้อนขึ้นหรือจำเป็นต้องใช้ Unicode ก็มีบางกรณีที่ ripgrep นำห่างอย่างมาก
  • ปัจจัยต่อไปนี้มีส่วนช่วยต่อประสิทธิภาพการค้นหาโค้ดของ ripgrep
    • การไล่ดูไดเรกทอรีที่รวดเร็วโดยตั้งเป้าให้เรียก stat ให้น้อยที่สุด
    • การแมตช์ glob ของ .gitignore ด้วย RegexSet
    • การกระจายงานผ่าน Chase-Lev work stealing queue
    • การเลือกไม่ใช้ memory map เมื่อค้นหาไฟล์ขนาดเล็กจำนวนมาก
    • เอนจิน regular expression ที่รวดเร็ว
  • ในการค้นหาไฟล์เดี่ยว ripgrep เร็วที่สุดในเบนช์มาร์กหลักทั้งหมด หรือไม่ก็นำหน้าด้วยส่วนต่างมาก
  • ประสิทธิภาพของไฟล์เดี่ยวได้รับอิทธิพลจาก memchr แบบ sparse byte, Teddy SIMD, Aho-Corasick และ DFA ที่ฝังการถอดรหัส UTF-8 ไว้ในตัว
  • ในเบนช์มาร์กที่ต้องใช้ฟีเจอร์ Unicode มีเพียง rg, GNU grep และ git grep เท่านั้นที่แสดงการรองรับอย่างมีความหมาย และโดยทั่วไป GNU grep กับ git grep ต้องจ่ายต้นทุนด้านประสิทธิภาพสูง
  • บน Linux x86_64 memory map เสียเปรียบในการค้นหาไฟล์ขนาดเล็กจำนวนมากแบบขนาน แต่ได้เปรียบในการค้นหาไฟล์ขนาดใหญ่ไฟล์เดียว และในสภาพแวดล้อม VM อาจมี penalty เพิ่มเติม

1 ความคิดเห็น

 
GN⁺ 2023-12-01
ความคิดเห็นจาก Hacker News
  • เร็วจริง และยังคงแนะนำให้ใช้ร่วมกับ fzf อยู่เสมอ
    ใช้เป็นฟังก์ชัน PowerShell ที่ค้นหาด้วย ripgrep ก่อน จากนั้นซ้อนการค้นหาแบบ fuzzy บนไฟล์ผลลัพธ์+ข้อความ และใช้ bat แสดงบริบท
    ในโปรเจกต์ที่มีหลาย repository ปะปนกันอยู่ สามารถค่อย ๆ จำกัดขอบเขตได้เร็วมากในกรณีที่ “รู้ว่ามันอยู่ที่ไหนสักแห่ง แต่ไม่รู้ตำแหน่งหรือชื่อที่แน่ชัด”
    วิธีนี้มาจาก https://github.com/junegunn/fzf/blob/master/ADVANCED.md และแม้จะไม่ได้ใช้ทั้งหมด ก็ควรลองอ่านผ่าน ๆ เพื่อเก็บไอเดีย

    • แนะนำให้ไปอีกขั้นด้วยการผสาน ripgrep-all(rga) เข้ากับ fzf
      ทำให้ค้นหาแบบ fuzzy ได้ไม่ใช่แค่ไฟล์ข้อความ แต่รวมถึงไฟล์หลายรูปแบบอย่าง PDF, zip ด้วย
      รายละเอียดอยู่ที่ https://github.com/phiresky/ripgrep-all/wiki/fzf-Integration
    • ผมเขียนสิ่งนี้เป็น เวอร์ชัน bash ด้วย
      วิธีคือเลือกผลลัพธ์จาก rg ด้วย fzf แล้ว parse ไฟล์กับเลขบรรทัดที่เลือก จากนั้นเปิดด้วย $EDITOR +"${linenumber}" "$file"
    • ถ้าไม่มี fzf+rg ใน Vim ก็รู้สึกเหมือนแทบใช้งานไม่ได้
      เหมือนบดกาแฟด้วยมือแทนที่จะใช้เครื่องบดไฟฟ้า
    • เมื่อใช้ fzf จะเลือกไฟล์จำนวนมากเพื่อเพิ่มเข้า Git พร้อมข้ามบางไฟล์ได้
      ถ้าใส่ fza = "!git ls-files -m -o --exclude-standard | fzf -m --print0 | xargs -0 git add" ไว้ใน [alias] ของ gitconfig ก็จะใช้ git fza เพื่อแสดงรายการไฟล์ที่ถูกแก้ไขหรือยังไม่ได้เพิ่ม แล้วกดสเปซเพื่อ toggle รายการและเลื่อนไปยังรายการถัดไปได้
      alias นี้กับ fzf+fd ช่วยเร่งบางส่วนของ workflow ได้มากพอสมควร
      ยังมีคู่มือที่รวบรวมสิ่งที่ควรใส่ในค่า zsh บน macOS ด้วย: https://gist.github.com/aclarknexient/0ffcb98aa262c585c49d4b...
    • ผมก็ใช้ ripgrep ในวิธีเกือบเหมือนกัน
      ใช้เป็น จุดเริ่มต้น เพื่อจำกัดขอบเขตไฟล์หรือโปรเจกต์ใน codebase ที่มี repository หลายร้อยตัว แล้วค่อยเจาะลึกต่อ
  • ใน Emacs ผมใช้ ripgrep ผ่านแพ็กเกจ project.el และ dumb-jump
    อาจไม่ใช่วิธียอดนิยมที่สุด แต่ประสบการณ์โดยรวมค่อนข้างน่าพอใจ
    แค่ติดตั้ง dumb-jump ด้วย package-install แล้วตั้งค่า (add-hook 'xref-backend-functions #'dumb-jump-xref-activate) ก็พอ
    ในโปรเจกต์ Python เมื่อใช้ M-. หรือ C-u M-. เพื่อค้นหานิยามของ identifier, dumb-jump จะรันคำสั่ง rg ให้เหมาะกับโปรเจกต์และชนิดไฟล์ปัจจุบัน แล้วแสดงผลใน Xref buffer
    รองรับ ag ด้วย และถ้าไม่มี ag หรือ rg ก็จะ fallback ไปใช้ grep ซึ่งเวลาค้นหาทั้ง home directory ก็อาจช้าตามที่คาดไว้

    • ใช้ ripgrep ได้ค่อนข้างง่ายด้วย project.el ที่มากับ Emacs อยู่แล้ว
      ไม่จำเป็นต้องมีแพ็กเกจภายนอกเสมอไป และถ้าต้องการใช้แทน grep ที่ช้าในไดเรกทอรีใหญ่ ๆ ก็ตั้งค่า (setq xref-search-program 'ripgrep) ได้
      จากนั้นการค้นหาในโปรเจกต์อย่าง C-x p g foo RET จะถูกรันในโปรเจกต์ปัจจุบันในรูปแบบ rg -i --null -nH --no-heading --no-messages -g '!*/' -e foo
      ผลลัพธ์จะปรากฏใน Xref buffer ทำให้ใช้คีย์อย่าง n, p, RET, C-o เพื่อไป match ถัดไป/ก่อนหน้า, jump ไปซอร์ส, หรือแสดงในหน้าต่างแบ่งได้สะดวก
    • ในฐานะผู้เขียน ripgrep มองว่า regex นั้นผมยังไม่ได้ลองรันเอง แต่คิดว่าน่าจะตัด flag --pcre2 ออกได้
      assertion \b ตัวที่สองและสามก็น่าจะตัดออกได้เช่นกัน ส่วนตัวแรกอาจจำเป็น
    • Deadgrep ใช้ ripgrep และมี binding ของ evil-collection ด้วย จึงใช้งานได้อย่างน่าพอใจ: https://github.com/Wilfred/deadgrep
    • วิธีนี้ก็ดี แต่เวลาต้องการค้นหาหลายโปรเจกต์พร้อมกัน หรือค้นหาเฉพาะโฟลเดอร์ย่อยภายในโปรเจกต์ ผมยังคงใช้ rg.el
      ซึ่งเป็นสถานการณ์ที่เดิมทีคงใช้ rgrep
  • จุดที่น่าสนใจคือ การค้นหาของ VS Code ตอนนี้ก็ทำงานผ่าน ripgrep ด้วย wrapper ของ Node.js แล้ว
    https://www.npmjs.com/package/@vscode/ripgrep

    • ถ้าอยู่ในสภาพแวดล้อมที่ขอใช้หรือติดตั้ง VS Code ได้ แต่ติดตั้ง ripgrep ไม่ได้ สิ่งนี้ดีมาก
      สามารถหาไบนารี rg ได้ภายในเส้นทางติดตั้งของ VS อย่างน้อยในสภาพแวดล้อม Windows ที่ทำงานของผมก็ทำได้
    • สงสัยมาตลอดว่า VS Code เป็นแอป Electron แต่ทำไมค้นหาได้เร็วขนาดนั้น ตอนนี้รู้เหตุผลแล้ว
    • ไม่ใช่ฟีเจอร์ใหม่ เพราะมีอยู่ใน VS Code มาตั้งแต่ 7 ปีก่อนแล้ว
  • ใช้ ripgrep มาประมาณ 2 ปี และตอนนี้กลายเป็นเครื่องมือที่ขาดไม่ได้แล้ว
    เหตุผลหลักที่ย้ายจาก grep คือ ความสะดวกในการใช้งาน
    โดยค่าเริ่มต้นมันเคารพกฎ .gitignore และข้ามไฟล์/ไดเรกทอรีที่ซ่อนอยู่ รวมถึงไฟล์ไบนารี ทำให้ rg search_term directory ดีกว่าคำสั่ง grep ที่เทียบเท่ากันมาก ส่วนความเร็วที่เพิ่มขึ้นถือเป็นโบนัส
    เวลาที่ match ยาวเกินไปจนทำให้ terminal เละ ผมมักใช้ ตัวเลือก -M อย่าง -M 1000

    • -M ยอดเยี่ยมจริง ๆ
      สะดวกเป็นพิเศษเมื่อต้องการละเว้นผลลัพธ์จากไฟล์ minified ที่ไม่อยากเห็น และการใช้ ตัวเลือก -g อย่าง -g *.cs เพื่อค้นหาเฉพาะไฟล์นามสกุลหนึ่งก็ใช้ได้ดี
      การเป็นไบนารีแบบ standalone ที่พกพาได้ก็มีประโยชน์เช่นกัน เวลาใช้เครื่องใหม่ก็แค่ใส่ไฟล์ executable เข้าไป แล้วตั้ง alias ของ grep ให้เป็น rg ถึงพิมพ์ grep ตามความเคยชินก็จะรัน rg แทน
  • ในปี 2023 ก็อาจยังเป็นคำพูดที่ถูกอยู่ แต่ปัญหาคือเครื่องมือทดแทน grep ที่ทำงานแบบขนาน เช่น ripgrep หรือ ag เร็วกว่า grep เดิมมาก จนความต่างด้านความเร็วเล็กน้อยระหว่างกันแทบใช้เป็นเกณฑ์แยกแยะได้ยาก
    ผมใช้ ag ใน Emacs กับโค้ดเบสราว 900,000 บรรทัด และบน Ryzen Threadripper 2950X แบบ 16 คอร์ มันก็เสร็จแทบจะทันที
    ไม่รู้สึกว่าจำเป็นต้องลดเวลาที่ต่ำกว่า 1 วินาทีอยู่แล้วให้ “ต่ำกว่า 1 วินาทีขึ้นอีกนิด”
    คุณสมบัติหลักของเครื่องมือแนว grep รุ่นใหม่จึงไม่ใช่ความเร็ว และควรประเมินกับเปรียบเทียบด้วยวิธีอื่น

    • ผมคิดว่าในปี 2016 ความเร็ว เป็นคุณสมบัติหลักอย่างชัดเจน
      ag มี performance cliff ค่อนข้างใหญ่ และเห็นได้ในบทความบล็อกด้วย
      อย่างไรก็ตาม workload ของแต่ละคนต่างกัน ดังนั้นในบางกรณีความต่างด้านประสิทธิภาพอาจไม่สำคัญ
      900,000 บรรทัดไม่ได้ใหญ่มากนัก และถ้าเป็น query แบบง่าย ๆ เครื่องมือแนว grep ส่วนใหญ่ที่ไม่ได้ทำแบบไร้เดียงสาก็จัดการได้เร็วมาก
      ถ้ามองด้วยเกณฑ์เปรียบเทียบอื่น ag แทบจะอยู่ในสภาพประคองชีวิต และดูเหมือนว่าเคยเกือบถูกถอดออกจาก Debian ก่อนจะมีคนช่วยไว้: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=999962
      บทความบล็อกยังเปรียบเทียบ การรองรับ Unicode ด้วย ซึ่ง ag แทบไม่มีการรองรับ Unicode เลย อาจไม่สำคัญกับทุกคน แต่ก็เพียงพอจะเป็นเกณฑ์เปรียบเทียบที่ไม่ใช่ประสิทธิภาพได้
    • จากประสบการณ์ของผม เครื่องมือพวกนี้ทั้งหมดถูกผูกไว้กับ คอขวด I/O อย่างหนัก
      เวลาค้นหาจะกินเวลาพอ ๆ กับเวลาที่โหลดไฟล์จากดิสก์ และความต่างหลังจากนั้นมักมีนัยสำคัญได้ยาก
      ถ้าไฟล์อยู่ในแคช เวลาในการย้ายไปมาในไฟล์ซิสเต็มและพิมพ์คำสั่งจะมีอิทธิพลมากกว่าเวลาค้นหาอยู่ดี ดังนั้นความต่างด้านประสิทธิภาพก็มีนัยสำคัญได้ยากเช่นกัน
  • ในชื่อเรื่องควรมี (2016)
    นี่เป็นโพสต์ประกาศเดิม ไม่ใช่ข้อมูลใหม่

  • ไม่ได้เร็วกว่า qgrep
    วิธีทำงานของทั้งสองต่างกันมาก และ qgrep ใช้ re2 เป็นฐาน แต่ความเร็วได้มาจากการมี ดัชนี
    ใน repository ไฟล์ขนาดใหญ่ การใช้ qgrep กับดัชนีสมเหตุสมผลกว่าการไล่กวาดทุกไฟล์ทุกครั้ง ผมเลยสงสัยว่าทำไมคนถึงลืมตัวเลือก qgrep กัน
    อย่างไรก็ตาม ถ้าต้องการ match หลายบรรทัดใน UTF-8 ผมคิดว่า ripgrep ต้องถอยไปใช้ไลบรารี PCRE2 อีกตัว จึงไม่น่าจะเร็วขนาดนั้น

    • ในฐานะผู้เขียน ripgrep เป็นเรื่องจริงที่ qgrep ได้เปรียบเครื่องมือที่ไม่ทำ indexing เพราะมันใช้ indexing
      แต่ก็ต้องตั้งค่าและดูแลดัชนี ดังนั้น UX จึงไม่เรียบง่ายเท่า “สั่งค้นหาเลย”
      เหตุผลที่คนไม่ใช้ qgrep ก็คล้ายกับเหตุผลที่ไม่ใช้ ripgrep เพราะ “สำหรับผม grep ก็เร็วพอแล้ว”
      กับเป้าหมายค้นหาขนาดเล็ก หลายครั้งผู้ใช้ไม่รู้สึกถึงความต่างด้านความเร็วระหว่าง ripgrep กับ grep หรือระหว่าง qgrep กับ ripgrep
      ถ้า ripgrep ค้นหา Linux kernel เสร็จภายใน 100ms การเปลี่ยนไปใช้เครื่องมือแบบ indexing ในการใช้งานโต้ตอบทั่วไปจะคุ้มกับความไม่สะดวกหรือไม่ก็ขึ้นกับสถานการณ์ แต่โดยมากคงไม่
      ผมเคยคิดถึงไอเดียเพิ่ม indexing ให้ ripgrep: https://github.com/BurntSushi/ripgrep/issues/1497
      และการค้นหาหลายบรรทัด ไม่จำเป็นต้องใช้ PCRE2 เอนจิน regular expression พื้นฐานก็รองรับ Unicode และแม้จะ build โดยไม่มี PCRE2 ก็ยังคงรองรับการค้นหาหลายบรรทัดอยู่
  • หลังย้ายจาก ripgrep ไปใช้ ugrep แล้วก็ไม่ได้หันกลับมาอีก
    ความเร็วก็ใกล้เคียงกัน แต่มี fuzzy matching มี TUI ที่ใช้กับ code review ได้ และค้นหาใน PDF หรือไฟล์บีบอัดได้ด้วย
    อีกอย่างที่สะดวกคือเลือกใช้ไวยากรณ์การค้นหาแบบ Google ได้
    https://ugrep.com

    • ผมเป็นแฟนตัวยงของ ripgrep แต่ช่วงหลังต้องไปหา ugrep เพราะฟีเจอร์ที่ ripgrep ไม่มีคือ การค้นหาภายในไฟล์ zip
      ค้นหาได้โดยไม่ต้องแตกไฟล์ลงดิสก์
      ผมจัดการ corpus ที่ถูกบีบอัดซึ่งมีไฟล์ข้อความเล็ก ๆ หลายล้านไฟล์ และดีที่ไม่ต้องแตกทั้งหมดลงไฟล์ซิสเต็ม เพราะไฟล์ซิสเต็มบางตัวรับมือกับขนาดระดับนี้ลำบาก
      ขอบคุณทั้งสองเครื่องมือ และขอบคุณผู้เขียนของแต่ละตัว
    • ถ้า grep เริ่มใช้ไวยากรณ์การค้นหาแบบ Google ผมกลัวว่าผลลัพธ์ส่วนใหญ่จะพยายามขายอะไรบางอย่างให้ผม
    • ผมหาบทความ “ugrep vs ripgrep” แบบผ่าน ๆ แล้วเจอโพสต์ที่ดูเหมือนผู้เขียน ugrep กับ ripgrep เถียงกันบน Reddit มาหลายปี
      เช่น https://www.reddit.com/r/programming/comments/120wqvr/ripgre...
      ก็แค่เรื่องเครื่องมือโอเพนซอร์ส แต่รู้สึกแปลก ๆ
    • สงสัยว่า TUI ดีกว่าการส่งผลลัพธ์ไปให้ fzf หรือเปล่า
      สำหรับผม ดูจะยากที่จะชนะ ความสามารถในการปรับแต่ง และความยืดหยุ่นของ fzf
    • ขอบคุณที่บอกเรื่องนี้
      killer feature น่าจะเป็น ความเข้ากันได้กับตัวเลือก command line ของ grep เดิม
      การไม่ต้องเรียนรู้ชุดตัวเลือกใหม่ทั้งหมดถือว่าดีทีเดียว
  • สงสัยว่าทำไม grep ถึงไม่ถูกแทนที่หรือปรับปรุง
    เรื่องนี้เองก็น่าจะค่อนข้างเก่าแล้ว

    • มีเหตุผลอธิบายได้หลายอย่าง
      เช่น ความเฉื่อย, ความเข้ากันได้, การต่อต้านการเปลี่ยนแปลง, ภาวะกลืนไม่เข้าคายไม่ออกของผู้สร้างนวัตกรรม เป็นต้น ไม่ได้พูดในแง่ลบนะ เพราะทั้งหมดนี้ก็ใช้กับผมเหมือนกัน
      เรื่องความเข้ากันได้ให้ดู FAQ: https://github.com/BurntSushi/ripgrep/blob/master/FAQ.md#pos...
    • คล้ายกับเหตุผลที่ไม่เปลี่ยนเก้าอี้อายุ 40 ปีที่นั่งอยู่ตอนนี้เป็น Razer UltraSeat XR3000-A
      มันนั่งสบาย เข้ากับสภาพแวดล้อมการทำงานรอบตัวดี และไม่มีเหตุผลพอที่จะเปลี่ยนแล้วต้องปรับทุกอย่างใหม่
      อุปมานี้ไปได้แค่ถึงจุดที่ว่า มีเก้าอี้แบบ Razer อยู่ใกล้ ๆ อยู่แล้วและกำลังใช้พาดเสื้อผ้าอยู่
    • คนที่ออกแบบ Unix บางคนทำให้ฟีเจอร์ของระบบบางอย่างเป็นทั้งฟีเจอร์หลักของ OS และเป็นเครื่องมือที่มนุษย์ใช้ด้วย ผลคือหลายสิบปีต่อมาเกิดผลลัพธ์แปลก ๆ อย่าง “ต้องมีโปรแกรมชื่อ xyz อยู่เสมอ และต้องรับอาร์กิวเมนต์นี้แล้วทำงานแบบนี้อย่างแม่นยำ”
    • ตอนนี้ก็มีเครื่องมือทดแทนหลายตัวอย่าง ripgrep ให้ใช้แล้ว
      ถ้าหมายถึงการเปลี่ยนคำสั่ง grep เองให้เป็นยูทิลิตีตัวอื่น ดูเหมือนว่าจะมีของที่พังมากเกินไปเมื่อเทียบกับคุณค่าที่ได้
      คนที่ต้องการ grep ที่เร็วขึ้นก็ใช้เครื่องมืออื่น ส่วนคนที่ใช้ grep เดิมก็ใช้ต่อไปได้ ดังนั้นตอนนี้ก็ใกล้เคียงกับสภาพที่เหมาะสมแล้ว
    • grep เป็น เครื่องมืออเนกประสงค์ สำหรับค้นหาข้อความในไฟล์ทุกประเภท และฝังอยู่ในมาตรฐาน UNIX
      โปรแกรมเมอร์บางส่วนใช้มันค้นหาซอร์สโค้ด แต่คนอื่นใช้ค้นหาข้อความที่ไม่เกี่ยวกับซอร์สโค้ดหรือใช้ในสคริปต์ และคาดหวังว่ามันจะต้องไม่แครชเด็ดขาด
      ในทางกลับกัน ripgrep เป็นเครื่องมือเฉพาะทางที่มีแนวทางชัดเจน ออกแบบมาเพื่อค้นหาใน repository ของซอร์สโค้ดเป็นหลัก
      แทบไม่มีช่องให้ทำให้การค้นหาข้อความอเนกประสงค์เร็วขึ้นมากนัก ถ้าใช้ mmap() ก็มีความเสี่ยงแครชกับไฟล์ที่ถูกตัดทอน ถ้าลดความสามารถของ regular expression ก็อาจเร็วขึ้นได้ และอาจทิ้งการรองรับ locale กับชุดอักขระทั้งหมดแล้ว hardcode ให้ใช้แค่ UTF-8/UTF-16 ก็ได้ แต่ไม่ควรทำแบบนั้น
  • ลองหาใน Portage แล้วดูเหมือนว่าจะมีเวอร์ชันที่จัดการเอกสารอื่น ๆ อย่าง PDF และ doc ได้ด้วย
    https://github.com/phiresky/ripgrep-all