اگر در حال حاضر از ابزارهای جستوجوی ساختاری در پروژههای عظیم کدنویسی استفاده میکنید، احتمالاً با گلوگاههای پردازشی در هنگام تحلیل فایلها مواجه شدهاید. بازنویسی تخصصی هسته Tree-sitter در زبان Rust توانست مصرف کلی پردازنده (CPU) را در ابزار ast-grep تا ۲۲.۲٪ کاهش دهد. طبق اعلام تیم توسعه در ۲۶ ژوئیه ۲۰۲۶، این پروژه نشان میدهد که چگونه ترجمه کد با کمک هوش مصنوعی میتواند «دیوارهای باربر» قدیمی یک سیستم پیچیده را جابهجا کند، بدون اینکه اکوسیستم اطراف آن فرو بریزد. مخزن این پروژه در HerringtonDarkholme/tree-sitter در دسترس است.
جستوجوی ساختاری بر خلاف جستوجوی متنی ساده، بر پایه درختهای نحو (Syntax Trees) عمل میکند. Tree-sitter — شبیه به یک مترجم دقیق که زبان برنامهنویسی را به نقشههای درختی تبدیل میکند تا کامپیوتر بفهمد هر بخش از کد دقیقاً چه نقشی دارد — استانداردی برای تولید این درختهاست. برای ابزارهایی مانند ast-grep، تجزیکننده (Parser) بنیاد و زیربنای هر عملیاتی است؛ اما پیادهسازی اولیه آن با زبان سی (C) در نهایت به یک سقف عملکردی تبدیل شد که بهینهسازیهای دستی دیگر قادر به شکستن آن نبودند.
همانطور که در تحلیلهای پیشین ما درباره بهینهسازیهای سطح پایین در ابزارهای توسعه اشاره کردیم، جایگزینی موتور مرکزی بدون تغییر در رابطهای بیرونی، چالشی مهندسی است. تصور کنید تجزیکننده، موتور یک خودرو است. ast-grep لاستیکها و آیرودینامیک خود را (از طریق قوانین داخلی و سیستمهای کشینگ) بهینه کرده بود، اما همچنان محدود به اسب بخار موتور بود. هدف این بود که موتور قدیمی را با یک موتور مبتنی بر Rust جایگزین کنند، بدون اینکه نحوه اتصال سایر بخشهای خودرو به آن تغییر کند. این گرایش به استفاده از Rust برای جایگزینی اجزای حساس به عملکرد در سیستمهای حیاتی، مشابه تصمیم جامع برای پذیرش Rust در هسته لینوکس است تا امنیت حافظه و کارایی همزمان تضمین شود.
استراتژی مهاجرت با هوش مصنوعی
این بازنویسی با یک رویکرد محافظهکارانه به عنوان یک تلاش برای ترجمه آغاز شد. توسعهدهنده از ChatGPT استفاده کرد تا مطمئن شود کد جدید Rust به عنوان یک «اوراکل رفتاری» (Behavioral Oracle) برای پیادهسازی اصلی در سی عمل میکند. این به معنای حفظ دقیق رابط باینری (ABI)، گرامرهای تولید شده موجود و اسکنرهای خارجی بود.
به نقل از گزارش astgrep.com، این فرآیند از یک قرارداد سختگیرانه پیروی کرد:
- استفاده از تستهای موجود به عنوان تنها مرجع حقیقت برای رفتار سیستم. یک پیادهسازی Rust که صرفاً «منطقی» به نظر میرسید کافی نبود؛ بلکه باید دقیقاً همان درختها، رفتارهای بازیابی (Recovery)، نتایج ناوبری و اثرات API عمومی را تولید میکرد.
- اطمینان از سازگاری جداول زبان تولید شده بدون نیاز به تولید مجدد. گرامرهای تولید شده موجود و اسکنرهای خارجی باید بدون هیچ تغییر در سورس کد به کار خود ادامه میدادند.
- حفظ رابط باینری (ABI). جداول زبان تولید شده، توابع عمومی سی، لایه بندیها (Layouts)، نمادها و قراردادهای فراخوانی باید سازگار میماندند، در حالی که زبان پیادهسازی در پشت صحنه تغییر میکرد.
- ترجمه خطبهخط جریان کنترل (Control Flow) پیش از هرگونه تلاش برای بازطراحی. اولین نسخه Rust عمداً شبیه به جریان کنترل در سی بود تا در صورت بروز شکست در برابری (Parity)، محدوده جستجوی خطا محدود باشد.
این رویکرد اجازه داد تا پروژه از ابزارهای پایه و ذخیرهسازی درخت به سمت حلقه پیچیده تجزیهکننده (Parser Loop) حرکت کند. هوش مصنوعی به عنوان پیادهساز اصلی عمل کرد، در حالی که انسان اهداف، محدودیتها و تصمیمات را بر اساس دادههای پروفایلینگ تعیین میکرد. عامل (Agent) کدهای سی و Rust را میخواند، وصلهها (Patches) را مینوشت، خطاهای کامپایلر را رفع میکرد، تستها را اجرا کرده و عدم تطابقها را بررسی مینمود. پیادهسازی، پروفایلینگ، ابزار دقیق (Instrumentation) و بخش بزرگی از کدهای آزمایشی توسط این عامل تحت هدایت نویسنده تولید شدند. این تجربه تداعیکننده پروژه بازنویسی گسترده Bun توسط مدل Claude است که نشان داد هوش مصنوعی میتواند حجم عظیمی از کدها را در زمان بسیار کوتاهتری نسبت به توسعهدهندگان انسانی بازنویسی کند.
شکست «برنامهنویسی حسی» (Vibe Coding)
سعیهای اولیه برای بهینهسازی زمانی شکست خورد که توسعهدهنده از یک پرامپت ساده مانند «عملکرد را ۲۰٪ بهبود ببخش» استفاده کرد؛ فرآیندی که نویسنده آن را Vibe Coding یا برنامهنویسی حسی مینامد. اگرچه هوش مصنوعی کدهایی تولید کرد که اعداد بنچمارک را بالا میبردند، اما نتیجه یک آشفتگی غیرقابلخوان از بهینهسازیهای متداخل بود. نویسنده از ChatGPT خواست تا از ابزارهای پروفایلینگ مناسب استفاده کند و به دنبال تغییرات الگوریتمی باشد، اما نتایج ناپایدار بودند.
این نسخه در نهایت شروع به Segfault کردن کرد، آن هم بدون اینکه Panicهای دوستانه Rust صادر شوند؛ فرآیند به سادگی متوقف میشد بدون اینکه هیچ Assertion شکست خوردهای گزارش شود. نویسنده تمام این تغییرات را بازگرداند و به این نتیجه رسید: تجزیکننده ای که ۲۰٪ سریعتر است اما گاهی ناگهان ناپدید میشود، یک شکست است. این اتفاق استراتژی را از درخواست از AI برای «سریعتر کردن توده کد» به «توضیحپذیر کردن سیستم» تغییر داد.
محدود کردن دامنه برای عاملهای هوش مصنوعی
برای رسیدن به سرعت واقعی، تیم یک مرز محصولی حیاتی را شناسایی کرد. Tree-sitter در حالت عادی برای ویرایشگرهای متن طراحی شده است، جایی که تجزیه افزایشی (Incremental Parsing) اجازه میدهد ابزار تنها بخش کوچکی از درخت را که با یک ضربه کلید تغییر کرده، بهروزرسانی کند. در آن دنیا، تجزیه مجدد کل فایل پس از هر ضربه کلید، کاری بیهوده است.
اما ast-grep و عاملهای کدنویسی هوش مصنوعی روی «اسنپشاتهای» کامل فایل عمل میکنند. یک عامل فایل را میخواند، آن را تحلیل یا بازنویسی میکند و سپس از ابزار میخواهد اسنپشات جدید را پردازش کند. در اینجا، بازخوانی کل فایل عملیات نرمال است، نه یک حالت پشتیبان یا جایگزین.
به همین دلیل، توسعهدهنده از ChatGPT خواست قابلیتهای زیر را حذف کند:
- بازاستفاده از درختهای قدیمی (Incremental old-tree reuse): ماشینافزارهای مربوط به یافتن و استفاده مجدد از تکههای درخت قدیمی از مسیرهای حساس پیادهسازی حذف شدند. اگرچه پارامتر عمومی برای حفظ سازگاری باقی ماند، اما زمان اجرا (Runtime) اکنون تجزیه را از ابتدا و به صورت تازه انجام میدهد.
- بارگذاری بومی گرامرهای کامپایلشده با Wasm: در حالی که بیلد Wasm برای مرورگر باقی ماند، قابلیت بارگذاری گرامرهای Wasm در ابزارهای بومی حذف شد، زیرا بخشی از حجم کاری هدف نبود.
حذف این ویژگیهای بلااستفاده، اولین بهینهسازی واقعی بود. این کار مسیر اجرای کد (Hot Path) را پاک کرد و اجازه داد منطقهای سنگین و وابسته به اشارهگرهای سی به ارجاعات اصولی Rust و تایپهای Option تبدیل شوند. این بازسازی باعث شد پارامترهای اشارهگر خام در جاهایی که Lifetimes محلی بودند، به Reference یا Slice تبدیل شوند و اشارهگرهای Sentinel به تایپهای Option تغییر یابند. ماژولهای بزرگ بر اساس مسئولیت تفکیک شدند و محاسبات پیچیده اشاره degeneration اشارهگرها پشت عملیاتهای نامگذاریشده و محدود پنهان گشتند.
معماری تجزیه و الگوریتم GLR
Tree-sitter کد منبع را با استفاده از جداول تجزیه تولید شده و کد Lexer به یک درخت نحو تبدیل میکند. یک Lexer کاراکترها را به توکن (Token) تبدیل میکند؛ سپس تجزیکننده با استفاده از یک پشته (Stack) و جدول تولید شده، معنای توکن را از طریق دو عملیات اصلی تعیین میکند: انتقال (Shift - مصرف یک توکن و قرار دادن آن روی پشته) و کاهش (Reduce - ترکیب چندین تکه نحوی در یک قانون والد).
به دلیل اینکه گرامرهای زبانهای برنامهنویسی اغلب دارای تداخل (Conflict) هستند، Tree-sitter از تجزیه GLR (Generalized LR) استفاده میکند. این روش اجازه میدهد تا چندین تاریخچه احتمالی بهطور همزمان با به اشتراک گذاشتن یک گذشته مشترک در یک پشته با ساختار گراف دنبال شوند. اگرچه این ماشینافزار گراف برای گرامرهای مبهم ضروری است، اما وقتی مسیر تجزیه مستقیم است، بسیار ناکارآمد عمل میکند.
الگوریتم GLR و مدیریت حافظه
پس از اینکه کد قابل نگهداری شد، تیم روی الگوریتم تجزیه GLR تمرکز کرد. در تجزیه LR معمولی، تجزیکننده یک تاریخچه را با یک پشته دنبال میکند. اما زبانهای برنامهنویسی تداخلاتی دارند که در آنها چندین اقدام ممکن است معتبر باشند تا زمانی که ورودی بیشتری دریافت شود. Tree-sitter از GLR برای دنبال کردن چندین تاریخچه همزمان استفاده میکند.
بیشتر مسیرهای تجزیه خطی هستند، اما Tree-sitter منابع زیادی را صرف ساخت ماشینافزار گراف برای هر مسیر میکرد، حتی آنهایی که ابهامی نداشتند. بهینهسازیهای کلیدی فنی شامل موارد زیر بود:
- مسیر پشته خطی (Linear Stack Path): اکنون تجزیکننده فقط زمانی گراف میسازد که ورودی واقعاً دوشاخه شود و از ثبت bookkeeping برای ۹۹٪ از مواردی که مسیر مستقیم است، اجتناب میکند. این تغییر بر اساس کارهای آکادمیک روی تجزیکنندههای تعمیمیافته بود.
- تخصیص آرنا (Arena Allocation): بهجای فراخوانی تخصیصدهنده عمومی (General-purpose allocator) برای هر گره نحوی داخلی — که بسیار گران است — یک «آرنا» یک بلوک در حال رشد را میگیرد و گرههای بسیاری را از آن تامین میکند.
- اندیسهای فشرده: تیم با استفاده از اندیسهای کوچکتر، مقدار بایتهای جابهجا شده بین پشته تجزیکننده و درخت را کاهش داد.
- مسیر سریع ASCII: ورودیهای ASCII معمولی اکنون مسیر کامل رمزگشایی کاراکترها را دور میزنند تا یک مسیر کوتاه برای سادهترین موارد ایجاد شود.
- جستوجوهای پیشمحاسبه شده: تجزیکننده جستوجوهای رایج گرامری را از پیش آماده میکند تا از تکرار کار جلوگیری شود.
حل تناقض عملکرد نهایی (End-to-End Paradox)
نتایج اولیه بهبود ۳۰ درصدی در تجزیه خام را نشان داد (توان عملیاتی از ۱۰۰ به ۱۲۹.۷۴ رسید)، اما کل اپلیکیشن ast-grep در واقع کندتر اجرا میشد! مقصر، یک «مراسم حافظه مجازی» (Virtual Memory Ceremony) بود: اولین آرنا هر بار هنگام ایجاد یک تجزیکننده، یک منطقه عظیم از حافظه مجازی را رزرو میکرد.
در یک مخزن با هزاران فایل، این موضوع باعث تلاطم شدید جداول صفحه (Page-table churn) و فراخوانیهای گرانقیمت سیستم (Syscalls) برای رزرو و آزادسازی حافظه شد. تیم این مورد را با یک تخصیص کوچک معمولی که بر اساس نیاز رشد میکند، جایگزین کرد.
جراحیهای بیشتری برای «مجموعه استرس تایپاسکریپت» (درختهای پایه تست مخزن کامپایلر TypeScript) مورد نیاز بود. این مورد، تست شکنجه حافظه پروژه است. حافظه اشغالشده (RSS) در ابتدا به ۱.۰۴ گیگابایت رسید. از طریق بهبودهای تکرار شونده در آرنا، تیم این مقدار را به ۹۱.۲ مگابایت کاهش داد.
در نهایت، خواننده درخت بهینهسازی شد تا گروههای فرزند را یکبار پردازش کند و نه بهطور مکرر. این کار زمانی را که به دلیل استفاده از اندیسهای فشرده از دست رفته بود، بازیابی کرد. نتیجه نهایی برای اجرای کامل outline در ast-grep (تجزیه تمام فایلها در یک مخزن و پیمایش هر درخت)، با ۲۲.۲٪ کمتر در مصرف CPU کاربر (۰.۹۶۰ ثانیه در برابر ۱.۲۳۳ ثانیه) نسبت به بیلد اصلی سی به پایان رسید. این تمرکز بر بهینهسازیهای سطح پایین برای دستیابی به سرعتهای خیرهکننده، یادآور استفاده از SIMD برای رسیدن به سرعت توکنسازی ۲۴ گیگابایت بر ثانیه است که در آن هر سیکل پردازشی به دقت مهندسی شده است.
خلاصه دادههای عملکرد
تاثیر بازنویسی Rust در معیارهای مختلف به شرح زیر است:
- تجزیه خام (Raw Parsing):
- توان عملیاتی: ۱۲۹.۷۴ (نسخه سی = ۱۰۰)، افزایش ۲۹.۷۴٪.
- حافظه RSS: ۸.۴۲–۲۵.۷۰ مگابایت (نسخه سی = ۸.۴۸–۲۱.۴۱ مگابایت)، که نشاندهنده افزایش ۲۰ درصدی در سقف است.
- پیمایش درخت (Tree Traversal):
- توان عملیاتی: ۱۱۰.۱۶ (نسخه سی = ۱۰۰)، افزایش ۱۰.۱۶٪.
- حافظه RSS: ۲۲.۲۰ مگابایت (نسخه سی = ۲۰.۳۸ مگابایت)، افزایش ۸.۹٪.
- اجرای کامل (Complete Outline):
- CPU کاربر: ۰.۹۶۰ ثانیه (نسخه سی = ۱.۲۳۳ ثانیه)، کاهش ۲۲.۲٪.
- حافظه RSS: ۳۴.۴۳ مگابایت (نسخه سی = ۲۶.۵۲ مگابایت)، افزایش ۲۹.۸٪.
تحلیل: تقسیم کار جدید
این پروژه فرضيات درباره نحوه پورت کتابخانههای قدیمی سی به Rust را تغییر میدهد. این نشان میدهد که «پورت قهرمانانه» (بازنویسی دستی همه چیز) دیگر تنها راه نیست. در عوض، هوش مصنوعی میتواند فضای پیادهسازی را با سرعتی کاوش کند که انسانها قادر به رقابت با آن نیستند. حلقه اولیه «هدف $ \rightarrow $ کد $ \rightarrow $ بنچمارک $ \rightarrow $ وصله» ناکارآمد بود، اما حلقه بعدی «کار سنگین $ \rightarrow $ توضیح $ \rightarrow $ تغییر مکانیزم $ \rightarrow $ مقایسه $ \rightarrow $ تست اپلیکیشن» موفقیتآمیز بود.
با این حال، این پروژه هشدار میدهد که عملکرد تولید شده توسط AI اغلب یک سراب است اگر فقط در بنچمارکهای ایزوله اندازهگیری شود. شکاف بین یک «تجزیهکننده ۳۰٪ سریعتر» و یک «اپلیکیشن کندتر»، برجسته میکند که گرانترین بخشهای یک سیستم اغلب مرزهای بین اجزاء (مانند تخصیص حافظه و syscallها) هستند، نه خود منطق اصلی.
برای توسعهدهندگان، نتیجه تغییر در نقش است: انسان دیگر تایپیست اصلی نیست، بلکه کیوریتور (Curator) ناورداها (Invariants) و محقق پروفایلهاست. هوش مصنوعی میتواند سرعت کاوش را فراهم کند، اما انسان باید شواهد درستی را ارائه دهد.
منتظر باشید تا این الگو در سایر ابزارهای اصلی توسعه تکرار شود. همانطور که عاملهای AI در مدیریت سازگاری ABI و محاسبات اشارهگر توانمندتر میشوند، احتمالاً شاهد موجی از Runtimeهای Rust «محدود شده» خواهیم بود که ویژگیهای متمرکز بر ویرایشگر را حذف میکنند تا توان عملیاتی را برای گردش کارهای هوش مصنوعی (Agentic AI Workflows) به حداکثر برسانند.




گفتگو