تصور کنید توسعهدهندگان پایتون بتوانند میلیونها تسک همزمان را روی تمام هستههای CPU اجرا کنند، بدون اینکه حتی یک بار کلمه async/await را بنویسند. این وعدهی Runloom است؛ محیط اجرای جدیدی برای نسخههای free-threaded پایتون ۳.۱۳t و بالاتر که با پیادهسازی یک زمانبند «سرقتکار» (work-stealing scheduler) به سبک زبان Go، کدهای مسدودکننده (blocking) را مانند فیبرهای تعاونی مدیریت میکند. طبق مستندات منتشرشده در گیتهاب این پروژه در ۱۰ ژوئیه ۲۰۲۶، این سیستم با استفاده از اسمبلی دستنویس برای تعویض زمینه (context switching)، به عملکردی نزدیک به زبانهای نیتیو دست یافته است.
سالها بود که قفل مفسر جهانی (GIL) در پایتون، برنامهنویسان را مجبور میکرد بین پیچیدگی asyncio یا سربار سنگین رشتههای سیستمعامل (OS threads) یکی را انتخاب کنند. در حالی که asyncio مقیاسپذیر است، اما نیازمند بازنویسی کامل کد در بلوکهای async def است. همانطور که در تحلیلهای پیشین ما دربارهی مدیریت حافظه در مدلهای زبانی اشاره کردیم، هرگونه سربار اضافی در لایههای زیرین میتواند کارایی کل سیستم را به شدت کاهش دهد. Runloom این بازی را با یک «مونکی پچ» (monkey patch) تغییر میدهد؛ ابزاری که باعث میشود فراخوانیهای استاندارد و مسدودکننده — مثل urllib.request.urlopen — بهجای متوقف کردن کل رشتهی سیستمعامل، بهطور شفاف گوروتین (goroutine) را پارک کنند.
جزئیات پیادهسازی و معماری
این سیستم بهطور خاص برای پایتون ۳.۱۳t و ۳.۱۴t (در حالت GIL-off) طراحی شده است. برای دستیابی به بهرهوری در چندهستهای، Runloom از یک snapshot از PyThreadState برای هر گوروتین استفاده میکند که شامل cframe، پشته داده (datastack)، اطلاعات استثناها (exc_info)، متغیرهای محیطی (contextvars) و بازگشتها (recursion) است. این معماری تضمین میکند که میلیونها گوروتینِ رها شده بتوانند بدون برخورد با مشکل «پرتگاه زنجیره فریمها»، از رشتههای مرکزی (hub threads) مشترک شوند.
ساختار زمانبندی این سیستم از مدل M:N استفاده میکند. هر hub دارای یک صف Chase-Lev و یک سیستم ارسال MPSC است تا اطمینان حاصل شود گوروتینهای بیدار شده به hub مبدأ خود بازگردانده میشوند. همچنین مکانیسم ایزولاسیون و بازیابی تعبیه شده است؛ بنابراین اگر یک فراخوانی مسدودکننده غیرمنتظره باعث توقف یک hub شود، محیط اجرا آن را بهطور خودکار شناسایی و بازیابی میکند.
طبق گزارشهای فنی روی یک ماشین ۶۴ هستهای، Runloom در توان عملیاتی (Throughput) — یعنی مقدار دادهای که سیستم در واحد زمان پردازش میکند، شبیه به سرعت عبور خودروها از یک اتوبان در ساعت پیک — با زبان Go برابری کرده است. یافتههای این پروژه نشان میدهد نرخ ایجاد تسک در لایه C (c_entry) به ۲.۲۹ میلیون در ثانیه رسیده که حتی از ۲.۱۰ میلیونِ Go بیشتر است. در حالی که استفاده از API سطح پایتون (runloom.fiber) سرعت را به ۱.۳۵ میلیون میرساند (حدود ۰.۶۵ برابر Go).
در تستهای echo با هندلر پایتونی، Runloom به ۵۹۶ هزار درخواست در ثانیه دست یافت که تقریباً با ۶۰۳ هزار درخواستِ Go برابر است. نکته قابل توجه این است که با استفاده از هندلر C، این عدد حتی از Go پیشی گرفت.
مشخصات فنی دقیق
- تعویض زمینه: استفاده از اسمبلی دستنویس برای x86_64 SysV و aarch64 با تأخیر حدود ۸۰ نانوثانیه در هر تعویض (swap)؛ در ویندوز از Windows Fibers یا POSIX ucontext استفاده میشود. زمان رهاسازی (Yield) حدود ۷۵ نانوثانیه است که با Gosched در زبان Go برابری میکند.
- زمانبند: مدل M:N با استفاده از صفهای Chase-Lev برای هر hub. زمان رفت و برگشت (RT) کانالها حدود ۵۰ نانوثانیه است.
- ورودی/خروجی شبکه: پشتیبانی یکپارچه از netpoll شامل epoll، kqueue، IOCP، WSAPoll و select. گوروتینها از طریق مکانیسم سهحالته park-commit که فاقد مشکل lost-wake است، پارک میشوند.
- ردپای حافظه: یک فیبر پارکشده ۸.۸ کیلوبایت حافظه میگیرد که حدود ۳.۳ برابر Go (۲.۷ کیلوبایت) است؛ این شکاف بهدلیل وجود فریمهای ارزیابی CPython (eval frame) است.
- اتصالات: نرخ چرخش اتصال (ایجاد اتصال جدید برای هر درخواست) حدود ۷۵ تا ۷۸ هزار در ثانیه است که دقیقاً با Go مطابقت دارد.
باید توجه داشت که Runloom سرعت اجرای کد پایتون را در هر هسته افزایش نمیدهد؛ بلکه اجازه میدهد CPython تمام هستههای موجود را با مدل برنامهنویسی مسدودکنندهِ آشنا اشباع کند. سرعت ۸۰ هزار عملیات خالص پایتونی در هر ثانیه بهازای هر هسته ثابت میماند، اما اکنون یک پروسه میتواند همزمان روی تمام هستهها به این حد برسد.
این محیط اجرا همچنین یک پل برای کدهای قدیمی asyncio از طریق runloom.aio.run(main()) ارائه میدهد. این مسیر اجازه میدهد بدون بازنویسی کد، آن را منتقل کنید، هرچند که در این حالت روی یک زمانبند تکرشتهای اجرا شده و افزایش سرعت چندهستهای را فراهم نمیکند.
این تغییر به معنای حذف «مالیات async» برای اپلیکیشنهای پایتونی با همزمانی بالا است. با انتقال منطق زمانبندی به یک اکستنشن C و بهرهگیری از بیلدهای بدون GIL در پایتون ۳.۱۳t و ۳.۱۴t، این محیط اجرا مشکل پرتگاه زنجیره فریمها را که معمولاً در بازگشتهای عمیق یا میلیونها تسک رها شده رخ میدهد، از بین میبرد.
از دیدگاه عملی، این یعنی یک توسعهدهنده میتواند یک خزندهی وب (crawler) ساده را با استفاده از urllib بنویسد و شاهد مقیاسپذیری آن روی ۶۴ هسته باشد، بدون اینکه حتی یک کلمه await به کار ببرد. هزینه این کار، سربار حافظه بالاتر است که توسط eval frame پایتون ایجاد شده و اصلیترین تفاوت با زبانهای کامپایلشدهای مثل Go است.
کاربران در حال حاضر میتوانند wheelهای پیشساخته را برای لینوکس (x86_64/aarch64)، مک (arm64/x86_64) و ویندوز (AMD64) برای نسخههای پایتون ۳.۱۱ تا ۳.۱۴ نصب کنند. برای حداکثر عملکرد، تنظیم runloom.optimize("throughput") اولویت را به نرخ ایجاد تسک از طریق fiber_fast میدهد، در حالی که حالت runloom.optimize("memory") اندازه پشتهها را برای کاهش مصرف RSS بهینه میکند.
برای شروع کار با این سیستم، توسعهدهندگان کافی است تابع runloom.monkey.patch() را فراخوانی کنند تا کتابخانه استاندارد تعاونی شود و سپس توابع خود را در runloom.fiber() قرار دهند. نقطه ورود runloom.run(n, main) تعیین میکند که چه تعداد رشتهی مرکزی (hub threads) روی هستههای واقعی CPU ایجاد شوند.
گام بعدی شما
- اگر از پایتون ۳.۱۳t استفاده میکنید، کتابخانه Runloom را برای تست مقیاسپذیری تسکهای I/O-bound نصب کنید.
- برای بهینهسازی، بین حالت throughput (برای حداکثر نرخ ایجاد تسک) و حالت memory (برای کاهش مصرف RAM) یکی را انتخاب کنید.
- فراخوانی
runloom.monkey.patch()را در ابتدای برنامه قرار دهید تا کتابخانههای استاندارد بهطور تعاونی عمل کنند.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell و اثر آن بر استنتاج مدلها مراجعه کنید.




گفتگو