Table of Contents
لماذا تختفي الذاكرة القياسية في قانون التميز العالي
وكل مبرمج يعتمد على و] لإدارة الذاكرة الدينامية، وهذه المهام ذات أغراض عامة، مصممة للعمل على مجموعة متنوعة من أنماط التوزيع، وأحجام الجسم، والعمر، وفي ظل غطاء الرأس، يتحكمون في الكاب، ويحتفظون بقوائم مجانية، ويحاصرون أحواض بحرية، ويتعاملون مع المواءمة، وهذه المرونة تأتي بتكلفة:
فبعد السرعة القصوى، يكون التجزؤ قاتلاً صامتاً، فمع مرور الوقت، يمكن أن ينشر مخصصات صغيرة عبر القاع، مما يترك ثغرات لا يمكن إعادة استخدامها بكفاءة، مما يؤدي إلى زيادة استخدام الذاكرة، وتباطؤ المخصصات في المستقبل، وتهدر دورات وحدة التصوير المقطعي، وتعالج أجهزة جمع الذاكرة في العالم بديلاً محدداً، وذوي المستوى المنخفض من حيث الحجم.
ويسير هذا الدليل في طريقكم إلى تصميم وتنفيذ مجمع للذاكرة ثابت الحجم في جيم. وستتعلمون كيف تهيكل المجمع، وتعالجون قضايا الحافة مثل الاستنفاد والمواءمة، وتمتد النمط إلى سيناريوهات متعددة الجدران، وفي النهاية سيكون لديكم أداة تقدم عمليات الذاكرة القريبة من الوقت وتلائم بشكل لا يطاق خطوط الأنابيب العالية الأداء.
مبادئ التصميم الأساسية لمجمع الذاكرة
وتمارس مجموعة من الذاكرات )تسمى أيضاً مشغلاً أو مجمعاً للوجه( فكرة بسيطة: تخصيص مجموعة كبيرة من الذاكرة المتاخمة، وتقسيمها إلى " فتحات " ثابتة، وإدارة أي فتحات بحرية باستخدام قائمة ذات صلة متبادلة، وعندما يطلب المستهلك الذاكرة، يعود المجمع إلى أول مكان من القائمة الحرة، وعندما يتم تحرير فتح فتح فتح فتح فتحه، لا يعاد إلى قائمة بالمجان.
Fixed —Size vs. Variable —Size Pools
وأكثر البدائل شيوعاً هي المجمع الثابت، حيث يكون كل فتحة بنفس الحجم، وهذا يطابق الهدف الذي يخدمه المجمع، على سبيل المثال، مجموعة من ] رموز، ويمكن أن تخصص مجموعات متنوعة من المعالم (تسمى أيضاً " الملوك الجوي " ) أجزاء من أحجام مختلفة، ولكنها تنطوي على تعقيدات: يجب أن تدير قائمة ثابتة من القطع المتفاوتة.
اعتبارات الإلغاء
ويحتاج وحدات الإزالة الحديثة إلى الوصول إلى الذاكرة أو يفضلها بشدة، وإذا كانت مخازن مجمعاتكم تحتوي على أنواع مثل ، أو ، أو أجهزة التحكم في الانبعاثات، يجب أن يضمن المجمع أن يبدأ كل فترة من فترات الاستراحة في عنوان متوائم مع أكبر متطلبات المواءمة من النوع المخزن.
السلامة
أما بالنسبة للتطبيقات ذات النسق الواحد، فلا حاجة إلى التزامن، غير أن العديد من نظم الإنتاج تتطلب الوصول المتزامن، فإضافة السلامة على الشاشة إلى المجمع أمر مباشر: حماية القائمة الحرة مع المتحول، أو استخدام قائمة خالية من القفل مع المقارنة الذرية والمبادلات، وسنقدم النسخة الأساسية الوحيدة المرتدة، ولكننا سنناقش نقاطاً للتركيز على البيئات المتعددة المستويات.
بناء مجمع تذكاري ثابت: خطوة خطوة
وسننفذ مجمعا يخزن أجساما ذات حجم تعسفي، وهو هيكل يحمل علامة على الذاكرة المأهولة، ورأس قائمة مجانية، وحجمها (المقرب إلى المواءمة)، ومجموع عدد الأماكن، والقائمة الحرة هي قائمة مترابطة متضمنة [(FLT:0]) داخل كل مكان خارجي حر: كل مخزن خارجي حر.
هياكل البيانات
#include <stddef.h>
#include <stdlib.h>
// Embedded free list node
typedef struct FreeNode {
struct FreeNode* next;
} FreeNode;
// Pool descriptor
typedef struct MemoryPool {
size_t slot_size; // Size of each slot (after alignment rounding)
size_t slot_count; // Number of slots in the pool
void* pool_start; // Start of the pre‑allocated memory block
FreeNode* free_list; // Head of the free list
} MemoryPool;
]]] يستخدم داخليا فقط؛ وتشغل الأجسام المخصصة نفس الذاكرة، وعندما يكون فتحة ما، تحتوي أول طرفياتها على نقطة أخرى، وعندما يخصص لها، يكتب مستخدم المجمع بياناته على تلك النقطة، ولهذا السبب يجب أن يكون حجمها على الأقل ] - وإلا لا يمكننا تخزين نقاط القائمة الحرة.
بدء التشغيل
ويخصص الاستهلالية مجموعة كبيرة من الذاكرة ويربط كل مكان في القائمة الحرة، ونجمع الحجم المطلوب إلى أقرب عدد من المواءمة (التي نختارها كـ ) وهذا يضمن مواءمة كل فترة، وبالتالي كل نقطة مرجعية أعيدت.
#include <stdint.h> // for max_align_t
int pool_init(MemoryPool* mp, size_t object_size, size_t object_count) {
// Round up object_size to the alignment of max_align_t
size_t alignment = _Alignof(max_align_t);
size_t aligned_size = (object_size + alignment - 1) & ~(alignment - 1);
// Ensure slot is large enough to hold a FreeNode pointer
if (aligned_size < sizeof(FreeNode))
aligned_size = sizeof(FreeNode);
mp->slot_size = aligned_size;
mp->slot_count = object_count;
// Allocate the contiguous pool memory
size_t total_size = aligned_size * object_count;
mp->pool_start = malloc(total_size);
if (mp->pool_start == NULL)
return -1; // allocation failure
// Build the free list
mp->free_list = (FreeNode*)mp->pool_start;
FreeNode* current = mp->free_list;
for (size_t i = 1; i < object_count; i++) {
current->next = (FreeNode*)((char*)mp->pool_start + i * aligned_size);
current = current->next;
}
current->next = NULL;
return 0;
}
نستخدم الذي يعطي أقصى ضمان للمواءمة يتطلبه .() وبالنسبة لمعظم المنصات، فإن هذه 8 أو 16 بوصة، وهي أعمال الخدعة المُقَدَّمة من أجل مواءمة قوّة اثنين، وهذا يضمن أن كل مُنْصِف أعيد آمن للاستخدام بأي نوع عادي.
المخصصات
وتفتح المقسمة على رأس القائمة الحرة وتعيدها، وإذا كانت القائمة الحرة فارغة، فإن المسبح مستنفد ونحن نعود .]
void* pool_alloc(MemoryPool* mp) {
if (mp->free_list == NULL) {
return NULL; // pool exhausted
}
FreeNode* block = mp->free_list;
mp->free_list = block->next;
return (void*)block;
}
هذا (أو) و ينفذ في حفنة من التعليمات لا أقفال ولا مكالمات نظامية
تحرير سلة
ويدفع التجميد إلى فتحة التسجيل في القائمة الحرة، ويجب على المتصل أن يكفل أن يكون المرسل ملكا لهذه المجموعة (سنناقش التصديق فيما بعد).
void pool_free(MemoryPool* mp, void* ptr) {
if (ptr == NULL) return; // standard behavior like free(NULL)
FreeNode* node = (FreeNode*)ptr;
node->next = mp->free_list;
mp->free_list = node;
}
مرة أخرى (أو (1)) لا تختلط ولا تدمج، وتصبح القطعة المحررة متاحة فوراً لإعادة استخدامها.
تدمير الصوف
وعندما لا تكون هناك حاجة إلى المجمع، تُحرر التوزيع الأساسي.
void pool_destroy(MemoryPool* mp) {
free(mp->pool_start);
mp->pool_start = NULL;
mp->free_list = NULL;
mp->slot_count = 0;
mp->slot_size = 0;
}
دائماً ما يُطلق عليه قبل أن يُخرج هيكل المجمع من نطاقه لتجنب تسرب الذاكرة.
مثال على الاستخدام
وهنا مثال كامل يخلق مجموعة من الـ 1024 من الطوابق المبتذلة، ويخصص واحدة منها، ويكتب قيمة، ويقرأها، ويحررها.
#include <stdio.h>
#include <assert.h>
int main(void) {
MemoryPool pool;
if (pool_init(&pool, sizeof(int), 1024) != 0) {
fprintf(stderr, "Pool initialization failed\n");
return 1;
}
int* p = (int*)pool_alloc(&pool);
if (p == NULL) {
fprintf(stderr, "Pool exhausted\n");
return 1;
}
*p = 42;
printf("Value: %d\n", *p);
pool_free(&pool, p);
pool_destroy(&pool);
return 0;
}
وفي طلب حقيقي، ستخصصين مجمعاً لكل نوع من أنواع الأشياء تحتاج إلى إدارته، فعلى سبيل المثال، قد يكون لخدم الشبكة و.
النظر في الطلبات المقدمة وتمديدها
رصد المخصصات من أجل تخفيف عبء الديون
ولا تتبع المجموعة الأساسية أي فتحات مخصصة حالياً، فبالنسبة للتزيين، قد تضيف حقلاً أو قائمة منفصلة من القطع المخصصة، مما يسمح لك بكشف الاختراق المزدوج أو التسربات، وفي الإنتاج، عادة ما يتم تجنب التعقب العام - فالطبيعة المحددة للمجمعات تجعل الحشرات أسهل من العثور عليها من خلال تسمم الذاكرة.
تسمم الذاكرة
وعندما يتم تحرير فتحة، يمكنك أن تُلغي محتوياتها بنمط معروف (مثلاً، ]) للكشف عن الاستخدام المطلق، وبالمثل، عندما تُخصّص، قد تملأ الفراغ بنمط للإمساك بقراءات غير مبدئية، ويضيف التسمم تكلفة ثابتة صغيرة ولكن يمكن أن ينقذ ساعات من التضليل.
إحصاءات الصوف المصدرة
وبالنسبة لضبط الأداء، فإن مواجهة مثل مجموع المخصصات، والمجانية الكاملة، والعد الحر الحالي، والطريقة البسيطة هي الحفاظ على حقل في هيكل المجمع، والتآكل على جميع الأعلام، والزيادة مجانا، وهذا يساعد أيضا على اكتشاف الانهيار دون فحص.
// Add to MemoryPool: size_t free_count;
// In pool_alloc: if (mp->free_list) { mp->free_count--; ... }
// In pool_free: mp->free_count++; ...
حمقى الصوفية
للحصول على نفس الرخصة، لف جميع الحقائب والمهام الحرة مع المتحول:
#include <pthread.h>
typedef struct ThreadSafePool {
MemoryPool pool;
pthread_mutex_t lock;
} ThreadSafePool;
void* ts_pool_alloc(ThreadSafePool* tsp) {
pthread_mutex_lock(&tsp->lock);
void* ptr = pool_alloc(&tsp->pool);
pthread_mutex_unlock(&tsp->lock);
return ptr;
}
void ts_pool_free(ThreadSafePool* tsp, void* ptr) {
pthread_mutex_lock(&tsp->lock);
pool_free(&tsp->pool, ptr);
pthread_mutex_unlock(&tsp->lock);
}
وللاستفسار عن الزعم، النظر في قائمة مجانية خالية من القفل باستخدام ، غير أن ذلك يتطلب معالجة مشكلة ABA - وهي تحد كلاسيكي ورد وصفه في العديد من الكتب المدرسية للتوافق، وبالنسبة لمعظم التطبيقات، فإن مجمعات الخيوط ذات النسق الواحد هي أبسط وأحسن حجما.
زراعة الصوف ديناميكيا
لا يمكن أن تنمو صناديق تجميعية ثابتة بمجرد أن تبدئ، إذا كنت بحاجة إلى بركة يمكنها التوسع، يمكنك الاحتفاظ بمجموعة من القطيع، وعندما يستنفد أحد القطيع، يخصص قطعة جديدة (من نفس الحجم) ويضيف فتحاتها إلى القائمة الحرة، ويبقى المشغل (أو (1)) دائما تقريبا، ولكن يجب أن تدير فصائل متعددة أثناء التدمير.
مؤشرات الأداء (المفهوم)
وفي علامة قياسية نموذجية على وحدة حديثة من نوع X86 - 64 وحدة من وحدات المستهلكين، يستغرق كلفة مجمعة/مجانية 15-30 ثانية، بينما /] بالنسبة لـ 32 جسماً من نوعه يمكن أن يستغرق 80 - 200 ثانية من النانو بسبب القفل والقابلية للاختراق، وفي التطبيقات الحقيقية، كثيراً ما يحسن التحسن في حجم العمل المكاني.
الشلالات المشتركة وكيفية تجنبها
- Mixing pool sizes:] never free a pointer that belong to a different pool (or to ) with . The result is undefined behavior. Consider storing a pool identifier in each slot for extra safety in debug builds.
- Alignment mismatch:] If you store types with unusual alignment requirements (e.g., ]), ensure your slot alignment is sufficient. The ]]] covers all standard types but may not cover SIMD types. Round up to an explicit 16 or 32 bytes if needed.
- Forgetting to call :] The underlying is never freed if you abandon destruction. Use RAII wrappers or a clear cleanup pattern.
- Using the pool for changing —sized allocations:] If you need objects of different sizes, create separate pools. trying to fit changing sizes into a fixed —size pool wastes memory or causes truncation.
السياق العالمي الحقيقي والقراءة الإضافية
تجمعات الذاكرة العرفية ليست فكرة جديدة، إنها تظهر في كل نظام من الأجهزة ذات الأداء العالي تقريباً:
- The Linux kernel uses slab allocators] for object caches (see the ] interface).
- Game motors like Unreal Engine and ] Godot] provide builtin pool allocators for actors and particles.
- (ه) استخدام مجمعات الذاكرة لأجهزة التعبئة لضمان عدم تخصيصها على المسار السريع.
- The Apache APR] library includes a pool API used by Apache HTTP Server.
For deeper study, read about The GNU C library’s malloc implementation] to understand what you are avoid, and examine the ]kernel slaocator documentation for design inspiration. The book ]Programming with POSIX Threads[FL
خاتمة
إن جميع الملوك في مجمع الذاكرة العرفية هي عملية ذات أثر عال بالنسبة للتطبيقات التي تدير العديد من الأشياء الصغيرة القصيرة الأجل، والتنفيذ في إطار نظام " جيم " هو أقل من 50 خطاً من الشفرة المصممة جيداً، بحيث يزيل التجزؤ، وفوائد الكاشيات، والرأس الإضافي لجميع الملوك في الأغراض العامة، ويفهم حجم العمل المتناثر (الحجم المحدد)