پرش به محتوای اصلی
پرش به محتوای مقاله

زبان U: دستیابی به عملکرد O(1) در مدیریت حافظه

·۴ مهر ۱۴۰۵۱۷ دقیقه مطالعه
سیستم حافظه U: معماری نوین مدیریت حافظه با ساختار سلسله‌مراتبی و الگوریتم جایگزینی بهینه برای پردازش داده‌های حجیم.
سیستم حافظه U: معماری نوین مدیریت حافظه با ساختار سلسله‌مراتبی و الگوریتم جایگزینی بهینه برای پردازش داده‌های حجیم.
اشتراک‌گذاری
واقعاً چه چیز جدید است؟

جایگزینی کامل Garbage Collection با مدل مالکیت DAG و استفاده از زنجیره اسلب برای دستیابی به تخصیص حافظه با هزینه $O(1)$ در زبان‌های پویا؛ رویکردی که برخلاف مدل‌های سنتی، هزینه آزادسازی را به اندازه کل Heap گره نمی‌زند.

تصور کنید برنامه‌ای می‌نویسید که هرگز برای پاک‌سازی حافظه متوقف نمی‌شود و هیچ «تأخیر ناگهانی» در پاسخ‌دهی ندارد. این وعده، هستهٔ معماری جدید حافظه در زبان 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)$ است.
  • نقشه‌های مرتب (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 و نحوه تعامل آن‌ها با حافظه‌های خط‌لوله‌ای مراجعه کنید.

چرا این موضوع مهم است؟

این معماری با حذف توقف‌های Garbage Collection، استانداردهای جدیدی برای پیش‌بینی‌پذیری تأخیر (Latency) در زبان‌های پویا تعریف می‌کند. تخصص تیم U در ترکیب مدل‌های DAG و تخصیص‌های توان-دو، راه را برای اجرای برنامه‌های سطح بالا روی سخت‌افزارهای حساس به زمان و GPUها هموار می‌کند.

تأثیر برای ایران

این معماری بیشتر برای توسعه‌دهندگان ابزارهای زیرساختی و کامپایلر در ایران اهمیت دارد. در حال حاضر اثر مستقیمی بر کاربران نهایی یا بازار مصرف داخلی ندارد.

·نگاه ما
تحریریه دات‌هوش

جدا کردن لایه ذخیره‌سازی (Slabs) از لایه شتاب‌دهنده (Resolvers) یک چرخش هوشمندانه است؛ این یعنی مدل‌های داده می‌توانند بدون جابه‌جایی فیزیکی در حافظه، نحوه دسترسی به خود را بهینه‌ کنند. این رویکرد در واقع «پویا بودن» زبان‌های سطح بالا را با «پیش‌بینی‌پذیری» زبان‌های سیستمی آشتی می‌دهد و نشان می‌دهد که حذف GC لزوماً به معنای سخت‌گیرانه کردن مدیریت حافظه برای برنامه‌نویس نیست، بلکه می‌تواند نتیجهٔ یک طراحی ریاضی درست در سطح کامپایلر باشد.

منابع

این گزارش با خط‌لولهٔ خودکار دات‌هوش از منابع معتبر جهانی تدوین و زیر نظر تحریریه منتشر شده است. روش کار ما

گفتگو

پنج‌شنبه‌های هوش‌محور

بسته‌ی هفتگی دات‌هوش

۵ خبر، ۲ ابزار، ۱ پرامپت در هر شماره. به‌زودی راه‌اندازی می‌شود — هر پنج‌شنبه صبح.

خبر کلیدی
ابزار کاربردی
پرامپت حرفه‌ای
تحلیل پژوهش
به‌زودی
زاویه‌ی ایرانی
به‌زودی
تمرین این هفته
به‌زودی

راهنماهای دات‌هوش

راهنماهای کاربردیِ دات‌هوش برای کار با هوش مصنوعی — از همین‌جا شروع کنید:

دات‌هوش

راهنمای فارسی هوش مصنوعی — با نگاه به ایران

اخبار روزانه، معرفی ابزارها و مدل‌ها، و آموزشِ کار با هوش مصنوعی؛ همیشه با این پرسش که از ایران چه چیزی کار می‌کند و چه چیزی نه.