حلّ أخطاء الربط في لعبة Unity هو عملية منهجية:

الحصول على لقطة للذاكرة
استخدِم Unity Profiler للحصول على لقطة ذاكرة مُدارة من Unity. تعرض "الشكل 2" طبقات إدارة الذاكرة التي تستخدمها Unity للتعامل مع الذاكرة في لعبتك.

الذاكرة المُدارة
تنفّذ إدارة الذاكرة في Unity طبقة ذاكرة خاضعة للتحكّم تستخدم مجموعة بيانات مُدارة وجامع بيانات غير مرغوب فيها لتخصيص الذاكرة وتعيينها تلقائيًا. نظام الذاكرة المُدارة هو بيئة برمجة نصية بلغة C# تستند إلى Mono أو IL2CPP. تتمثّل فائدة نظام الذاكرة المُدارة في أنّه يستخدم أداة لجمع البيانات غير الضرورية من أجل تفريغ مساحات الذاكرة المخصّصة تلقائيًا.
الذاكرة غير المُدارة في لغة C#
توفّر طبقة ذاكرة C# غير المُدارة إمكانية الوصول إلى طبقة الذاكرة الأصلية، ما يتيح التحكّم بدقة في عمليات تخصيص الذاكرة أثناء استخدام رمز C#. يمكن الوصول إلى طبقة إدارة الذاكرة هذه من خلال مساحة الاسم Unity.Collections ومن خلال دوال مثل UnsafeUtility.Malloc وUnsafeUtility.Free.
الذاكرة الأصلية
يستخدم جوهر C/C++ الداخلي في Unity نظام ذاكرة أصليًا لإدارة المشاهد والأصول وواجهات برمجة التطبيقات للرسومات وبرامج التشغيل والأنظمة الفرعية ومخازن المكوّنات الإضافية. على الرغم من أنّ الوصول المباشر محدود، يمكنك معالجة البيانات بأمان باستخدام واجهة برمجة التطبيقات C# من Unity والاستفادة من الرمز البرمجي الأصلي الفعّال. لا تتطلّب الذاكرة الأصلية عادةً تفاعلاً مباشرًا، ولكن يمكنك مراقبة تأثيرها في الأداء باستخدام أداة Profiler وتعديل الإعدادات لتحسين الأداء.
لا تتم مشاركة الذاكرة بين C# والرموز البرمجية الأصلية كما هو موضّح في الشكل 3. يتم تخصيص البيانات المطلوبة من خلال C# في مساحة الذاكرة المُدارة في كل مرة تكون هناك حاجة إليها.
لكي يتمكّن رمز اللعبة المُدارة (C#) من الوصول إلى بيانات الذاكرة الأصلية للمحرّك، على سبيل المثال، يؤدي طلب GameObject.transform إلى إجراء طلب أصلي للوصول إلى بيانات الذاكرة في المنطقة الأصلية، ثم يتم عرض القيم إلى C# باستخدام عمليات الربط. تضمن عمليات الربط اتّباع قواعد الاتصال المناسبة لكل نظام أساسي، كما تتعامل مع عملية تحويل الأنواع المُدارة تلقائيًا إلى الأنواع الأصلية المكافئة لها.
يحدث ذلك للمرة الأولى فقط، لأنّ الواجهة المُدارة الخاصة بالوصول إلى السمة transform يتم الاحتفاظ بها في الرمز البرمجي الأصلي. يمكن أن يؤدي تخزين السمة transform مؤقتًا إلى تقليل عدد عمليات الاستدعاء المتبادلة بين الرمز المُدار والرمز البرمجي الأصلي، ولكن تعتمد فائدة التخزين المؤقت على عدد مرات استخدام السمة. يُرجى أيضًا ملاحظة أنّ Unity لا تنسخ أجزاءً من الذاكرة الأصلية إلى الذاكرة المُدارة عند الوصول إلى واجهات برمجة التطبيقات هذه.

لمزيد من المعلومات، يُرجى الاطّلاع على مقدمة عن الذاكرة في Unity.
بالإضافة إلى ذلك، من الضروري وضع ميزانية للذاكرة من أجل الحفاظ على سلاسة تشغيل لعبتك، كما أنّ تنفيذ نظام لتحليل أو إعداد تقارير عن استهلاك الذاكرة يضمن عدم تجاوز كل إصدار جديد لميزانية الذاكرة. تُعدّ عملية دمج اختبارات وضع Play مع عملية الدمج المتواصل (CI) للتحقّق من استهلاك الذاكرة في مناطق معيّنة من اللعبة استراتيجية أخرى للحصول على إحصاءات أفضل.
إدارة مواد العرض
هذا هو الجزء الأكثر تأثيرًا وقابلية للتنفيذ في استهلاك الذاكرة. الملف الشخصي في أقرب وقت ممكن.
يمكن أن يختلف استخدام الذاكرة في ألعاب Android بشكل كبير حسب نوع اللعبة وعدد الأصول وأنواعها واستراتيجيات تحسين الذاكرة. ومع ذلك، تشمل العناصر الشائعة التي تساهم في استخدام الذاكرة عادةً مواد العرض والشبكات والملفات الصوتية وبرامج التظليل والرسوم المتحركة والنصوص البرمجية.
رصد مواد العرض المكرّرة
تتمثّل الخطوة الأولى في رصد مواد العرض التي تم إعدادها بشكل سيئ ومواد العرض المكرّرة باستخدام أداة تحليل استخدام الذاكرة أو أداة تقرير الإنشاء أو مدقّق المشاريع.
زخارف
حلِّل توافق لعبتك مع الأجهزة وحدِّد تنسيق النسيج المناسب. يمكنك تقسيم حِزم مواد العرض الخاصة بالرسومات إلى حِزم مخصّصة للأجهزة المتطورة والأجهزة المنخفضة الإمكانات باستخدام عرض المواد في Play أو العناصر القابلة للتوجيه أو عملية يدوية أكثر باستخدام AssetBundle.
اتّبِع الاقتراحات الأكثر شيوعًا المتوفّرة في تحسين أداء ألعابك على الأجهزة الجوّالة وفي منشور المناقشة تحسين إعدادات استيراد مواد العرض في Unity. بعد ذلك، جرِّب الحلول التالية:
اضغط مواد العرض باستخدام تنسيقات ASTC لتقليل مساحة الذاكرة المستخدَمة وجرِّب معدل حظر أعلى، مثل 8x8.
إذا كان استخدام ETC2 مطلوبًا، عليك تجميع مواد العرض في Atlas. يضمن وضع العديد من مواد العرض في مادة عرض واحدة أن تكون هذه المادة من مضاعفات العدد 2، ويمكن أن يقلّل من عدد طلبات الرسم، كما يمكن أن يزيد من سرعة العرض.
تحسين تنسيق وحجم نسيج RenderTarget تجنَّب استخدام مواد عرض عالية الدقة بشكل غير ضروري. يؤدي استخدام زخارف أصغر حجمًا على الأجهزة الجوّالة إلى توفير مساحة في الذاكرة.
استخدِم تجميع قنوات الزخرفة لحفظ ذاكرة الزخرفة.
الشبكات والنماذج
ابدأ بالتحقّق من الإعدادات الأساسية (الصفحة 27) وراجِع إعدادات استيراد الشبكة التالية:
- دمج الشبكات المتكررة والأصغر
- قلِّل عدد الرؤوس للعناصر في المشاهد (على سبيل المثال، العناصر الثابتة أو البعيدة).
- إنشاء مجموعات "مستوى التفاصيل" (LOD) لمواد العرض ذات الهندسة العالية
المواد والمظلات
- إزالة صيغ التظليل غير المستخدَمة آليًا أثناء عملية الإنشاء
- يمكنك دمج أشكال التظليل المستخدَمة بشكل متكرر في أشكال تظليل شاملة لتجنُّب تكرار أشكال التظليل.
- تفعيل التحميل الديناميكي لبرامج التظليل من أجل معالجة حجم الذاكرة الكبير الذي تستهلكه برامج التظليل المحمَّلة مسبقًا في ذاكرة الوصول العشوائي للفيديو (VRAM) أو ذاكرة الوصول العشوائي (RAM) ومع ذلك، يجب الانتباه إذا كان تجميع برامج التظليل يتسبّب في حدوث مشاكل في عرض اللقطات.
- استخدِم ميزة التحميل الديناميكي لبرامج التظليل لمنع تحميل جميع الصيغ. لمزيد من المعلومات، يُرجى الاطّلاع على مشاركة المدونة تحسينات على أوقات إنشاء التظليل واستخدام الذاكرة.
- استخدِم ميزة إنشاء مثيلات للمواد بشكل صحيح من خلال الاستفادة من
MaterialPropertyBlocks
.
الصوت
ابدأ بالتحقّق من الإعدادات الأساسية (الصفحة 41)، وتأكَّد من إعدادات استيراد الشبكة التالية:
- أزِل مراجع
AudioClip
غير المستخدَمة أو المكرّرة عند استخدام محركات صوت تابعة لجهات خارجية، مثل FMOD أو Wwise. - التحميل المُسبَق لبيانات الصوت إيقاف التحميل المُسبَق للمقاطع التي لا تكون مطلوبة على الفور أثناء وقت التشغيل أو بدء المشهد يساعد ذلك في تقليل الحمل الزائد للذاكرة أثناء عملية تهيئة المشهد.
الصور المتحركة
- اضبط إعدادات ضغط الرسوم المتحركة في Unity لتقليل عدد الإطارات الرئيسية وإزالة البيانات المكرّرة.
- تقليل الإطارات الرئيسية: تتم إزالة الإطارات الرئيسية غير الضرورية تلقائيًا
- ضغط الرباعيات: يضغط بيانات الدوران لتقليل استخدام الذاكرة
يمكنك ضبط إعدادات الضغط في إعدادات استيراد الرسوم المتحركة ضمن علامة التبويب المنصة أو الرسوم المتحركة.
أعِد استخدام مقاطع الصور المتحركة بدلاً من تكرارها لعناصر مختلفة.
استخدِم عناصر التحكّم في استبدال Animator لإعادة استخدام Animator Controller واستبدال مقاطع معيّنة بشخصيات مختلفة.
إنشاء صور متحركة مستندة إلى الفيزياء: إذا كانت الصور المتحركة مستندة إلى الفيزياء أو إجرائية، يمكنك إنشاءها في مقاطع صور متحركة لتجنُّب عمليات الحساب أثناء وقت التشغيل.
تحسين هيكل العظام: استخدِم عددًا أقل من العظام في الهيكل لتقليل التعقيد واستهلاك الذاكرة.
- تجنَّب استخدام عدد كبير من العظام للأجسام الصغيرة أو الثابتة.
- إذا لم تكن هناك حاجة إلى بعض العظام أو لم يتم تحريكها، أزِلها من هيكل التحريك.
تقليل طول مقطع الصورة المتحركة
- اقطع مقاطع الرسوم المتحركة لتضمين اللقطات الضرورية فقط. تجنَّب تخزين الصور المتحركة غير المستخدَمة أو الطويلة جدًا.
- استخدِم صورًا متحركة متكررة بدلاً من إنشاء مقاطع طويلة للحركات المتكررة.
تأكَّد من ربط مكوّن حركة واحد أو تفعيله فقط. على سبيل المثال، يمكنك إيقاف مكوّنات الرسوم المتحركة القديمة أو إزالتها إذا كنت تستخدم Animator.
تجنَّب استخدام Animator إذا لم يكن ذلك ضروريًا. بالنسبة إلى المؤثرات البصرية البسيطة، استخدِم مكتبات التوسيط أو نفِّذ المؤثر البصري في نص برمجي. يمكن أن يكون نظام الرسوم المتحركة مكلفًا من حيث الموارد، خاصةً على الأجهزة الجوّالة المنخفضة المواصفات.
استخدِم نظام المهام للصور المتحركة عند التعامل مع عدد كبير من الصور المتحركة، لأنّ هذا النظام تمت إعادة تصميمه بالكامل ليكون أكثر كفاءة في استخدام الذاكرة.
المَشَاهد
عند تحميل مَشاهد جديدة، يتم إحضار مواد العرض كعناصر تابعة. ومع ذلك، بدون إدارة دورة حياة الأصول بشكل سليم، لا يتم تتبُّع هذه التبعيات من خلال عدّادات المراجع. ونتيجةً لذلك، قد تبقى مواد العرض في الذاكرة حتى بعد إلغاء تحميل المشاهد غير المستخدَمة، ما يؤدي إلى تجزئة الذاكرة.
- استخدِم ميزة تجميع العناصر في Unity لإعادة استخدام مثيلات GameObject للعناصر المتكررة في اللعب، لأنّ ميزة تجميع العناصر تستخدم حزمة لتخزين مجموعة من مثيلات العناصر لإعادة استخدامها، وهي ليست آمنة للاستخدام المتزامن. يؤدي تصغير
Instantiate
وDestroy
إلى تحسين أداء وحدة المعالجة المركزية وثبات الذاكرة. - جارٍ إلغاء تحميل مواد العرض:
- يمكنك إيقاف تحميل مواد العرض بشكل استراتيجي خلال اللحظات الأقل أهمية، مثل شاشات البداية أو شاشات التحميل.
- يؤدي الاستخدام المتكرر لـ
Resources.UnloadUnusedAssets
إلى حدوث ارتفاعات مفاجئة في معالجة وحدة المعالجة المركزية بسبب عمليات المراقبة الكبيرة للتبعيات الداخلية. - ابحث عن ارتفاعات كبيرة في استخدام وحدة المعالجة المركزية في علامة الملف الشخصي GC.MarkDependencies.
يمكنك إزالة أو تقليل معدّل تكرار تنفيذها، وإلغاء تحميل موارد معيّنة يدويًا باستخدام Resources.UnloadAsset بدلاً من الاعتماد على
Resources.UnloadUnusedAssets()
الشاملة.
- إعادة هيكلة المشاهد بدلاً من استخدام Resources.UnloadUnusedAssets باستمرار
- قد يؤدي استدعاء
Resources.UnloadUnusedAssets()
من أجلAddressables
إلى إلغاء تحميل الحِزم التي تم تحميلها ديناميكيًا عن غير قصد. إدارة مراحل نشاط مواد العرض التي يتم تحميلها ديناميكيًا بعناية
بنود متنوعة
التجزئة الناتجة عن انتقالات المشاهد: عند استدعاء الطريقة
Resources.UnloadUnusedAssets()
، ينفّذ Unity ما يلي:- تفريغ الذاكرة لمواد العرض التي لم يعُد يتم استخدامها
- تنفيذ عملية مشابهة لعملية جمع البيانات غير الضرورية للتحقّق من كومة الذاكرة المؤقتة للعناصر المُدارة والأصلية بحثًا عن مواد عرض غير مستخدَمة وإلغاء تحميلها
- تنظيف ذاكرة المواد والشبكات المتداخلة ومواد العرض شرط عدم توفّر أي مرجع نشط
AssetBundle
أوAddressable
- إجراء تغييرات في هذا المجال أمر معقّد ويتطلّب جهدًا جماعيًا من الفريق لتنفيذ الاستراتيجيات. ومع ذلك، بعد إتقان هذه الاستراتيجيات، يمكنها تحسين استخدام الذاكرة بشكل كبير وتقليل حجم التنزيل وخفض تكاليف السحابة الإلكترونية. لمزيد من المعلومات حول إدارة مواد العرض في Unity، يُرجى الاطّلاع علىAddressables
.التبعيات المشترَكة المركزية: يمكنك تجميع التبعيات المشترَكة، مثل برامج التظليل والرسومات والخطوط، بشكل منتظم في حِزم أو مجموعات مخصّصة.
Addressable
يقلّل ذلك من التكرار ويضمن إلغاء تحميل مواد العرض غير الضرورية بكفاءة.استخدِم
Addressables
لتتبُّع العناصر التابعة - العناصر القابلة للعنونة تسهّل عملية التحميل والإلغاء، ويمكنها تلقائيًا إلغاء تحميل العناصر التابعة التي لم يعُد يتم الرجوع إليها. قد يكون الانتقال إلىAddressables
لإدارة المحتوى وحل التبعيات حلاً قابلاً للتطبيق، وذلك حسب حالة اللعبة المحددة. يمكنك تحليل سلاسل التبعيات باستخدام الأداة "تحليل" لتحديد التكرارات أو التبعيات غير الضرورية. بدلاً من ذلك، يمكنك الرجوع إلى "أدوات بيانات Unity" إذا كنت تستخدم AssetBundles.
TypeTrees
- إذا تم إنشاءAddressables
وAssetBundles
في لعبتك ونشرهما باستخدام إصدار Unity نفسه الذي يستخدمه اللاعب، ولا يتطلّبان توافقًا مع الإصدارات السابقة من إصدارات اللاعبين الأخرى، ننصحك بإيقاف عملية كتابةTypeTree
، ما سيؤدي إلى تقليل حجم الحِزمة ومساحة الذاكرة المستخدَمة في كائن الملف المتسلسل. عدِّل عملية الإنشاء في إعداد حزمة Addressables المحلية ContentBuildFlags إلى DisableWriteTypeTree.
كتابة رمز برمجي متوافق مع أداة جمع البيانات المهملة
تستخدم Unity جمع البيانات غير الضرورية (GC) لإدارة الذاكرة من خلال التعرّف تلقائيًا على الذاكرة غير المستخدَمة وتفريغها. مع أنّ جمع البيانات غير الضرورية أمر ضروري، إلا أنّه قد يتسبّب في حدوث مشاكل في الأداء (مثل ارتفاع معدّل عرض اللقطات) إذا لم تتم معالجته بشكل سليم، لأنّ هذه العملية يمكن أن توقف اللعبة مؤقتًا، ما يؤدي إلى حدوث مشاكل في الأداء وتجربة مستخدم غير مثالية.
راجِع دليل Unity للاطّلاع على تقنيات مفيدة بشأن تقليل معدّل تكرار عمليات تخصيص الذاكرة المدارة، وUnityPerformanceTuningBible، الصفحة 271، للاطّلاع على أمثلة.
تقليل عمليات تخصيص أداة جمع البيانات غير المرغوب فيها:
- تجنَّب استخدام LINQ وlambdas وclosures التي تخصّص ذاكرة الكومة.
- استخدِم
StringBuilder
للسلاسل القابلة للتغيير بدلاً من تسلسل السلاسل. - أعِد استخدام المجموعات من خلال استدعاء
COLLECTIONS.Clear()
بدلاً من إعادة إنشاء مثيل لها.
تتوفر معلومات إضافية في الكتاب الإلكتروني الدليل الشامل لإنشاء ملفات تعريف أداء ألعاب Unity.
إدارة تحديثات لوحة العرض في واجهة المستخدم:
- التغييرات الديناميكية في عناصر واجهة المستخدم: عندما يتم تعديل عناصر واجهة المستخدم، مثل النص أو الصورة أو خصائص
RectTransform
(على سبيل المثال، تغيير محتوى النص أو تغيير حجم العناصر أو تحريك المواضع)، قد يخصّص المحرّك مساحة تخزين للكائنات المؤقتة. - عمليات تخصيص السلاسل: غالبًا ما تتطلّب عناصر واجهة المستخدم، مثل "النص"، تعديلات على السلاسل، لأنّ السلاسل غير قابلة للتغيير في معظم لغات البرمجة.
- اللوحة غير النظيفة: عندما يتغيّر شيء ما على اللوحة (على سبيل المثال، تغيير الحجم أو تفعيل العناصر وإيقافها أو تعديل خصائص التنسيق)، قد يتم وضع علامة غير نظيفة على اللوحة بأكملها أو جزء منها، ثم إعادة إنشائها. يمكن أن يؤدي ذلك إلى إنشاء بنى بيانات مؤقتة (مثل بيانات الشبكة أو مخازن مؤقتة للرؤوس أو عمليات حسابية للتصميم)، ما يزيد من إنشاء البيانات غير الضرورية.
- التعديلات المعقّدة أو المتكرّرة: إذا كانت لوحة العرض تتضمّن عددًا كبيرًا من العناصر أو يتم تعديلها بشكل متكرّر (على سبيل المثال، كل إطار)، يمكن أن تؤدي عمليات إعادة الإنشاء هذه إلى حدوث اضطراب كبير في الذاكرة.
- التغييرات الديناميكية في عناصر واجهة المستخدم: عندما يتم تعديل عناصر واجهة المستخدم، مثل النص أو الصورة أو خصائص
فعِّل ميزة "التنظيف التدريجي لذاكرة التخزين المؤقت" لتقليل الارتفاعات الكبيرة في عمليات التنظيف من خلال توزيع عمليات تنظيف التخصيص على عدّة لقطات. يمكنك إنشاء ملف تعريف للتحقّق مما إذا كان هذا الخيار يحسّن أداء لعبتك ومساحة الذاكرة التي تشغلها.
إذا كانت لعبتك تتطلّب أسلوبًا متحكّمًا فيه، اضبط وضع جمع البيانات غير الضرورية على "يدوي". بعد ذلك، عند تغيير المستوى أو في لحظة أخرى بدون لعب نشط، يمكنك طلب جمع البيانات غير الضرورية.
استخدِم طلبات تجميع البيانات غير المرغوب فيها يدويًا GC.Collect() عند الانتقال بين حالات اللعبة (على سبيل المثال، التبديل بين المستويات).
يمكن تحسين المصفوفات بدءًا من ممارسات الرموز البرمجية البسيطة، وعند الضرورة، باستخدام مصفوفات أصلية أو حاويات أصلية أخرى للمصفوفات الكبيرة.
تتبُّع الكائنات المُدارة باستخدام أدوات مثل Unity Memory Profiler لتتبُّع مراجع الكائنات غير المُدارة التي تظل موجودة بعد التدمير
استخدِم علامة Profiler لإرسال البيانات إلى أداة إعداد تقارير الأداء من أجل اتّباع نهج مبرمَج.
تجنُّب تسرُّب الذاكرة وتجزئتها
تسرّبات الذاكرة
في رمز C#، عندما يكون هناك مرجع إلى عنصر Unity بعد تدمير العنصر، يظل عنصر التغليف المُدار، المعروف باسم Managed
Shell، في الذاكرة. يتم تحرير الذاكرة الأصلية المرتبطة بالمرجع عند إلغاء تحميل المشهد أو عند تدمير GameObject المرتبط بالذاكرة أو أي من الكائنات الرئيسية الخاصة به من خلال طريقة Destroy()
. ومع ذلك، إذا لم تتم إزالة المراجع الأخرى إلى Scene أو GameObject، قد يستمر ظهور الذاكرة المُدارة ككائن Leaked Shell. لمزيد من التفاصيل حول عناصر Managed Shell، يُرجى الرجوع إلى دليل عناصر Managed Shell.
بالإضافة إلى ذلك، يمكن أن تحدث تسريبات الذاكرة بسبب عمليات الاشتراك في الأحداث، وعبارات lambda وعمليات الإغلاق، وعمليات ربط السلاسل، والإدارة غير السليمة للعناصر المجمّعة:
- للبدء، اطّلِع على العثور على تسرُّبات الذاكرة لمقارنة لقطات ذاكرة Unity بشكل صحيح.
- تحقَّق من اشتراكات الأحداث وتسريب الذاكرة. إذا كانت العناصر تشترك في الأحداث (على سبيل المثال، من خلال المندوبين أو UnityEvents) ولكنها لا تلغي الاشتراك بشكل صحيح قبل تدميرها، قد يحتفظ مدير الأحداث أو الناشر بمراجع لتلك العناصر. ويمنع ذلك جمع البيانات غير الضرورية لهذه العناصر، ما يؤدي إلى حدوث تسرّبات في الذاكرة.
- مراقبة أحداث الفئة العامة أو الفئة الفردية التي لم يتم إلغاء تسجيلها عند إتلاف العنصر على سبيل المثال، إلغاء الاشتراك أو إلغاء ربط المفوضين في برامج تدمير الكائنات.
- تأكَّد من أنّ إتلاف العناصر المجمّعة يؤدي إلى إلغاء جميع الإشارات إلى مكوّنات شبكة النص والرسومات والعناصر الرئيسية في اللعبة.
- يُرجى العِلم أنّه عند مقارنة لقطات Unity Memory Profiler وملاحظة اختلاف في استهلاك الذاكرة بدون سبب واضح، قد يكون الاختلاف ناتجًا عن برنامج تشغيل الرسومات أو نظام التشغيل نفسه.
تجزئة الذاكرة
يحدث تجزئة الذاكرة عند تحرير العديد من عمليات التخصيص الصغيرة بترتيب عشوائي. يتم تخصيص الذاكرة في الكومة بشكل تسلسلي، ما يعني أنّه يتم إنشاء أجزاء جديدة من الذاكرة عندما ينفد المساحة في الجزء السابق. ونتيجةً لذلك، لا تملأ العناصر الجديدة المساحات الفارغة في الأجزاء القديمة، ما يؤدي إلى حدوث تجزئة. بالإضافة إلى ذلك، يمكن أن تؤدي عمليات التخصيص المؤقتة الكبيرة إلى تجزئة دائمة طوال مدة جلسة اللعبة.
تصبح هذه المشكلة أكثر حدة عندما يتم تخصيص مساحات كبيرة من الذاكرة لفترة قصيرة بالقرب من مساحات أخرى مخصصة لفترة طويلة.
يجب أن تستند عمليات تخصيص المجموعات إلى مدة بقائها، ومن المفترض أن يتم تخصيص المجموعات التي تدوم طويلاً معًا في وقت مبكر من دورة حياة التطبيق.
المراقبون ومدراء الأحداث
- بالإضافة إلى المشكلة المذكورة في القسم (تسريب الذاكرة)77، يمكن أن تساهم عمليات تسريب الذاكرة بمرور الوقت في حدوث تجزئة من خلال ترك ذاكرة غير مستخدَمة مخصّصة لعناصر لم تعُد قيد الاستخدام.
- تأكَّد من أنّ إتلاف العناصر المجمّعة يؤدي إلى إبطال كامل للمراجع إلى مكوّنات شبكة النص والرسومات والعنصر الرئيسي
GameObjects
. - غالبًا ما ينشئ مديرو الأحداث قوائم أو قواميس ويخزّنونها لإدارة الاشتراكات في الأحداث. وإذا زاد حجمها ونقص بشكل ديناميكي أثناء وقت التشغيل، يمكن أن يؤدي ذلك إلى تجزئة الذاكرة بسبب عمليات التخصيص وإلغاء التخصيص المتكررة.
الرمز
- تخصّص الروتينات الفرعية أحيانًا مساحة تخزين، ويمكن تجنُّب ذلك بسهولة من خلال تخزين عبارة الإرجاع الخاصة بـ IEnumerator مؤقتًا بدلاً من تعريف عبارة جديدة في كل مرة.
- راقِب باستمرار حالات دورة الحياة للعناصر المجمّعة لتجنُّب الاحتفاظ بمراجع غير صالحة.
UnityEngine.Object
مواد العرض
- استخدِم أنظمة احتياطية ديناميكية لتجارب الألعاب المستندة إلى النصوص لتجنُّب التحميل المُسبَق لجميع الخطوط في حالات اللغات المتعددة.
- نظِّم مواد العرض (مثل الأنسجة والجسيمات) معًا حسب النوع ودورة الحياة المتوقّعة.
- يمكنك تقليل حجم مواد العرض التي تتضمّن سمات دورة حياة غير نشطة، مثل صور واجهة المستخدم المكرّرة والشبكات الثابتة.
عمليات التخصيص المستندة إلى مدة الاشتراك
- خصِّص مواد عرض طويلة الأمد في بداية دورة حياة التطبيق لضمان تخصيصات مضغوطة.
- استخدِم NativeCollections أو أدوات تخصيص مخصّصة لبُنى البيانات المؤقتة أو التي تتطلّب مساحة كبيرة من الذاكرة (مثل مجموعات الفيزياء).
إجراء الذاكرة المتعلّق بالرموز والملفات التنفيذية
يؤثر ملف تنفيذ اللعبة والإضافات أيضًا في استخدام الذاكرة.
البيانات الوصفية لـ IL2CPP
تنشئ IL2CPP بيانات وصفية لكل نوع (على سبيل المثال، الفئات والعناصر العامة وعناصر التفويض) في وقت الإنشاء، ويتم استخدامها بعد ذلك في وقت التشغيل لإجراء عمليات الانعكاس والتحقّق من النوع وغيرها من العمليات الخاصة بوقت التشغيل. يتم تخزين هذه البيانات الوصفية في الذاكرة، ويمكن أن تساهم بشكل كبير في إجمالي مساحة الذاكرة التي يشغلها التطبيق. يساهم ذاكرة التخزين المؤقت للبيانات الوصفية في IL2CPP بشكل كبير في أوقات التهيئة والتحميل. بالإضافة إلى ذلك، لا يزيل IL2CPP التكرار من بعض عناصر البيانات الوصفية (مثل الأنواع العامة أو المعلومات المتسلسلة)، ما قد يؤدي إلى زيادة استخدام الذاكرة. ويزداد الأمر سوءًا عند استخدام أنواع متكررة أو زائدة عن الحاجة في المشروع.
يمكن تقليل حجم البيانات الوصفية لـ IL2CPP من خلال:
- تجنُّب استخدام واجهات برمجة التطبيقات الخاصة بالانعكاس، لأنّها قد تساهم بشكل كبير في عمليات تخصيص البيانات الوصفية في IL2CPP
- إيقاف الحِزم المضمّنة
- تطبيق المشاركة العامة الكاملة في Unity 2022، ما سيساعد في تقليل النفقات العامة الناتجة عن الأنواع العامة ومع ذلك، للمساعدة في تقليل عمليات التخصيص بشكل أكبر، يجب تقليل استخدام الأنواع العامة.
إزالة الرموز
بالإضافة إلى تقليل حجم الإصدار، يؤدي حذف الرموز البرمجية أيضًا إلى تقليل استخدام الذاكرة. عند إنشاء إصدار باستخدام IL2CPP كبرنامج نصي، تؤدي عملية إزالة الرموز غير المستخدَمة من تجميعات مُدارة (المفعّلة تلقائيًا) إلى إزالة الرموز غير المستخدَمة من التجميعات المُدارة. تعمل هذه العملية من خلال تحديد التجميعات الجذرية ثم استخدام تحليل الرمز الثابت لتحديد الرمز المُدار الآخر الذي تستخدمه هذه التجميعات الجذرية. تتم إزالة أي رمز لا يمكن الوصول إليه. لمزيد من المعلومات حول ميزة "إزالة الرموز البرمجية المُدارة"، يُرجى الاطّلاع على مشاركة المدوّنة قصص من خنادق التحسين: إزالة أفضل للرموز البرمجية المُدارة باستخدام Unity 2020 LTS ومستندات إزالة الرموز البرمجية المُدارة.
أدوات التخصيص الأصلية
جرِّب أدوات تخصيص الذاكرة الأصلية لضبط أدوات تخصيص الذاكرة بدقة. إذا كانت الذاكرة المتاحة للعبة منخفضة، استخدِم وحدات ذاكرة أصغر، حتى لو كان ذلك يتضمّن أدوات تخصيص أبطأ. لمزيد من المعلومات، يمكنك الاطّلاع على مثال لمخصّص الذاكرة المؤقتة الديناميكي.
إدارة المكوّنات الإضافية وحِزم تطوير البرامج (SDK) الأصلية
ابحث عن المكوّن الإضافي الذي يسبّب المشكلة، ثم أزِل كل مكوّن إضافي وقارِن بين لقطات ذاكرة اللعبة. يتضمّن ذلك إيقاف الكثير من وظائف الرموز البرمجية باستخدام Scripting Define Symbols وإعادة تصميم الفئات المرتبطة بشكل كبير باستخدام الواجهات. راجِع الارتقاء بمستوى الرمز البرمجي من خلال أنماط برمجة الألعاب لتسهيل عملية إيقاف التبعيات الخارجية بدون جعل لعبتك غير قابلة للتشغيل.
تواصَل مع مؤلف المكوّن الإضافي أو حزمة SDK، لأنّ معظم المكوّنات الإضافية ليست مفتوحة المصدر.
إعادة إنتاج استخدام الذاكرة للمكوّن الإضافي: يمكنك كتابة مكوّن إضافي بسيط (استخدِم مكوّن Unity الإضافي هذا كمرجع) ينفّذ عمليات تخصيص الذاكرة. افحص لقطات الذاكرة باستخدام Android Studio (لأنّ Unity لا يتتبّع عمليات التخصيص هذه) أو استدعِ الفئة
MemoryInfo
والطريقةRuntime.totalMemory()
في المشروع نفسه.
يخصّص مكوّن Unity الإضافي ذاكرة Java وذاكرة أصلية، وإليك كيفية إجراء ذلك:
Java
byte[] largeObject = new byte[1024 * 1024 * megaBytes];
list.add(largeObject);
Native
char* buffer = new char[megabytes * 1024 * 1024];
// Random data to fill the buffer
for (int i = 1; i < megabytes * 1024 * 1024; ++i) {
buffer[i] = 'A' + (i % 26); // Fill with letters A-Z
}