چرا ripgrep ابزار ۵۰ ساله grep را شکست داد؟ ۳ تصمیم مهندسی پشت سرعت ۸,۰۰۰ برابری

بررسی ۳ تصمیم مهندسی حیاتی در ripgrep که سرعت جستجو در کدهای متنی را تا ۸,۰۰۰ برابر نسبت به grep افزایش داد. بررسی عمیق معماری و SIMD.

چرا ripgrep ابزار ۵۰ ساله grep را شکست داد؟ ۳ تصمیم مهندسی پشت سرعت ۸,۰۰۰ برابری

نکات کلیدی (چکیده سریع)

  • جهش خارق‌العاده در دنیای واقعی: در یک پروژه واقعی با حجم ۲۱ گیگابایت، اجرای دستور grep بیش از ۴ دقیقه طول می‌کشد، در حالی که ripgrep همان جستجو را در ۳۰ میلی‌ثانیه (حدود ۸,۰۰۰ برابر سریع‌تر) انجام می‌دهد.
  • پردازش موازی با الگوریتم دزدی کار (Work-Stealing): برخلاف grep تک‌تردی و سنتی، ابزار ripgrep پوشه‌ها را میان تمام هسته‌های پردازنده توزیع می‌کند تا هیچ هسته‌ای بیکار نماند.
  • پیش‌فیلتر هوشمند با دستورات SIMD: به جای اجرای سنگین موتور عبارات باقاعده (Regex) روی تک‌تک بایت‌ها، ripgrep با کمک بردارسازی سخت‌افزاری پردازنده بایت‌های کلیدی را اسکن کرده و موتور رجکس را تنها در نقاط مشکوک فعال می‌کند.
  • راز اصلی: جستجو نکردن داده‌های زائد: بزرگ‌ترین برگ برنده ripgrep خواندن خودکار فایل‌های .gitignore و نادیده گرفتن پوشه‌های حجیمی مانند node_modules و دیتابیس گیت است.

اگر تاکنون محیط ترمینال لینوکس یا مک را باز کرده باشید، بدون شک با دستور grep کار کرده‌اید. این ابزار از سال ۱۹۷۳ توسط «کن تامپسون» (Ken Thompson) خلق شد و برای پنج دهه، انتخاب اول تمام برنامه‌نویسان جهان برای جستجو در میان فایل‌ها بود.

اما با بزرگ‌تر شدن پروژه‌های نرم‌افزاری و پیدایش مخازن چند ده گیگابایتی، ابزارهای قدیمی دیگر پاسخگوی نیازهای مدرن نیستند. اینجاست که ابزار انقلابی ripgrep (یا دستور rg) وارد میدان شد؛ ابزاری توسعه‌یافته با زبان Rust توسط «اندرو گالانت» (Andrew Gallant) که تجربه جستجو در کد را دگرگون کرد.

در یک بنچمارک واقعی روی پروژه‌ای به حجم ۲۱ گیگابایت، ابزار سنتی grep بیش از ۴ دقیقه زمان نیاز دارد؛ در حالی که ripgrep دقیقاً همان عبارت را در ۳۰ میلی‌ثانیه پیدا می‌کند! یعنی سرعتی نزدیک به ۸,۰۰۰ برابر بیشتر.

پاسخ کوتاه (Featured Snippet): ابزار ripgrep تا ۸,۰۰۰ برابر سریع‌تر از grep عمل می‌کند و این جهش فراتر از سرعت ذاتی زبان Rust است. این کارایی حاصل ۳ تصمیم مهندسی کلیدی است: پردازش موازی و الگوریتم دزدی کار (Work-Stealing)، فیلتر اولیه بایت‌ها با بردارسازی سخت‌افزاری SIMD جهت دور زدن محاسبات سنگین رجکس، و ادغام هوشمندانه با فایل‌های .gitignore برای خواندن حداقلی دیسک.


۱. بنچمارک شگفت‌انگیز: چرا VS Code و ابزارهای هوش مصنوعی به ripgrep کوچ کردند؟

وقتی از سرعت صحبت می‌کنیم، موضوع بهبود ۱۰ یا ۲۰ درصدی نیست؛ ما با چندین رتبه بزرگی (Orders of Magnitude) تفاوت در زمان اجرا مواجهیم.

graph LR
    subgraph SearchTime ["زمان جستجو در پروژه ۲۱ گیگابایتی (کمتر بهتر است)"]
    A["دستور سنتی GNU grep"] -->|۲۴۰,۰۰۰ میلی‌ثانیه / ۴ دقیقه| B["درگیری شدید دیسک و پیمایش کند"]
    C["ابزار مدرن ripgrep"] -->|۳۰ میلی‌ثانیه| D["نمایش آنی نتیجه"]
    end

همین سرعت خیره‌کننده و لحظه‌ای باعث شده است تا ابزارهای توسعه مدرن به جای پیاده‌سازی موتور جستجوی اختصاصی، مستقیماً از ripgrep به عنوان هسته اصلی خود استفاده کنند:

  • محیط VS Code: وقتی در وی‌اس کد کلیدهای Cmd+Shift+F (یا Ctrl+Shift+F) را فشار می‌دهید، در پس‌زمینه ابزار ripgrep اجرا می‌شود تا نتایج را در کسری از ثانیه روی صفحه بیاورد.
  • دستیاران و عامل‌های هوش مصنوعی (AI Coding Agents): ابزارهایی مانند Claude Code، Cursor و Codex برای ایندکس کردن کدهای کاربر و جستجوی ساختارهای کد، متکی به ripgrep هستند.

| ابزار جستجو | زبان توسعه | بنچمارک پروژه ۲۱ گیگابایتی | پردازش موازی | پشتیبانی خودکار از .gitignore | | :--- | :--- | :--- | :--- | :--- | | GNU grep | C | ~۲۴۰,۰۰۰ میلی‌ثانیه (۴ دقیقه) | خیر (تک‌ترد) | فقط با فلگ دستی (--exclude-dir) | | The Silver Searcher (ag) | C | ~۱,۲۰۰ میلی‌ثانیه (۱.۲ ثانیه) | بله | بله | | ripgrep (rg) | Rust | ~۳۰ میلی‌ثانیه (۰.۰۳ ثانیه) | بله (الگوریتم Work-Stealing) | داخلی و فوق‌العاده بهینه |

شاید بپرسید: «آیا این همه سرعت صرفاً به خاطر زبان Rust است؟» قطعاً زبان راست سریع است و به لطف عدم وجود زباله‌روب (Garbage Collector) حافظه را هدر نمی‌دهد؛ اما این شکاف ۸,۰۰۰ برابری ریشه در ۳ تصمیم مهندسی و معماری بسیار هوشمندانه دارد.


۲. تصمیم اول: پیمایش موازی فایل‌ها با الگوریتم «دزدی کار» (Work-Stealing)

ابزار grep در دورانی متولد شد که کامپیوترها تنها یک هسته پردازشی داشتند. به همین خاطر، ساختار آن کاملاً تک‌تردی (Single-threaded) و ترتیبی طراحی شده است؛ یعنی ابتدا پوشه A اسکن می‌شود، تک‌تک فایل‌هایش خوانده می‌شوند و سپس سیستم سراغ پوشه B می‌رود.

در دنیای امروز، حتی ارزان‌ترین لپ‌تاپ‌ها دارای ۸ هسته پردازشی هستند و سرورها ده‌ها هسته دارند. اجرای یک ابزار تک‌تردی یعنی بیش از ۹۰ درصد از توان سخت‌افزاری سیستم شما دست‌نخورده و بیکار می‌ماند.

ابزار ripgrep این معادله را به هم می‌زند. این ابزار از یک معماری چندتردی پیشرفته بر پایه الگوهای همروندی زبان Rust استفاده می‌کند.

graph TD
    A["پوشه اصلی مخزن کد"] --> B["توزیع‌کننده موازی دایرکتوری‌ها"]

    subgraph ThreadPool ["موتور دزدی کار در پردازنده چند هسته‌ای"]
    B --> T1["ترد ۱ (اسکن پوشه src/)"]
    B --> T2["ترد ۲ (اسکن پوشه tests/)"]
    B --> T3["ترد ۳ (اسکن پوشه docs/)"]

    T1 -.->|"ترد ۱ کارش تمام شد"| WS["صف اشتراکی دزدی کار"]
    WS -->|"دزدیدن زیرپوشه‌های باقی‌مانده"| T2
    end

    T1 --> R["بافر تجمیع خروجی نتایج"]
    T2 --> R
    T3 --> R

اما ماجرا از چه قرار است؟ ۱. ripgrep پوشه‌ها را میان تمامی هسته‌های در دسترس تقسیم می‌کند. ۲. اگر کار یک ترد زودتر از بقیه تمام شود، به خواب نمی‌رود؛ بلکه با مکانیزم «دزدی کار» (Work-Stealing)، زیرپوشه‌های ناتمام تردهای دیگر را می‌دزدد و اسکن آن‌ها را آغاز می‌کند. ۳. در نتیجه، حتی برای یک لحظه هم هسته‌های پردازنده بیکار نمی‌مانند و داده‌ها با بالاترین پهنای باند ممکن پردازش می‌شوند.


۳. تصمیم دوم: پیش‌فیلتر با شتاب‌دهنده سخت‌افزاری SIMD و فرار از رجکس

اجرای کامل یک موتور عبارات باقاعده یا رجکس (Regular Expression) روی تک‌تک بایت‌های یک فایل بزرگ، از نظر بار پردازشی کابوس است! اگر قرار باشد ماشین‌های حالت DFA/NFA برای هر کاراکتر ارزیابی شوند، سرعت پردازش به شدت افت می‌کند.

توسعه‌دهنده ripgrep نکته ظریفی را کشف کرد: تقریباً تمام عبارات منظم، دست‌کم یک کاراکتر یا پیشوند متنی مشخص و ثابت دارند.

به عنوان مثال، اگر به دنبال الگوی یک آدرس ایمیل باشید، تمام نتایج معتبر حتماً باید شامل علامت @ باشند. اگر به دنبال تابع خاصی باشید، کلمه کلیدی آن در الگو وجود دارد.

graph TD
    A["جریان داده‌های خام فایل"] --> B["موتور برداری SIMD پردازنده"]
    B -->|"بررسی ۳۲ تا ۶۴ بایت در هر کلاک پالس"| C{"آیا بایت کاندید پیدا شد؟"}

    C -->|"خیر (۹۹ درصد حجم فایل)"| D["رد شدن سریع از قطعه بدون اجرای رجکس"]
    C -->|"بله (یافتن علامت @ یا متن ثابت)"| E["اجرای متمرکز موتور رجکس روی همان بخش کوچک"]
    E --> F["ثبت خط و موقعیت نتیجه"]

شاهکار مهندسی ripgrep در این بخش بدین صورت کار می‌کند:

  • بردارسازی با SIMD: با استفاده از قابلیت سخت‌افزاری Single Instruction, Multiple Data (مانند دستورات AVX2 در اینتل یا NEON در پردازنده‌های ARM)، سیستم در هر تیک ساعت پردازنده به جای ۱ بایت، بین ۳۲ تا ۶۴ بایت را به صورت موازی بررسی می‌کند تا ببیند آیا کاراکتر مورد نظر (مثلاً علامت @) در آن محدوده هست یا خیر.
  • اجرای متمرکز و نقطه‌ای: موتور سنگین رجکس فقط و فقط روی همان چند بایتی اجرا می‌شود که دستور SIMD کاراکتر هدف را در آن پیدا کرده است.
  • دور زدن ۹۹ درصدی فایل: بخش اعظم محتوای فایل اصلاً وارد موتور محاسباتی رجکس نمی‌شود و بدون کوچک‌ترین اتلاف وقتی نادیده گرفته می‌شود!

علاوه بر این، هنگام جستجوی هم‌زمان چند کلمه، ripgrep از الگوریتم پیشرفته Aho-Corasick با شتاب‌دهنده‌های برداری اختصاصی استفاده می‌کند که به مراتب سریع‌تر از روش‌های سنتی Boyer-Moore در grep عمل می‌کند.


۴. تصمیم سوم: بزرگ‌ترین راز — جستجو نکردن داده‌های اضافه!

دو تصمیم اول سرعت پردازش را بالا می‌برند، اما مهم‌ترین دلیلی که در دنیای واقعی باعث برتری مطلق ripgrep می‌شود، کاملاً متفاوت است:

سریع‌ترین عملیات خواندن از دیسک، عملیاتی است که اصلاً انجام نشود!

graph LR
    subgraph GrepScan ["روش سنتی grep (خواندن کورکورانه تمام فایل‌ها)"]
    G1["پوشه اصلی پروژه"] --> G2["کدهای منبع src/ با حجم ۵۰ مگابایت"]
    G1 --> G3["پوشه node_modules/ با حجم ۴.۵ گیگابایت"]
    G1 --> G4["دیتابیس git/ با حجم ۱۶ گیگابایت"]
    G1 --> G5["خروجی کامپایل target/ با حجم ۱.۵ گیگابایت"]
    end
graph LR
    subgraph RipgrepScan ["روش هوشمند ripgrep (فیلتر بر اساس gitignore)"]
    R1["پوشه اصلی پروژه"] --> R2["بررسی فایل‌های gitignore."]
    R2 --> R3["اسکن موازی کدهای src/ (۵۰ مگابایت)"]
    R2 -.->|"نادیده گرفتن خودکار"| R4["پوشه node_modules/"]
    R2 -.->|"نادیده گرفتن خودکار"| R5["دیتابیس git/"]
    R2 -.->|"نادیده گرفتن خودکار"| R6["پوشه target/"]
    end

وقتی دستور سنتی grep -r را در یک پروژه اجرا می‌کنید، این ابزار کوچک‌ترین درکی از سیستم کنترل نسخه (Git) ندارد:

  • وارد پوشه مخوف node_modules/ می‌شود و صدها هزار پکیج جاوااسکریپت را تک‌تک می‌خواند.
  • تمام فایل‌های باینری فشرده‌شده در دایرکتوری .git/ را باز می‌کند.
  • تمام خروجی‌های کامپایل و فایل‌های باینری در پوشه‌های build/ یا target/ را اسکن می‌کند.

برای دور زدن این فایل‌ها در grep، باید دستوراتی طولانی با چندین فلگ --exclude-dir به خاطر بسپارید.

اما ripgrep قبل از باز کردن هر فایلی، ابتدا فایل‌های .gitignore و تنظیمات سراسری گیت را می‌خواند. اگر گیت آن فایل را نادیده گرفته باشد، ripgrep حتی فایل را باز هم نمی‌کند.

در پروژه ۲۱ گیگابایتی بنچمارک ما:

  • کل کدهای اصلی برنامه: فقط حدود ۲۰۰ مگابایت
  • وابستگی‌ها، باینری‌ها و تاریخچه گیت: بیش از ۲۰.۸ گیگابایت!

در حالی که grep به مدت ۴ دقیقه هارد دیسک را درگیر خواندن ۲۰ گیگابایت دیتای بی‌استفاده می‌کرد، ripgrep فقط همان ۲۰۰ مگابایت فایل متنی اصلی را خواند و در ۳۰ میلی‌ثانیه کار را تمام کرد.


۵. جمع‌بندی مهندسی: وقتی معماری بر بهینه‌سازی‌های سطحی پیروز می‌شود

تکامل از grep به ripgrep درس بزرگی برای همه مهندسان نرم‌افزار است.

| بعد معماری | رویکرد سنتی (grep) | رویکرد مدرن (ripgrep) | نتیجه در عملکرد | | :--- | :--- | :--- | :--- | | مدل همروندی | پیمایش بازگشتی تک‌تردی | صف پردازش موازی و دزدی کار | اشغال کامل و بهینه تمامی هسته‌های CPU | | موتور تطبیق الگو | بررسی بایت‌به‌بایت با رجکس | فیلتر اول سخت‌افزاری با SIMD | دور زدن رجکس برای ۹۹٪ داده‌ها | | پیمایش فایل‌سیستم | خواندن کورکورانه تمام هارد | خواندن خودکار قوانین .gitignore | حذف چندین گیگابایت عملیات دیسک (I/O) | | مدیریت حافظه | بافرهای دستی در زبان C | انتزاع بدون هزینه (Zero-Cost) در Rust | پایداری بالا، بدون نشت حافظه یا مکث سیستم |

زبان Rust به عنوان بستری مطمئن، بدون ریسک خطاهای اشاره‌گر و بدون بار پردازشی زباله‌روب، امکان ساخت چنین سیستمی را فراهم کرد. اما چیزی که اختلاف ۸,۰۰۰ برابری را رقم زد، تصمیمات معماری درست بود: توزیع موازی کارها، بهره‌گیری از توان سخت‌افزاری برداری و مهم‌تر از همه، دانستن اینکه چه چیزهایی را نباید خواند.


نتیجه‌گیری

موفقیت ripgrep ثابت کرد که حتی در پایه‌ای‌ترین ابزارهای ۵۰ ساله دنیای یونیکس، اگر ابزارها را متناسب با سخت‌افزار مدرن و نیازهای امروز برنامه‌نویسان بازطراحی کنیم، می‌توان به شاهکارهای مهندسی دست یافت.

دفعه بعد که در ادیتور خود با یک کلید ترکیبی در کمتر از یک ثانیه کل پروژه را زیر و رو کردید، این ۳ ستون را به یاد بیاورید: ۱. پردازش موازی برای مهار کل قدرت پردازنده. ۲. بردارسازی SIMD برای جهش از روی محاسبات سنگین رجکس. ۳. فیلتر هوشمند فایل‌ها برای درگیر نکردن دیسک.

تجربه شما از کار با ripgrep چیست؟ آیا ابزار خط فرمان دیگری را سراغ دارید که سرعت کار شما را متحول کرده باشد؟ نظرات و تجربیات خود را در بخش دیدگاه‌ها با ما در میان بگذارید!


پرسش‌های متداول (FAQ)

:::details چگونه می‌توان ripgrep را مجبور به جستجو در فایل‌های نادیده گرفته شده (gitignore) یا مخفی کرد؟ شما می‌توانید با اضافه کردن فلگ -u رفتار فیلتر را تغییر دهید. با یک بار استفاده از -u فایل‌های مخفی اسکن می‌شوند، با -uu قوانین .gitignore نادیده گرفته می‌شوند، و با -uuu فایل‌های باینری نیز همانند grep سنتی جستجو خواهند شد. :::

:::details آیا ripgrep فایل‌های باینری را هم به طور پیش‌فرض جستجو می‌کند؟ خیر. ابزار ripgrep بایت‌های ابتدایی فایل را برای کاراکترهای Null بررسی می‌کند و در صورت باینری بودن، فایل را رد می‌کند. این کار مانع از بهم‌ریختگی محیط ترمینال و اتلاف پردازنده روی فایل‌های اجرایی، تصاویر و آرشیوها می‌شود. :::

:::details ابزار ripgrep چه تفاوتی با The Silver Searcher (دستور ag) یا ack دارد؟ اگرچه ابزارهای ack و ag زودتر از ripgrep ایده نادیده گرفتن فایل‌های گیت را پیاده کردند، اما ripgrep به لطف موتور رجکس فوق‌سریع در زبان Rust، بهره‌گیری کامل از SIMD و صف‌های موازی بدون قفل، با اختلاف چشم‌گیری از هر دوی آن‌ها سریع‌تر است. :::

:::details چرا دستیاران هوش مصنوعی مانند Claude Code از ripgrep استفاده می‌کنند؟ عامل‌های هوش مصنوعی برای پاسخ‌دهی به درخواست‌های کاربر نیاز دارند کدهای پروژه‌های عظیم را در کمتر از چند میلی‌ثانیه جستجو و ارزیابی کنند. خروجی ساختاریافته در قالب JSON، سرعت میلی‌ثانیه‌ای و پشتیبانی دقیق از رجکس، ripgrep را به بهترین گزینه برای هوش مصنوعی تبدیل کرده است. :::