تصور کنید برنامهای مینویسید که هرگز برای پاکسازی حافظه متوقف نمیشود و هیچ «تأخیر ناگهانی» در پاسخدهی ندارد. این وعده، هستهٔ معماری جدید حافظه در زبان U است که مدیریت منابع را از یک فرآیند تصادفی به یک ساختار ریاضی قطعی تبدیل کرده است.
طبق مستندات منتشر شده در ۲۵ سپتامبر ۲۰۲۶ در وبسایت ulanguage.org، این سیستم با اجبار به ساختار مالکیت گراف جهتدار بدون دور (Directed Acyclic Graph یا DAG) — شبیه به یک نمودار سلسلهمراتبی که هرگز به عقب برنمیگردد — نیاز به جمعآوری زباله (Garbage Collection) را کاملاً حذف میکند. در این مدل، ارجاعات قوی فقط از والد به فرزند اشاره میکنند؛ بنابراین به محض اینکه شمارندهٔ ارجاع یک مالک به صفر برسد، تمام زیردرخت متصل به آن فوراً و بدون نیاز به جستوجو در کل حافظه، آزاد میشود.
این رویکرد در زمانی ارائه میشود که توسعهدهندگان با نوسانات تأخیر در جمعآوریکنندههای ردیابی (Tracing Collectors) دستوپنجه نرم میکنند. همانطور که در تحلیل قبلی ما دربارهی محدودیتهای پنجرههای متنی میلیونی اشاره کردیم، حافظهٔ موقت مدلها نمیتواند جایگزین سیستمهای حافظهٔ فیزیکی بهینه شود. سیستم حافظه U دقیقاً روی همین بهرهوری فیزیکی در محیطهای با کارایی بالا تمرکز کرده است.
در زبان U، حافظه دیگر یک تودهٔ عظیم (Heap) نیست که باید هر از گاهی اسکن شود، بلکه مجموعهای از زنجیرههای سازمانیافته است. هر مالک یک زنجیره اسلب (Slab Chain) دارد که تخصیص حافظه در آن صرفاً با جابهجایی یک اشارهگر (Bump Pointer) انجام میشود. وقتی یک اسلب پر شود، اسلب جدیدی با دو برابر اندازه قبلی متصل میشود تا تعداد اسلبها برای n بایت، حداکثر $\lceil\log_2(n/initial)\rceil$ باشد.
پایان توقفهای GC
به دلیل استفاده از ساختار DAG، زبان U بهطور کامل از مکانیزمهای Mark-Sweep و ردیابی فاصله میگیرد. به نقل از توسعهدهندگان این زبان، در این سیستم هیچ ردیابی، هیچ علامتگذاری و در نتیجه هیچ توقفی (Pause) وجود ندارد. هزینهٔ آزادسازی حافظه تنها متناسب با مقدار تخصیصشده توسط آن مالک خاص است، نه اندازه کل حافظه.
برای جلوگیری از ایجاد دورها (Cycles)، کامپایلر از الگوریتم SCC تارژان روی گراف ارجاعات نوع استفاده میکند تا هر برنامهای که در آن یک دور بدون حداقل یک ارجاع بازگشتی ضعیف وجود دارد را رد کند. این ارجاعات بازگشتی با +R(parent) علامتگذاری میشوند و به عنوان ارجاعات ضعیف شناخته میشوند؛ یعنی در شمارندهٔ ارجاع محاسبه نمیشوند و با مرگ مرجع، مقدار آنها به none تغییر میکند.
تخصیصکننده زنجیره اسلب
هر مالک در U برای مدیریت حافظه از یک زنجیره اسلب استفاده میکند. تخصیصها از طریق یک اشارهگر پیشرونده انجام میشود: سیستم اشارهگر را افزایش داده و آن را با انتهای اسلب فعلی مقایسه میکند. این سازوکار نیاز به پیمایش لیستهای آزاد (Free-list) برای هر شیء یا متادیتای پیچیده برای اشیاء محلی را از بین میبرد.
وقتی یک اسلب پر شود، اسلب جدیدی با اندازه دو برابر (مثلاً ۴ کیلوبایت $\rightarrow$ ۸ کیلوبایت $\rightarrow$ ۱۶ کیلوبایت) متصل میشود. هر اسلب یک تخصیص توان-دو است که اجازه میدهد تخصیصکنندههای سیستم آنها را بهطور بهینه مدیریت کنند. آزادسازی حافظه صرفاً با پیمایش زنجیره و آزاد کردن هر اسلب صورت میگیرد که برای تخصیصهای معمولی، هزینهٔ آن $O(\log n)$ و در عمل برای محدودههای رایج، ثابت است.
ساختارهای داده با کارایی بالا
زبان U برای کوچک نگه داشتن ردپای حافظه و افزایش سرعت دسترسی، مکانیزمهای تخصصی را پیاده کرده است:
- NaN-Boxing: مقادیر پویا در لیستها و نقشهها از یک نمایش ۸ بایتی تگدار استفاده میکنند. با استانداردسازی NaNهای اعداد اعشاری، U از فضای باقیمانده برای ذخیره اعداد صحیح کوچک، مقادیر بولی، اشارهگرها و
tombstonesاستفاده میکند. این کار نیاز به تخصیصهای جداگانه در Heap برای مقادیر نهایی را حذف میکند. - لیستهای پایدار (Stable Lists): لیستها از اسلبهای توان-دو پایدار استفاده میکنند. عناصر با رشد لیست جابهجا نمیشوند. اگر عنصری یک شاخص منطقی دریافت کند، افزودن عناصر بعدی باعث تغییر مکان عناصر قبلی نمیشود.
- دسترسی تصادفی $O(1)$: برای شاخص k، اسلب مربوطه با استفاده از بیتهای باارزش، دستور
clzو محاسبات ریاضی تعیین میشود. آدرس نهایی از طریق فرمولpointer + (k - base) * element_sizeمحاسبه میگردد. - پیمایش (Iteration): تکرارکنندهها اشارهگر اسلب فعلی و تعداد باقیمانده را نگه میدارند که باعث میشود سرعت پیمایش به پهنای باند حافظه متوالی نزدیک شود و از پیشخوانی (Prefetching) سختافزاری بهره ببرد.
- افزودن $O(1)$: افزودن در یک اسلب صرفاً یک جابهجایی اشارهگر است. در بدترین حالت، تخصیص یک اسلب جدید رخ میدهد که نسبت به طول لیست، هزینه آن $O(1)$ است.
- دسترسی تصادفی $O(1)$: برای شاخص k، اسلب مربوطه با استفاده از بیتهای باارزش، دستور
- نقشههای مرتب (Ordered Maps): برخلاف جداول هش سنتی، نقشه در U اساساً یک ذخیرهساز متراکم مرتب است که از دو لیست پایدار موازی (یکی برای کلیدها و یکی برای مقادیر) تشکیل شده است. رابطهٔ
شاخص درج i $\leftrightarrow$ keys[i] $\leftrightarrow$ values[i]همواره برقرار است.
بهینهسازی نقشه و منشأ داده
زبان U بر این اصل استوار است که باید تا حد امکان از جستوجوی معکوس (کلید $\rightarrow$ شاخص) اجتناب کرد. بسیاری از الگوها در PHP یا JavaScript، شاخص پایدار را از طریق تکرار یا کلیدهای ثابت کامپایلر فاش میکنند.
- منشأ تکرارکننده (Iterator Provenance): اگر کلیدی از یک حلقه
foreachبه دست آید، تکرارکننده از پیش شاخص پایدار را میداند. در نتیجه دسترسی$a[$b]مستقیماً بهa.values[bi]تبدیل میشود و مرحلهٔ حل (Resolver) را دور میزند. - کلیدهای ثابت کامپایلر: کلیدهای لیترال (مانند
"id") به نمادهای (Symbols) استاندارد تبدیل میشوند. پس از اولین شناسایی، دسترسیهای بعدی از یک شاخص کششده استفاده میکنند که با افزودن دادههای جدید باطل نمیشود. - اشکال مشترک (Shared Shapes): نقشههایی با تاریخچه درج یکسان، متادیتای نماد-به-شاخص مشترکی دارند. این موضوع برای اشیاء JSON و ردیفهای پایگاهداده حیاتی است.
معناشناسی کلیدهای شیء
اشیاء دلخواه میتوانند با تعریف __hash__() برای تولید یک مقدار H قطعی و __equals__(other) برای شناسایی، به عنوان کلید نقشه عمل کنند. هر دو متد بهطور پیشفرض -E-D (بدون اثر و قطعی) هستند.
به دلیل قطعی بودن، کامپایلر میتواند این متدها را در زمان کامپایل یا JIT اجرا کند. فرآیند حل به این صورت است: K $\rightarrow$ K.__hash__() $\rightarrow$ H $\rightarrow$ تولید حلکننده $\rightarrow$ شاخص پایدار کاندید i $\rightarrow$ keys[i].__equals__(K). از آنجا که هش ممکن است تداخل داشته باشد، __equals__ تطبیق نهایی را تأیید میکند.
سلسلهمراتب حلکننده (Resolver)
وقتی کامپایلر نتواند شاخص را از طریق منشأ یا اشکال پیدا کند، به مسیر پویا میرود. سیستم از Resolver<K>.probe(K) برای یافتن شاخصهای کاندید استفاده میکند. حلکننده در واقع متادیتای شتابدهنده است، نه خودِ ذخیرهساز نقشه.
حل پویا در دو مرحله:
۱. هشینگ: یک تابع کلید قطعی مقدار H را تولید میکند (مثلاً UTF-8 برای رشتهها یا ID برای نمادها).
۲. کاوش (Probing): حلکننده معکوس از H برای یافتن شاخص پایدار i استفاده کرده و آن را با keys[i] اعتبارسنجی میکند.
حلکنندههای معکوس نسلی
حل پویا توسط یک حلکننده معکوس نسلی مدیریت میشود. بهجای انتقال ورودیهای قدیمی هنگام رشد نقشه، حلکننده به نسلهای مختلف (G0, G1, G2 و ...) تقسیم میشود که بهصورت هندسی رشد میکنند (مثلاً ۸، ۳۲، ۱۲۸ و ...).
بسته به اندازه نسل، سیستم بهطور خودکار الگوریتم را تغییر میدهد:
- بسیار کوچک (۱–۸ عنصر): از اسکنهای SIMD بازشده استفاده میکند چون هزینه متاداتا بیشتر از خودِ جستوجو است.
- کوچک (۹–۶۴ عنصر): از اثرانگشتهای SIMD یا گروههای کوچک SwissTable استفاده میکند که معمولاً در حافظه L1 جای میگیرند.
- متوسط (۶۵–۴ هزار عنصر): از حلکننده مدل SwissTable استفاده میکند که در آن ذخیرهساز پراکنده فقط شامل متاداتا و شاخصهای پایدار است.
- بزرگ (۴ هزار–۶۴ هزار عنصر): از SwissTable یا حلکننده رادیکس فشرده استفاده میکند. برای رشتهها، ممکن است از درخت ART فشرده یا درخت Patricia استفاده شود.
- بسیار بزرگ (بیش از ۶۴ هزار عنصر): برای رشتهها، عملکرد ART فشرده را در برابر SwissTable میسنجد و برای کلیدهای دلخواه، حلکنندههای مبتنی بر هش فشرده را ترجیح میدهد.
مکانیزمهای پیشرفته حلکننده
فیلترهای عضویت: برای جلوگیری از کاوش در نسلهای سرد (Cold)، U از فیلترهای منفی فشرده (مانند Bloom یا XOR) استفاده میکند. نتیجه منفی یعنی کلید قطعاً در آن نسل نیست و سیستم از کاوش در آن نسل صرفنظر میکند.
جستوجوی خطلولهای (Pipelined): چون نسلهای حلکننده مستقل هستند، U میتواند درخواستهای بارگذاری را بهطور همزمان صادر کند. سیستم میتواند G0 تا G3 را همزمان کاوش کرده و جدیدترین شاخص معتبر را انتخاب کند.
Tombstones و درج مجدد: حذف دادهها باعث جابهجایی نمیشود، بلکه یک TOMBSTONE در جایگاه کلید نوشته میشود. برای سازگاری با معناشناسی PHP، درج مجدد یک کلید، آن را به انتهای لیست میبرد و شاخص جدیدی میدهد. این یعنی نسلهای قدیمی حلکننده نیازی به بهروزرسانی هنگام حذف ندارند.
فشردهسازی ذخیرهساز در برابر حلکننده
زبان U بین دو نوع فشردهسازی تفاوت قائل میشود:
- فشردهسازی ذخیرهساز: Tombstoneها را پاک کرده و ورودیها را بازشماری میکند. این کار یک نسل جدید ایجاد کرده و شاخصهای پایدار قبلی را باطل میکند.
- فشردهسازی حلکننده: متادیتای شاخص معکوس را سازماندهی میکند در حالی که
keys[]وvalues[]دستنخورده میمانند. در اینجا شاخصهای پایدار معتبر میمانند.
ایمنی حافظه و قابلیتها (Capabilities)
مدیریت حافظه در U با یک مدل امنیتی مبتنی بر قابلیت ادغام شده است. توابع بهطور پیشفرض قطعی و بدون اثر (-E-D) هستند. اگر تابعی نیاز به ایجاد اثر داشته باشد، باید صراحتاً +E (اثرات) یا +D (غیرقطعی) را اعلام کند و قابلیت لازم را از طریق پارامترها دریافت کند.
ارجاعات بازگشتی و رویدادها:
ارجاعات بازگشتی با +R(parent) علامتگذاری میشوند و در مالکیت قوی شرکت نمیکنند. آنها میتوانند قابلیتهای رویداد (مانند +E(RemoveEvent)) را حمل کنند. سیستم رویدادها از ارجاعات ضعیف برای هندلرها استفاده میکند تا ثبت رویداد باعث ایجاد دورهای مالکیت نشود.
برای حالتهای تغییرپذیر مشترک، U از کنترل همزمانی چندنسختی (MVCC) استفاده میکند. خواندنها یک نسخه ریشه را میگیرند و نوشتنها یک نسخه جدید تغییرناپذیر ساخته و ریشه را از طریق عملیات Compare-And-Swap (CAS) بهروز میکنند.
عملکرد و تبدیل کد (Transpilation)
جداسازی ذخیرهساز متراکم از متادیتای شتابدهنده به U اجازه میدهد حلکننده را بدون جابهجایی دادههای واقعی، دوباره هش یا فشرده کند.
این موضوع برای تبدیل کدهای PHP و JavaScript تحولآفرین است. در یک حلقه foreach در PHP، کامپایلر شاخص پایدار $id را حفظ میکند و دسترسی به آرایه را به یک دسترسی ساده تبدیل میکند. متغیرهای سراسری مانند $_GET به نماهای محلی درخواست (Request-local views) نگاشت میشوند.
یکپارچگی با سختافزار و GPU
زبان U برای سختافزارهای مدرن طراحی شده است. عملیات +V (برداریسازی) تبدیلهای حجیم را به حلقههای SIMD روی مناطق متوالی اسلب تبدیل میکند. برای اهداف GPU، یک زنجیره اسلب میتواند به یک نمایش متوالی در دستگاه تبدیل شود و نقشهها بهصورت ساختار-از-آرایهها (Structure-of-Arrays) منتقل شوند.
گام بعدی شما
- اگر با زبانهای پویا مثل PHP یا JS کار میکنید، مدل «شاخصهای پایدار» U را مطالعه کنید تا بفهمید چگونه میتوان بدون از دست دادن انعطافپذیری، به سرعت زبانهای کامپایلشده رسید.
- معماری DAG را در طراحی سیستمهای توزیعشده خود برای مدیریت چرخه حیات اشیاء بدون نیاز به GC بررسی کنید.
- مستندات
NaN-Boxingرا برای بهینهسازی مصرف حافظه در ساختارهای داده متراکم مطالعه کنید.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell و نحوه تعامل آنها با حافظههای خطلولهای مراجعه کنید.




گفتگو