مرحبا يا هبر! سأحاول في هذه المقالة معرفة إدارة الذاكرة في البرامج / التطبيقات من وجهة نظر مبرمج التطبيق. هذا ليس دليلاً أو دليلاً شاملاً ، ولكن ببساطة لمحة عامة عن المشكلات الحالية وبعض الطرق لحلها.
لماذا هذا ضروري؟ البرنامج عبارة عن سلسلة من إرشادات معالجة البيانات (في الحالة العامة). يجب تخزين هذه البيانات وتحميلها ونقلها وما إلى ذلك بطريقة ما. لا تحدث جميع هذه العمليات على الفور ، وبالتالي ، فهي تؤثر بشكل مباشر على سرعة التطبيق النهائي. ستتيح لك القدرة على إدارة البيانات على النحو الأمثل في عملية العمل إنشاء برامج غير تافهة للغاية وتتطلب موارد كبيرة.
ملاحظة: يتم تقديم الجزء الأكبر من المواد مع أمثلة من محركات الألعاب / الألعاب (نظرًا لأن هذا الموضوع أكثر إثارة للاهتمام بالنسبة لي شخصيًا) ، ومع ذلك ، يمكن تطبيق معظم المواد على خوادم الكتابة وتطبيقات المستخدم وحزم الرسومات ، إلخ.

من المستحيل أن تضع كل شيء في الاعتبار. ولكن إذا لم تتمكن من تحميله ، فستحصل على الصابون
الحق قبالة الخفافيش
لقد حدث في هذه الصناعة أن مشاريع ألعاب AAA الكبيرة يتم تطويرها بشكل أساسي على محركات مكتوبة باستخدام C ++. إحدى ميزات هذه اللغة هي الحاجة إلى الإدارة اليدوية للذاكرة. جافا / جيم # الخ يتباهون بجمع البيانات المهملة (GarbageCollection / GC) - القدرة على إنشاء كائنات ومع ذلك لا يتم تحرير الذاكرة المستخدمة يدويًا. تعمل هذه العملية على تبسيط عملية التطوير وتسريعها ، ولكنها قد تسبب أيضًا بعض المشكلات: يمكن لمجمع مجمعات البيانات المهملة بشكل دوري أن يقتل كل الوقت الفعلي ويضيف تجميدًا غير سارٍ إلى اللعبة.
نعم ، في مشاريع مثل "Minecraft" ، قد لا يكون GC ملحوظًا بشكل عام لا يطلبون موارد جهاز الكمبيوتر ، ولكن الألعاب مثل "Red Dead Redemption 2" و "God of War" و "Last of Us" تعمل "تقريبًا" في ذروة أداء النظام وبالتالي لا تحتاج فقط إلى حجم كبير كمية الموارد ، ولكن أيضا في توزيع المختصة.
بالإضافة إلى ذلك ، في بيئة ذات تخصيص تلقائي للذاكرة وجمع البيانات المهملة ، قد تواجه نقصًا في المرونة في إدارة الموارد. ليس سراً أن Java تخفي كل تفاصيل التنفيذ وجوانب عملها تحت الغطاء ، لذلك في المخرجات لديك فقط الواجهة المثبتة للتفاعل مع موارد النظام ، ولكن قد لا يكون ذلك كافياً لحل بعض المشاكل. على سبيل المثال ، يؤدي بدء الخوارزمية بعدد غير ثابت من تخصيصات الذاكرة في كل إطار (يمكن أن يكون هذا البحث عن مسارات لـ AI ، والتحقق من الرؤية ، والرسوم المتحركة ، وما إلى ذلك) يؤدي حتما إلى انخفاض كارثي في الأداء.
كيف تبدو التخصيصات في الكود
قبل متابعة المناقشة ، أود أن أوضح كيف يحدث العمل مع الذاكرة في C / C ++ مباشرةً مع بضعة أمثلة. بشكل عام ، يتم تمثيل الواجهة القياسية والأبسط لتخصيص ذاكرة العملية بالعمليات التالية:
// size void* malloc(size_t size); // p void free(void* p);
يمكنك هنا إضافة وظائف إضافية تسمح لك بتخصيص جزء من الذاكرة محاذي:
// C11 - , * alignment void* aligned_alloc(size_t size, size_t alignment); // Posix - // address (*address = allocated_mem_p) int posix_memalign(void** address, size_t alignment, size_t size);
يرجى ملاحظة أن المنصات المختلفة قد تدعم معايير وظائف مختلفة ، متوفرة على سبيل المثال على نظام ماكنتوش وغير متوفرة في حالة فوز.
إذا نظرنا إلى الأمام ، فقد تحتاج إلى مساحات ذاكرة خاصة قد تحتاجها للوصول إلى خطوط ذاكرة التخزين المؤقت للمعالج وللحسابات باستخدام مجموعة موسعة من السجلات ( SSE ، MMX ، AVX ، إلخ).
مثال على برنامج لعبة يخصص الذاكرة ويطبع قيم المخزن المؤقت ، وتفسيرها على أنها أعداد صحيحة موقعة:
/* main.cpp */ #include <cstdio> #include <cstdlib> int main(int argc, char** argv) { const int N = 10; int* buffer = (int*) malloc(sizeof(int) * N); for(int i = 0; i < N; i++) { printf("%i ", buffer[i]); } free(buffer); return 0; }
على نظام التشغيل MacOS 10.14 ، يمكن بناء هذا البرنامج وتشغيله باستخدام مجموعة الأوامر التالية:
$ clang++ main.cpp -o main $ ./main
ملاحظة: فيما يلي ، لا أريد حقًا تغطية عمليات C ++ مثل الجديد / الحذف ، حيث من المرجح أن تستخدم لبناء / تدمير الكائنات مباشرة ، لكنها تستخدم العمليات المعتادة مع ذاكرة مثل malloc / free.
مشاكل الذاكرة
هناك العديد من المشكلات التي تنشأ عند العمل مع ذاكرة الوصول العشوائي للكمبيوتر. جميعها ، بطريقة أو بأخرى ، لا تنتج فقط عن ميزات نظام التشغيل والبرامج ، ولكن أيضًا بسبب بنية الحديد التي تعمل عليها كل هذه الأشياء.
1. مقدار الذاكرة
لسوء الحظ ، الذاكرة محدودة جسديا. على PlayStation 4 ، هذا هو 8 GiB GDDR5 ، 3.5 GiB الذي يحتفظ نظام التشغيل باحتياجاته . لن تساعد الذاكرة الافتراضية ومبادلة الصفحات كثيرًا ، لأن تبديل الصفحات إلى القرص يعد عملية بطيئة جدًا (داخل إطارات N ثابتة في الثانية ، إذا تحدثنا عن الألعاب).
كما تجدر الإشارة إلى " الميزانية " المحدودة - بعض القيود المصطنعة على مقدار الذاكرة المستخدمة ، التي تم إنشاؤها من أجل تشغيل التطبيق على العديد من المنصات. إذا كنت تقوم بإنشاء لعبة لمنصة متنقلة وترغب في دعم ليس فقط واحدة ، ولكن أيضًا مجموعة كاملة من الأجهزة ، فسوف يتعين عليك الحد من شهيتك من أجل توفير سوق مبيعات أوسع. يمكن تحقيق ذلك من خلال الحد من استهلاك ذاكرة الوصول العشوائي (RAM) ، والقدرة على تكوين هذا التقييد اعتمادًا على الأداة التي تبدأ اللعبة فعليًا.
2. تجزئة
تأثير غير سارة يظهر أثناء عملية تخصيصات متعددة لقطع الذاكرة من مختلف الأحجام. نتيجة لذلك ، تحصل على مساحة عنوان مجزأة في العديد من الأجزاء المنفصلة. لن يعمل دمج هذه الأجزاء في كتل مفردة ذات حجم أكبر ، لأن جزءًا من الذاكرة مشغول ، ولا يمكننا تحريكه بحرية.

تجزئة بواسطة مثال عمليات تخصيص متسلسلة وإصدارات كتل الذاكرة
نتيجة لذلك: يمكن أن يكون لدينا ذاكرة حرة كافية ، ولكن ليس نوعيًا. والطلب التالي ، على سبيل المثال ، "تخصيص مساحة للمسار الصوتي" ، لن يتمكن المُخصص من الإرضاء ، لأنه ببساطة لا توجد ذاكرة واحدة بهذا الحجم.
3. ذاكرة التخزين المؤقت وحدة المعالجة المركزية

التسلسل الهرمي لذاكرة الكمبيوتر
ذاكرة التخزين المؤقت للمعالج الحديث عبارة عن رابط وسيط يربط الذاكرة الرئيسية (RAM) ويقوم المعالج بتسجيلها مباشرة. لقد حدث أن وصول القراءة / الكتابة إلى الذاكرة هو عملية بطيئة جدًا (إذا تحدثنا عن عدد دورات ساعة وحدة المعالجة المركزية (CPU) المطلوبة للتنفيذ). لذلك ، هناك بعض التسلسل الهرمي للتخزين المؤقت (L1 ، L2 ، L3 ، وما إلى ذلك) ، والذي يسمح ، كما كان ، "لتحميل بعض البيانات من ذاكرة الوصول العشوائي" ، أو دفعها ببطء إلى ذاكرة أبطأ.
يسمح لك وضع كائنات من نفس النوع في صف في الذاكرة بتسريع عملية معالجتها (بشكل ملحوظ) (في حالة حدوث المعالجة بالتسلسل) ، لأنه في هذه الحالة يكون من السهل التنبؤ بالبيانات المطلوبة بعد ذلك. ويعني "كبير" مكاسب الإنتاجية في بعض الأحيان. لقد تحدث مطورو محرك Unity مرارًا وتكرارًا حول هذا الأمر في تقاريرهم في GDC .
4. موضوع خيوط
يعد توفير وصول آمن إلى الذاكرة المشتركة في بيئة متعددة الخيوط إحدى المشكلات الرئيسية التي سيتعين عليك حلها عند إنشاء محرك / لعبة / أي تطبيق آخر يستخدم مؤشرات ترابط متعددة لتحقيق أداء أفضل. يتم ترتيب أجهزة الكمبيوتر الحديثة بطريقة غير تافهة للغاية. لدينا كل من بنية ذاكرة التخزين المؤقت المعقدة والعديد من النوى الحاسبة. كل هذا ، إذا تم استخدامه بشكل غير صحيح ، يمكن أن يؤدي إلى مواقف عندما تتضرر البيانات المشتركة الخاصة بالعملية نتيجة للعديد من مؤشرات الترابط (إذا حاولوا في وقت واحد العمل مع هذه البيانات دون التحكم في الوصول). في أبسط الحالات ، سيبدو كما يلي:

لا أريد الخوض في موضوع البرمجة متعددة الخيوط ، حيث أن العديد من جوانبها تتجاوز نطاق المقالة أو حتى الكتاب بأكمله.
5. Malloc / مجانا
عمليات التخصيص / الإطلاق لا تحدث على الفور. في أنظمة التشغيل الحديثة ، إذا كنا نتحدث عن Windows / Linux / MacOS ، يتم تنفيذها جيدًا وتعمل بسرعة في معظم الحالات . لكن من المحتمل أن هذه عملية تستغرق وقتًا طويلاً للغاية. لا يقتصر الأمر على إجراء مكالمة النظام هذه ، ولكن اعتمادًا على التطبيق ، قد يستغرق الأمر بعض الوقت للعثور على قطعة ذاكرة مناسبة (First Fit أو Best fit أو ما إلى ذلك) أو للعثور على مكان لإدراج و / أو دمج المنطقة المحررة.
بالإضافة إلى ذلك ، قد لا يتم بالفعل تعيين الذاكرة المخصصة حديثًا على الصفحات الفعلية الحقيقية ، والتي قد تستغرق أيضًا بعض الوقت عند الوصول الأول.
هذه هي تفاصيل التنفيذ ، ولكن ماذا عن قابلية التطبيق؟ ليس لدى Malloc / new أي فكرة عن المكان أو كيف أو لماذا اتصلت بهم. يخصصون الذاكرة (في أسوأ الحالات) 1 كيلوبايت و 100 ميغا بايت على قدم المساواة ... سيئة على حد سواء. مباشرة ، يتم ترك استراتيجية الاستخدام إما للمبرمج أو الشخص الذي قام بتنفيذ وقت تشغيل البرنامج.
6. تلف الذاكرة
كما تقول الويكي ، هذا واحد من أكثر الأخطاء التي لا يمكن التنبؤ بها والتي تظهر فقط أثناء سير البرنامج ، وغالبًا ما يكون سببها مباشرًا أخطاء في كتابة هذا البرنامج. لكن ما هذه المشكلة؟ لحسن الحظ (أو لسوء الحظ) ، لا يرتبط بفساد جهاز الكمبيوتر الخاص بك. بدلاً من ذلك ، فإنه يعرض موقفًا تحاول فيه استخدام ذاكرة لا تخصك . سأشرح الآن:
- قد تكون هذه محاولة للقراءة / الكتابة إلى جزء من الذاكرة غير المخصصة.
- تجاوز حدود كتلة الذاكرة المقدمة لك. هذه المشكلة هي نوع من الحالات الخاصة للمشكلة (1) ، لكنها أسوأ لأن النظام سيخبرك بأنك تجاوزت الحدود فقط عندما تترك الصفحة معروضة لك. هذا هو ، من المحتمل ، أن تكون هذه المشكلة صعبة للغاية ، لأن نظام التشغيل قادر على الاستجابة فقط إذا تركت حدود الصفحات الافتراضية معروضة لك. يمكنك إفساد ذاكرة العملية والحصول على خطأ غريب جدًا من المكان الذي لم يكن متوقعًا منه على الإطلاق.
- تحرير ذاكرة تم تحريرها بالفعل (تبدو غريبة) أو لم يتم تخصيصها بعد
- إلخ
في C / C ++ ، حيث يوجد حساب مؤشر ، سوف تصادف هذا واحد أو مرتين. ومع ذلك ، في Java Runtime ، يجب عليك التعرق بشدة للحصول على هذا النوع من الأخطاء (لم أحاول ذلك بنفسي ، لكنني أعتقد أن هذا ممكن ، وإلا ستكون الحياة بسيطة للغاية).
7. تسرب الذاكرة
إنها حالة خاصة لمشكلة أكثر عمومية تحدث في العديد من لغات البرمجة. توفر مكتبة C / C ++ القياسية الوصول إلى موارد نظام التشغيل. يمكن أن يكون ملفات ، مآخذ ، ذاكرة ، إلخ. بعد الاستخدام ، يجب إغلاق المورد بشكل صحيح و
يجب تحرير الذاكرة التي يحتلها. وإذا تحدثنا بالتحديد عن تحرير الذاكرة - فقد تؤدي التسريبات المتراكمة نتيجة البرنامج إلى حدوث خطأ "نفاد الذاكرة" عندما يتعذر على نظام التشغيل تلبية الطلب التالي للتخصيص. غالبًا ما ينسى المطور ببساطة تحرير الذاكرة المستخدمة لسبب أو لآخر.
من الجدير إضافة حول الإغلاق الصحيح وإصدار الموارد على وحدة معالجة الرسومات ، لأن برامج التشغيل الأولى لم تسمح باستئناف العمل مع بطاقة الفيديو إذا لم تكن الجلسة السابقة قد اكتملت بشكل صحيح. يمكن فقط لإعادة تشغيل النظام حل هذه المشكلة ، وهو أمر مشكوك فيه للغاية - لإجبار المستخدم على إعادة تشغيل النظام بعد تشغيل التطبيق الخاص بك.
8. مؤشر التعلق
مؤشر التعلق هو بعض المصطلحات التي تصف موقفًا يشير فيه المؤشر إلى قيمة غير صالحة. يمكن أن تنشأ حالة مماثلة بسهولة عند استخدام مؤشرات النمط الكلاسيكي C في برنامج C / C ++. افترض أنك قمت بتخصيص الذاكرة ، وحفظت العنوان في مؤشر p ، ثم حرر الذاكرة (انظر مثال الكود):
// void* p = malloc(size); // ... - // free(p); // p? // *p == ?
يخزن المؤشر بعض القيمة ، والتي يمكننا تفسيرها كعنوان كتلة الذاكرة. لقد حدث أن لا يمكننا القول ما إذا كانت كتلة الذاكرة هذه صالحة أم لا. فقط المبرمج ، بناءً على اتفاقيات معينة ، يمكنه العمل بمؤشر. بدءًا من الإصدار C ++ 11 ، تم إدخال عدد من مؤشرات "المؤشرات الذكية" الإضافية في المكتبة القياسية ، والتي تتيح في بعض النواحي إضعاف التحكم في الموارد بواسطة المبرمج باستخدام معلومات تعريف إضافية داخل أنفسهم (المزيد حول هذا لاحقًا).
كحل جزئي ، يمكنك استخدام القيمة الخاصة للمؤشر ، مما سيشيرنا إلى أنه لا يوجد شيء في هذا العنوان. في C ، يتم استخدام الماكرو NULL كقيمة لهذه القيمة ، وفي C ++ ، يتم استخدام الكلمة الأساسية لغة nullptr. الحل جزئي لأن:
- يجب تعيين قيمة المؤشر يدويًا ، لذلك يمكن للمبرمج أن ينسى القيام بذلك.
- يتم تضمين nullptr أو 0x0 فقط في مجموعة القيم التي يقبلها المؤشر ، وهو أمر غير جيد عندما يتم التعبير عن الحالة الخاصة لكائن من خلال حالته المعتادة. هذا نوع من الإرث ، وبالاتفاق ، لن يقوم نظام التشغيل بتخصيص جزء من الذاكرة يبدأ عنوانه بـ 0x0.
رمز عينة مع فارغة:
// - p free(p); p = nullptr; // p == nullptr ,
يمكنك أتمتة هذه العملية إلى حد ما:
void _free(void* &p) { free(p); p = nullptr; } // - p _free(p); // p == nullptr, //
9. نوع الذاكرة
ذاكرة الوصول العشوائي هي ذاكرة وصول عشوائي للأغراض العامة عادية ، يمكن الوصول إليها من خلال الناقل المركزي من خلال جميع مراكز المعالج والأجهزة الطرفية الخاصة بك. يختلف حجمها ، لكن في أغلب الأحيان نتحدث عن N غيغا بايت ، حيث N هي 1،2،4،8،16 وهكذا. المكالمات malloc / free تسعى لوضع كتلة الذاكرة التي تريدها مباشرة في ذاكرة الوصول العشوائي للكمبيوتر.
VRAM (ذاكرة الفيديو) - ذاكرة فيديو ، مزودة ببطاقة الفيديو / مسرّع الفيديو بجهاز الكمبيوتر. وكقاعدة عامة ، أصغر من ذاكرة الوصول العشوائي (حوالي 1.2.4 جيجا بايت) ، ولكن لديه سرعة عالية. تتم معالجة توزيع هذا النوع من الذاكرة بواسطة برنامج تشغيل بطاقة الفيديو ، وغالبًا ما لا يكون لديك وصول مباشر إليه.
لا يوجد مثل هذا الفصل على PlayStation 4 ، ويمثل كل ذاكرة الوصول العشوائي 8 غيغابايت واحدة على GDDR5. لذلك ، جميع البيانات لكل من المعالج ومسرع الفيديو في مكان قريب.
تتضمن الإدارة الجيدة للموارد في محرك اللعبة تخصيص ذاكرة مختصًا في ذاكرة الوصول العشوائي الرئيسية وعلى جانب VRAM. قد تواجه هنا ازدواجية عند وجود نفس البيانات هناك ، أو مع نقل مفرط للبيانات من ذاكرة الوصول العشوائي إلى VRAM والعكس بالعكس.
كتوضيح لجميع المشاكل التي تم التعبير عنها : يمكنك إلقاء نظرة على جوانب أجهزة الكمبيوتر الخاصة بالجهاز في مثال بنية PlayStation 4 (الشكل). هنا هو المعالج المركزي ، و 8 مراكز ، ومخازن L1 و L2 ، وحافلات البيانات ، وذاكرة الوصول العشوائي ، ومسرع الرسومات ، إلخ. للحصول على وصف كامل ومفصل ، راجع "هندسة محرك اللعبة" لجاسون غريغوري.

بلاي ستيشن 4 العمارة
المناهج العامة
لا يوجد حل عالمي. ولكن هناك مجموعة من النقاط التي يجب التركيز عليها إذا كنت ستنفذ تخصيص وإدارة الذاكرة يدويًا في تطبيقك. ويشمل ذلك الحاويات والمخصصات المتخصصة ، واستراتيجيات تخصيص الذاكرة ، وتصميم النظام / اللعبة ، ومديري الموارد ، وأكثر من ذلك.
أنواع الموزعين
يعتمد استخدام أدوات تخصيص الذاكرة الخاصة على الفكرة التالية: تعرف الحجم ، ولحظات العمل ، وفي أي مكان ستحتاج إلى أجزاء من الذاكرة. لذلك ، يمكنك تخصيص الذاكرة اللازمة ، بطريقة ما بنائها واستخدامها / إعادة استخدامها. هذه هي الفكرة / المفهوم العام لاستخدام المخصصات الخاصة. ما هم (بالطبع ، ليس كل شيء) يمكن رؤيته أكثر:
الخطي التخصيص
يمثل المخزن المؤقت مساحة العنوان متجاورة. في سياق العمل ، يسمح لك بتخصيص أقسام من الذاكرة ذات الحجم التعسفي (بحيث تناسبها في مخزن مؤقت). ولكن يمكنك تحرير جميع الذاكرة المخصصة مرة واحدة فقط. أي أنه لا يمكن تحرير قطعة ذاكرة تعسفية - ستبقى كما لو كانت مشغولة حتى يتم وضع علامة على المخزن المؤقت بأكمله على أنه نظيف. يوفر هذا التصميم تخصيص وإصدار O (1) ، مما يعطي ضمانًا للسرعة تحت أي ظروف.

حالة الاستخدام النموذجية: في عملية تحديث حالة العملية (كل إطار في اللعبة) ، يمكنك استخدام LinearAllocator لتخصيص مخازن مؤقتة tmp لأي احتياجات تقنية: معالجة المدخلات ، العمل مع السلاسل ، تحليل أوامر ConsoleManager في وضع التصحيح ، إلخ.
مكدس مخصص
تعديل مخصص خطي. يسمح لك بتحرير الذاكرة بالترتيب العكسي للتخصيص ، وبعبارة أخرى ، يتصرف مثل مكدس منتظم وفقا لمبدأ LIFO. يمكن أن يكون مفيدًا جدًا لإجراء العمليات الحسابية المحملة (التسلسل الهرمي للتحولات) ، لتنفيذ عمل النظام الفرعي للبرمجة النصية ، لأي عمليات حسابية يكون فيها الترتيب المحدد لتحرير الذاكرة معروفًا مسبقًا.

توفر بساطة التصميم تخصيص ذاكرة O (1) وسرعة تحرير.
حمام سباحة مخصص
يسمح لك بتخصيص كتل الذاكرة من نفس الحجم. يمكن تطبيقه كمخزن مؤقت لمساحة العنوان المستمرة ، مقسومًا إلى كتل ذات حجم محدد مسبقًا. هذه الكتل يمكن أن تشكل قائمة مرتبطة. ونعرف دائمًا أي كتلة يجب تقديمها في التخصيص التالي. يمكن تخزين معلومات التعريف هذه في الكتل نفسها ، مما يفرض قيودًا على الحد الأدنى لحجم الكتلة (sizeof (void *)). في الواقع ، هذه ليست حرجة.

نظرًا لأن جميع الكتل من نفس الحجم ، لا يهمنا أي من المجموعات المراد إرجاعها ، وبالتالي ، يمكن إجراء جميع عمليات التخصيص / إلغاء التخصيص في O (1).
مخصص الإطار
يسمح لك التخصيص الخطي ولكن بالإشارة إلى الإطار الحالي فقط - بتخصيص ذاكرة tmp ثم تحرير كل شيء تلقائيًا عند تغيير الإطار. يجب أن يتم تمييزها بشكل منفصل ، نظرًا لأن هذا كيان عالمي وفريد من نوعه في إطار لعبة وقت التشغيل ، وبالتالي يمكن تصنيعه بحجم مثير للإعجاب ، كما يقول بضع عشرات MiB ، والتي ستكون مفيدة للغاية عند تحميل الموارد ومعالجتها.
تخصيص إطار مزدوج
إنه أداة تخصيص إطار مزدوج ، ولكن مع بعض الميزات. يسمح لك بتخصيص الذاكرة في الإطار الحالي ، واستخدامها في كل من الإطار الحالي والإطار التالي. بمعنى ، سيتم تحرير الذاكرة التي قمت بتخصيصها في الإطار N فقط بعد إطار N + 1. يتحقق ذلك عن طريق تبديل الإطار النشط للإبراز في نهاية كل إطار.

لكن هذا النوع من التخصيص ، مثل سابقه ، يفرض عددًا من القيود على عمر الكائنات التي تم إنشاؤها في الذاكرة المخصصة له. لذلك ، يجب أن تدرك أنه في نهاية الإطار ، تصبح البيانات ببساطة غير صالحة ، وأن الوصول إليها بشكل متكرر يمكن أن يسبب مشاكل خطيرة.
مخصص ثابت
يخصص هذا النوع من التخصيص الذاكرة من مخزن مؤقت تم الحصول عليه ، على سبيل المثال ، في مرحلة تشغيل البرنامج ، أو تم التقاطه في بنية تخزين العناصر في إطار دالة. حسب النوع ، يمكن أن يكون أي مخصص: خطي ، تجمع ، مكدس. لماذا يطلق عليه ثابت ؟ يجب أن يكون حجم المخزن المؤقت للذاكرة التي تم التقاطها معروفًا في مرحلة التحويل البرمجي للبرنامج. هذا يفرض قيودًا كبيرة: لا يمكن تغيير مقدار الذاكرة المتوفرة لهذا المخصص أثناء العملية. ولكن ما هي الفوائد؟ سيتم التقاط المخزن المؤقت المستخدم تلقائيًا ثم تحريره (إما عند الانتهاء من العمل أو عند الخروج من الوظيفة). هذا لا يحمّل الكومة ، يحفظك من التفتت ، يسمح لك بتخصيص الذاكرة بسرعة في مكانها.
يمكنك إلقاء نظرة على مثال الكود باستخدام هذا المخصص ، إذا كنت بحاجة إلى تقسيم السلسلة إلى سلاسل فرعية وفعل شيء معها:

كما يمكن الإشارة إلى أن استخدام الذاكرة من المكدس من الناحية النظرية هو أكثر فعالية بكثير ، لأنه كومة إطار الوظيفة الحالية مع وجود احتمال كبير سيكون بالفعل في ذاكرة التخزين المؤقت للمعالج.
كل هذه المخصصات تحل بطريقة ما مشاكل التجزؤ ، مع نقص الذاكرة ، مع سرعة تلقي وإطلاق كتل بالحجم المطلوب ، مع عمر الكائنات والذاكرة التي تشغلها.
تجدر الإشارة أيضًا إلى أن النهج الصحيح لتصميم الواجهة سيسمح لك بإنشاء نوع من التسلسل الهرمي للمخصصين ، على سبيل المثال: تجمع يخصص الذاكرة من تخصيص الإطار ، ويخصص تخصيص الإطار بدوره الذاكرة من التخصيص الخطي. يمكن الاستمرار في بنية مماثلة ، والتكيف مع المهام والاحتياجات الخاصة بك.

أرى واجهة مشابهة لإنشاء تسلسلات هرمية كما يلي:
class IAllocator { public: virtual void* alloc(size_t size) = 0; virtual void* alloc(size_t size, size_t alignment) = 0; virtual void free (void* &p) = 0; }
malloc/free , . , , . / , .
Smart pointer — C++ ++11 ( boost, ). -, , - , . .
? :
- (/)
:
Unique pointer
1 ( ).
unique pointer , . , .. 1 / .
uniquePtr1 uniquePtr2, uniquePtr1 , . 1 .

Shared pointer
(reference counting). , , . , , , .

. -, , . . -, - .
Weak pointer
. , . ماذا يعني هذا؟ shared pointer. , shared pointer , . , shared pointer weak pointer. , (shared) , weak pointer shared pointer. — weak pointer , , , .

shared, weak pointer meta-data . - , .. , O(N) overhead , N — - . , . , . .
: . , shared pointer, , ( ) - - - . . meta-info , , . مثال:
/* */ /* , shared pointer */ Array<TSharedPtr<Object>> objects; objects.add(newShared<Object>(...)); ... objects.add(newShared<Object>(...));
/* ( meta-info ) */ Array<Object> objects; objects.emplace(...); ... objects.emplace(...);
. . حول هذا الموضوع كذلك.
Unique id
, . (id/identificator), , , -. :
, id. , , , id.
, ( , )
id , , id.
. , id, .
: id, , id, .
id , (Vulkan, OpenGL), (Godot, CryEngine). EntityID CryEngine .
, id : . , ( ), , .
/* */ class ID { uint32 index; uint32 generation; }
/* - / */ class ObjectManager { public: ID create(...); void destroy(ID); void update(ID id, ...); private: Array<uint32> generations; Array<Objects> objects; }
ID , ID . :
generation = generations[id.index]; if (generation == id.generation) then /* */ else /* , */
id generation 1 id ids.
C++ , . std, , . :
- Linked list —
- Array — /
- Queue —
- Stack —
- Map —
- Set —
? memory corruption. / , , , , .
, , . , , / .
, , . , ( ) . , malloc/free , , .
? , (/ ), , , . , , , .

ryEngine Sandbox:
, Unreal, Unity, CryEngine ., , . , , , — , .
Pre-allocating
, / .
: malloc/free . , "run out of memory", . . , (, , .).
. . , - . , malloc/free, : , , .
. : , , , .. .
: , , , . open-source , , . , , — malloc/free.
GDC CD Project Red , , "The Witcher: Blood and Wine" () . , , , , .

Naughty Dog , "Uncharted 4: A Thief's End" , (, ) .

استنتاج
, , , . , . / , , - .. , (, ).