اگر امروز برای افزایش سرعت پردازش دادهها به Rust پناه میبرید، احتمالاً متوجه شدهاید که سریعترین کدها هم در لحظه بازگشت به پایتون کند میشوند. نبرد واقعی برای سرعت نه در قلب الگوریتم، بلکه در مرز میان این دو زبان رخ میدهد. یک تجزیهگر JSON با کارایی بالا که به زبان Rust نوشته شده باشد، میتواند تا ۳.۵ برابر سریعتر از نسخه بومی پایتون اجرا شود، اما چالش اصلی در نقطهای است که این دو زبان با هم تلاقی میکنند.
به گزارش belderbos.dev در ۱۳ سپتامبر ۲۰۲۶، ابزار PyO3 به توسعهدهندگان اجازه میدهد Rust را درون پایتون جاسازی کنند؛ تکنیکی که همین حالا هسته Pydantic v2 را از طریق کتابخانه pydantic-core قدرت میبخشد. برای اکثر برنامهنویسان پایتون، انعطافپذیری این زبان با یک «مالیات عملکرد» همراه است. وقتی یک کتابخانه باید حجم عظیمی از دادهها را اعتبارسنجی یا تجزیه کند، توسعهدهندگان برای انجام کارهای سنگین به Rust روی میآورند. این کار یک معماری ترکیبی میسازد که در آن Rust محاسبات را مدیریت میکند و پایتون رابط کاربری سطح بالا (High-level API) را بر عهده دارد.
همانطور که در تحلیلهای پیشین ما درباره بهینهسازی زیرساختهای داده اشاره کردیم، ادغام این دو زبان نیازمند یک خط لوله چهارمرحلهای است:
۱. نوشتن یک ماژول معمولی در Rust.
۲. افزودن متادیتای PyO3 به کد.
۳. کامپایل و نصب ماژول توسط ابزار Maturin.
۴. وارد کردن (Import) نتیجه در محیط پایتون.
توسعهدهندگان از ماکروهای PyO3 — بهویژه #[pyfunction] و #[pymodule] — برای ایجاد کدهای رابط (Glue Code) استفاده میکنند. این ماکروهای ویژگی (Attribute Macros) شبیه به دکوراتورهای پایتون عمل کرده و تابع را بهگونهای بازنویسی میکنند که تبدیل انواع داده و شمارش ارجاعات (Reference Counting) در مرز دو زبان مدیریت شود. سپس Maturin کد Rust را به یک کتابخانه مشترک (مانند .so در لینوکس، .dylib در مک یا .dll در ویندوز) تبدیل کرده و آن را در محیط مجازی (Virtual Environment) قرار میدهد تا دستور import بهسادگی کار کند.
تجزیهگر در سمت Rust
قبل از اینکه دادهها به پایتون برسند، تجزیهگر یک enum ساده در Rust تولید میکند. در یک دوره آموزشی تخصصی برای انتقال پایتون به Rust، دانشجویان شش هفته زمان صرف ساخت یک توکنساز (Tokenizer) و تجزیهگر بازگشتی (Recursive-descent Parser) از صفر کردند، بدون اینکه از کتابخانه معروف serde استفاده کنند.
طبق گزارشهای این دوره، یکی از دانشجویان به نام جاش نسخهای ساخت که در تستهای واقعی روی دادههای نمونه، از ماژول json در CPython (که با زبان C نوشته شده) سریعتر بود. دانشجوی دیگری به نام یوخن توانست به سرعتی ۳.۵ برابر سریعتر از نسخه پایتون دست یابد. هسته این سیستم یک JsonValue است که درخت JSON را بهدقت نقشهبرداری میکند:
NullBoolean(bool)Number(f64)String(String)Array(Vec<JsonValue>)Object(HashMap<String, JsonValue>)
این درخت کاملاً در محیط Rust زندگی میکند و پایتون هرگز خودِ این enum را نمیبیند، بلکه فقط نتیجه تبدیلشده و سازگار با پایتون را دریافت میکند.
سازوکار تبدیل دادهها
برای معرفی یک تابع به پایتون، تنها دو خط کد کافی است. امضای تابع از py: Python<'py> استفاده میکند که توکنی برای دسترسی به مفسر پایتون است. در ساختارهای سنتی پایتون، این دسترسی با نگه داشتن قفل جهانی مفسر (GIL) مرتبط است.
برای بازگرداندن دادهها، تابع از PyResult<Bound<'py, PyAny>> استفاده میکند. در اینجا Bound<'py, PyAny> در واقع دستهای برای یک شیء پایتون از هر نوعی است (معادل PyObject در زبان C) و PyResult پوششی برای Result<T, PyErr> است. اگر تابع parse در Rust با شکست مواجه شود، اپراتور ? خطا را به بالا منتقل میکند و پایتون یک Exception استاندارد را مشاهده میکند.
برای ترجمه مقادیر Rust، توسعهدهندگان ویژگی (Trait) IntoPyObject را پیادهسازی میکنند. این Trait تعیین میکند یک مقدار Rust چگونه به شیء پایتون تبدیل شود:
- Null: به
py.None().into_bound(py)تبدیل میشود. - Number: مقدار
f64به یک float پایتون تبدیل میشود. - Object: یک
HashMapباعث ایجاد یکPyDictمیشود که سپس بهصورت بازگشتی برای هر جفت کلید-مقدار، متدinto_pyobjectرا فراخوانی میکند. - Array: یک
Vecبه یک لیست (List) پایتون تبدیل میشود.
هزینه پنهان در مرز دو زبان
بر اساس بررسیهای belderbos.dev، حیاتیترین نکته عملکردی این است که مسیر بازگشت از Rust به پایتون اغلب گرانترین بخش فرآیند است. در حالی که تجزیه یک رشته JSON در Rust فوقالعاده سریع است، اما تبدیل (Materialization) آن به اشیاء پایتون رایگان نیست.
اگر یک سند شامل ۱۰۰,۰۰۰ مقدار باشد، فراخوانی into_pyobject باید ۱۰۰,۰۰۰ شیء مجزای پایتون ایجاد کند. در اسناد بزرگ، این حلقه تبدیل میتواند زمان اجرای نهایی را به تسلط درآورد و حتی از خودِ فرآیند تجزیه (Parsing) زمانبرتر باشد.
مدیریت خطاها در دو زبان
ابزار PyO3 ترجمه خطاها را بهگونهای مدیریت میکند که لایه Rust در تجربه کاربر پایتون «نشت» نکند. با پیادهسازی ویژگی From، خطاهای خاص Rust به استثناهای استاندارد پایتون تبدیل میشوند.
برای مثال، گونهی JsonError::UnterminatedString به یک PyValueError تبدیل میشود تا برنامهنویس پایتون یک خطای آشنا را همراه با موقعیت دقیق (Offset) شکست در تجزیه ببیند. به همین ترتیب، خطاهای std::io::Error بهطور خودکار به استثناهای متناظر پایتون تبدیل میشوند؛ به این معنی که اگر مسیر یک فایل اشتباه باشد، پایتون یک FileNotFoundError استاندارد را برمیگرداند.
پیامدهای معماری
برای کسانی که کد خود را به Rust منتقل میکنند، استراتژی به نوع داده بازگشتی بستگی دارد. اگر تابع یک مقدار تک (Scalar) مانند یک عدد یا رشته برگرداند، هزینه مرزی ناچیز است. اما برای ساختارهای بزرگ، تبدیل دادهها گلوگاه اصلی است.
برای بهینهسازی این وضعیت، توسعهدهندگان میتوانند گامهای زیر را بردارند:
- پیشتخصیص (Preallocation): تخصیص پیشفرض فضای
PyDictمیتواند در حاشیه به بهبود سرعت کمک کند. - بارگذاری تنبل (Lazy Loading): اجتناب از تبدیل کل درخت به اشیاء پایتون، در صورتی که فراخواننده به تمام بخشهای آن نیاز ندارد.
- نماهای مبتنی بر Rust (Rust-Backed Views): بازگرداندن یک نمای تنبل و ساخت اشیاء پایتون فقط در زمان درخواست (On-demand).
این تغییر دیدگاه — از تحلیل الگوریتم به تحلیل مرز تبدیل — تعیین میکند که آیا انتقال به Rust واقعاً در محیط عملیاتی سودمند است یا خیر. سریع کردن Rust بخش آسان ماجراست؛ مدیریت سفر مقادیر از Rust به اشیاء پایتون است که ارزش این انتقال را تعیین میکند.
گام بعدی شما
- اگر از کتابخانههایی مثل Pydantic استفاده میکنید، بررسی کنید که آیا حجم دادههای خروجی شما باعث کندی در لایه تبدیل شده است یا خیر.
- در پروژههای Rust-Python، بهجای بازگرداندن ساختارهای پیچیده، سعی کنید از Lazy Views برای کاهش تعداد اشیاء ایجاد شده در پایتون استفاده کنید.
- مستندات PyO3 را برای پیادهسازی بهینه
IntoPyObjectمطالعه کنید تا از ایجاد اشیاء تکراری جلوگیری شود.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما درباره تراشههای Blackwell مراجعه کنید.




گفتگو