Android पर Unity गेम की मेमोरी को ऑप्टिमाइज़ करना

आजकल के मोबाइल डिवाइसों में रैम की क्षमता लगातार बढ़ रही है. हालांकि, ज़्यादा रिज़ॉल्यूशन वाली ऐसेट और रेंडरिंग के मुश्किल पाइपलाइन की वजह से, गेम में मेमोरी की खपत इस बढ़ोतरी से ज़्यादा तेज़ी से बढ़ रही है.

ज़्यादा मेमोरी का इस्तेमाल करने की वजह से होने वाली मुख्य समस्याएं

  • लो मेमोरी किलर (एलएमके) की वजह से ऐप्लिकेशन बंद होना: सिस्टम में मेमोरी की कमी होने पर, ओएस बैकग्राउंड या फ़ोरग्राउंड ऐप्लिकेशन को ज़बरदस्ती बंद कर देता है.
  • फ़्रेम ड्रॉप होना और अटकना (जंक): मैनेज्ड हीप में बार-बार गार्बेज कलेक्शन (जीसी) स्पाइक होने या ओएस-लेवल पर मेमोरी स्वैपिंग होने से, प्रोसेसिंग में रुकावटें आती हैं.
  • थर्मल थ्रॉटलिंग और तेज़ी से बैटरी खर्च होना: लगातार मेमोरी का बंटवारा, डीएलोकेशन, और मेमोरी पेज कमिट होने से, सीपीयू पर ज़्यादा लोड पड़ता है. इससे डिवाइस ज़्यादा गर्म होता है और बैटरी तेज़ी से खत्म होती है.

Android में मेमोरी मैनेजमेंट से जुड़े अपडेट

  • Android 17: MemoryLimiter की सुविधा: Android 17 में MemoryLimiter की सुविधा जोड़ी गई है. यह सुविधा, डिवाइस के हिसाब से तय की गई थ्रेशोल्ड के मुकाबले, ऐप्लिकेशन की मेमोरी की खपत पर नज़र रखती है. अगर कोई ऐप्लिकेशन, मेमोरी की तय सीमा से ज़्यादा मेमोरी इस्तेमाल करता है, तो उसे सिस्टम लेवल पर तुरंत बंद कर दिया जाता है. यह मैकेनिज़्म, पारंपरिक एलएमके से ज़्यादा सख्त है. इसलिए, पीक मेमोरी का इस्तेमाल मैनेज करना पहले से ज़्यादा ज़रूरी हो गया है.

Unity में मेमोरी का इस्तेमाल कम करना

Unity में, इंजन के इंटरनल मेमोरी पूल (नेटिव ब्लॉक एलोकेटर और मैनेज्ड हीप) को ज़्यादा पीक लोड के हिसाब से बढ़ाने के बाद, वह उन मेमोरी पेजों को ओएस को तुरंत वापस करने के बजाय, सेव रखता है. इसलिए, ज़्यादा मेमोरी इस्तेमाल करने वाली ऐसेट को अनलोड करने के बाद भी, रेज़िडेंट मेमोरी बढ़ जाती है. इससे ऐप्लिकेशन, Android ओएस की प्रोसेस टर्मिनेशन (जैसे, एलएमके या MemoryLimiter) के लिए ज़्यादा संवेदनशील हो जाता है.

ओएस-लेवल पर लागू होने से रोकने के लिए, मेमोरी ऑप्टिमाइज़ेशन को इन तीन मुख्य पहलुओं के हिसाब से किया जाना चाहिए:

  • कैटगरी A: पीक मेमोरी का इस्तेमाल कम करना
  • कैटगरी B: गैर-ज़रूरी ऐसेट और सिस्टम फ़ुटप्रिंट हटाना
  • कैटगरी C: गैर-ज़रूरी जीसी एलोकेशन हटाना

कैटगरी A: पीक मेमोरी का इस्तेमाल कम करना

Unity के एलोकेटर, फ़्री की गई मेमोरी पेजों को ओएस को तुरंत वापस करने के बजाय, फिर से इस्तेमाल करने के लिए सेव रखते हैं. इसलिए, बेसलाइन मेमोरी, मौजूदा यूसेज के बजाय, सबसे ज़्यादा पीक को दिखाती है. इसलिए, बाद में साफ़-सफ़ाई करने के बजाय, पहले से ही स्पाइक को रोकना ज़्यादा असरदार होता है.

1. बहुत बड़े AssetBundle इस्तेमाल करने से बचना

Unity के बंडल लोडिंग मैकेनिज़्म की वजह से, बड़े AssetBundle से मेमोरी में ज़्यादा डेटा सेव होता है और ओओएम क्रैश होते हैं. संसाधन मैनेजमेंट को बेहतर बनाने के लिए, बंडल को मॉड्यूलर रखें.

मुख्य समस्याएं

  • मेमोरी ओवरहेड ज़्यादा होना: किसी एक छोटी ऐसेट का अनुरोध करने पर, Unity को पूरे बंडल की फ़ाइल (जिसमें हेडर, मेटाडेटा, और स्ट्रीमिंग बफ़र शामिल हैं) को रैम में लोड करना पड़ता है.
  • अनलोड ट्रैप: अगर किसी बंडल में मौजूद कोई भी ऐसेट, ऐक्टिव तौर पर इस्तेमाल की जा रही है, तो पूरे बंडल को अनलोड नहीं किया जा सकता. इससे, इस्तेमाल न किए गए डेटा को रैम में सेव रखा जाता है.

सबसे सही तरीके

  • बंडल को मॉड्यूलर रखें: ऐसेट को सीन या लाइफ़साइकल के हिसाब से लॉजिकली ग्रुप करें.
  • Unity 6.6 या इसके बाद के वर्शन के लिए सलाह: अनचाहे क्रॉस-बंडल डिपेंडेंसी से बचने के लिए, कॉन्टेंट डायरेक्ट्री का इस्तेमाल करें.

2. ScriptableObjects में ऐसेट रेफ़रंस ऑप्टिमाइज़ करना

ScriptableObject में सीधे UnityEngine.Object सीरियलाइज़्ड फ़ील्ड से, हार्ड रेफ़रंस बनते हैं. इससे, रेफ़रंस की गई सभी ऐसेट को रैम में लोड करना पड़ता है. ऐसा तब होता है, जब ScriptableObject को लोड या इंस्टैंशिएट किया जाता है.

// BEFORE: Loading SceneRequiredAssets forces _worldAsset and _spawnSettings into RAM immediately
public class SceneRequiredAssets : ScriptableObject
{
    public string sceneName;
    public Object _worldAsset;
    public Object _spawnSettings;
}

// AFTER: Use AssetReference to enable asynchronous, on-demand loading using Addressables
public class SceneRequiredAssets : ScriptableObject
{
    public string sceneName;
    public AssetReference _worldAsset;
    public AssetReference _spawnSettings;
}

3. ऑडियो क्लिप और चैनल कॉन्फ़िगर करना

सभी ऑडियो क्लिप को अनकंप्रेस करके सीधे मेमोरी में लोड करने से, मेमोरी में ज़्यादा डेटा सेव होता है और वह हमेशा के लिए सेव रहता है. ऑडियो लोड सेटिंग को, उनके यूसेज प्रोफ़ाइल के हिसाब से कॉन्फ़िगर किया जाना चाहिए:

ऑडियो कैटगरी लोड टाइप कारण
बीजीएम (बैकग्राउंड म्यूज़िक) स्ट्रीमिंग मेमोरी स्पाइक से बचने के लिए, डिस्क से छोटे बफ़र में ऑडियो स्ट्रीम करता है.
लंबे साउंड इफ़ेक्ट मेमोरी में कंप्रेस किया गया रैम ओवरहेड को कम रखता है और प्लेबैक के दौरान ऑडियो को तुरंत डीकंप्रेस करता है.
छोटे और बार-बार इस्तेमाल होने वाले साउंड इफ़ेक्ट लोड होने पर डीकंप्रेस करें प्लेबैक के दौरान रनटाइम सीपीयू ओवरहेड से बचने के लिए, लोड होने पर ऑडियो को रैम में डीकंप्रेस करता है.

लोड टाइप के अलावा, मेमोरी ओवरहेड को और कम करने के लिए, ऑडियो के ये कॉन्फ़िगरेशन लागू करें:

  • नॉन-डायरेक्शनल ऑडियो के लिए, 'फ़ोर्स टू मोनो' की सुविधा चालू करना: कई ऑडियो ऐसेट—जैसे कि यूज़र इंटरफ़ेस (यूआई) साउंड इफ़ेक्ट, वॉइस-ओवर, और 3D स्पेशलाइज़्ड साउंड इफ़ेक्ट—के लिए स्टीरियो चैनल सेपरेशन की ज़रूरत नहीं होती. इन ऐसेट के लिए, फ़ोर्स टू मोनो की सुविधा चालू करें. इससे, इनकी AudioClipमेमोरी फ़ुटप्रिंट और बफ़र साइज़ आधी (50% कम) हो जाती है.
  • डिवाइस टियर के हिसाब से, ऐक्टिव ऑडियो चैनल को थ्रॉटल करना: हर ऐक्टिव ऑडियो वॉइस नेटिव डीएसपी मिक्सर और डीकंप्रेशन बफ़र को एलोकेट करती है. कम मेमोरी वाले डिवाइसों पर, एक साथ ऐक्टिव चैनलों की ज़्यादा से ज़्यादा संख्या कम करें. उदाहरण के लिए, मैक्स रियल वॉइस (जैसे, 32 से 16) को कम करें. इसके लिए, Unity में AudioSettings.Reset का इस्तेमाल करें या तीसरे पक्ष के ऑडियो इंजन में सॉफ़्टवेयर प्लेबैक चैनलों को सीमित करें.

4. ऑब्जेक्ट पूलिंग और रिलीज़ की रणनीतियां लागू करना

सीन ट्रांज़िशन के दौरान, ऑब्जेक्ट पूल में मौजूद अनरिलीज़्ड इंस्टेंस, रिज़र्व्ड मेमोरी को हमेशा के लिए सेव रखते हैं. इससे, बेसलाइन मेमोरी फ़ुटप्रिंट ज़रूरत से ज़्यादा बढ़ जाता है.

  • कार्रवाई: सीन ट्रांज़िशन या कम गतिविधि वाले समय के दौरान, समय-समय पर पूल किए गए इस्तेमाल न किए गए ऑब्जेक्ट को साफ़ करें या ट्रिम करें. इससे Unity को, अन्य एलोकेशन के लिए रिज़र्व की गई जगह को वापस करने या फिर से इस्तेमाल करने की अनुमति मिलती है.

कैटगरी B: गैर-ज़रूरी मेमोरी का इस्तेमाल हटाना

रिडंडेंट ग्राफ़िक ऐसेट और रेंडरिंग टारगेट बफ़र हटाने से, बेसलाइन मेमोरी फ़ुटप्रिंट सीधे तौर पर कम हो जाता है.

1. रेंडर टेक्सचर और कैमरे की डेप्थ ऑप्टिमाइज़ करना

  • डेप्थ/स्टेंसिल बफ़र हटाना: सिर्फ़ कलर डेटा की ज़रूरत वाले रेंडर टेक्सचर के लिए, डेप्थ स्टेंसिल फ़ॉर्मैट को कोई नहीं पर सेट करें.
  • यूआई कैमरे की डेप्थ टेक्सचर बंद करना: यूआई कैमरे के लिए, जहां डेप्थ डेटा की ज़रूरत नहीं है वहां यूआरपी कैमरा सेटिंग में डेप्थ टेक्सचर जनरेशन बंद करें. इससे, CopyDepth पास और उससे जुड़ी जीपीयू टेक्सचर मेमोरी हट जाती है.

2. टेक्सचर और मेश ऑप्टिमाइज़ करना

कैटगरी ऑप्टिमाइज़ेशन से जुड़ी गाइडलाइन
टेक्सचर कंप्रेशन टारगेट प्लैटफ़ॉर्म कंप्रेशन फ़ॉर्मैट (उदाहरण के लिए, Android के लिए ASTC) हमेशा लागू करें.
रीड/राइट की सुविधा चालू है ज़रूरत न होने पर, इसे बंद रखें. इसे चालू करने से, सीपीयू और जीपीयू रैम में टेक्सचर मेमोरी डुप्लीकेट हो जाती है.
मिपमैप यूआई टेक्सचर या किसी तय दूरी पर मौजूद ऑब्जेक्ट के लिए, मिपमैप बंद करें. इससे, ~33% टेक्सचर मेमोरी सेव होती है.
मेश की जटिलता जीपीयू और नेटिव मेमोरी फ़ुटप्रिंट को कम करने के लिए, गैर-ज़रूरी पॉलीगॉन की संख्या और वर्टेक्स स्ट्रीम कम करें.

3. शेडर वैरिएंट हटाना और मेमोरी ऑप्टिमाइज़ करना

यूबर-शेडर (उदाहरण के लिए, यूआरपी लिट शेडर) में #multi_compile और shader_feature कीवर्ड का इस्तेमाल करके, कई सुविधाएं शामिल की जाती हैं. ऑप्टिमाइज़ेशन के बिना, कॉम्बिनेटरियल एक्सप्लोज़न से हज़ारों यूनीक शेडर वैरिएंटबनते हैं. इससे, बिल्ड साइज़ बढ़ जाता है, नेटिव मेमोरी की खपत ज़्यादा होती है, और गेमप्ले के दौरान जीपीयू ड्राइवर कंपाइलेशन में गड़बड़ियां आती हैं.

A. शेडर वैरिएंट मेमोरी ओवरहेड के मैकेनिज़्म

  • कॉम्बिनेटरियल एक्सप्लोज़न: हर कीवर्ड ग्रुप जोड़ने पर, कुल संभावित वैरिएंट तेज़ी से बढ़ते हैं.
  • चंक एलोकेशन आर्किटेक्चर: Unity, कंपाइल किए गए बाइनरी वैरिएंट को कंप्रेस किए गए मेमोरी ब्लॉक में ग्रुप करता है. इन्हें चंक कहा जाता है. डिफ़ॉल्ट रूप से, इनका साइज़ 4 एमबी होता है.
  • नेटिव मेमोरी में ज़्यादा डेटा सेव होना: रनटाइम कोड, किसी चंक में मौजूद एक वैरिएंट का भी अनुरोध करता है, तो पूरे 4 एमबी के चंक को रैम में डीकंप्रेस किया जाता है. अगर ऑप्टिमाइज़ नहीं किया जाता है, तो उन चंक में पैकेज किए गए हज़ारों इस्तेमाल न किए गए वैरिएंट, नेटिव मेमोरी में हमेशा के लिए सेव रहते हैं.

B. Unity का बिल्ट-इन मल्टी-स्टेज स्ट्रिपिंग पाइपलाइन: Unity, टारगेट प्लैटफ़ॉर्म ग्राफ़िक्स सेटिंग और इस्तेमाल न की गई इंजन सुविधाओं (उदाहरण के लिए, फ़ॉग, लाइटमैप, और एक्सआर सेटिंग) के आधार पर, बिल्ड के दौरान गैर-ज़रूरी वैरिएंट को अपने-आप हटा देता है. इसके अलावा, shader_feature वैरिएंट को अपने-आप फ़िल्टर कर दिया जाता है, अगर उनके कीवर्ड का इस्तेमाल प्रोजेक्ट में किसी भी मटीरियल से नहीं किया जाता है. वहीं, #multi_compile वैरिएंट को इस्तेमाल के बावजूद, ज़बरदस्ती शामिल किया जाता है. हालांकि, अगर यूआरपी या पाइपलाइन सेटिंग में रेंडरिंग या पोस्ट-प्रोसेसिंग की सुविधाएं (जैसे, एडिशनल लाइट, रीयल-टाइम शैडो या इस्तेमाल न किए गए UberPost इफ़ेक्ट) चालू रहती हैं, तो Unity यह तय नहीं कर पाता कि आपका कॉन्टेंट उनका इस्तेमाल करता है या नहीं. साथ ही, वह #multi_compile के सभी वैरिएंट जनरेट करता है. इस्तेमाल न की गई पाइपलाइन सेटिंग की समीक्षा करें और उन्हें बंद करें, ताकि वे आपकी रेंडरिंग की असल ज़रूरतों से मेल खाएं और गैर-ज़रूरी वैरिएंट जनरेट होने से बचें.

C. कस्टम ऑटोमेटेड स्ट्रिपिंग आर्किटेक्चर (IPreprocessShaders) स्टैटिक विश्लेषण, रनटाइम C# स्क्रिप्ट (Material.EnableKeyword) का इस्तेमाल करके, डाइनैमिक तौर पर बदले गए कीवर्ड का पता नहीं लगा सकता. इसलिए, स्टैंडर्ड स्ट्रिपिंग अक्सर काफ़ी नहीं होती. यह पक्का करने के लिए कि सिर्फ़ इस्तेमाल किए गए वैरिएंट शामिल किए जाएं, क्यूए टेस्ट सुइट के दौरान Player.log (Editor सेटिंग में लॉग शेडर कंपाइलेशन चालू करके) या प्रोफ़ाइलर ट्रेस (Shader.CreateGPUProgram मार्कर) का इस्तेमाल करके, वैरिएंट इकट्ठा किए जा सकते हैं. इसके बाद, रनटाइम के दौरान कभी भी एक्ज़ीक्यूट न किए गए वैरिएंट को फ़िल्टर करने के लिए, Editor स्क्रिप्ट में IPreprocessShaders.OnProcessShader लागू करें. इससे, बिल्ड में सिर्फ़ ज़रूरी वैरिएंट शामिल किए जा सकेंगे.

D. रिडंडेंट शेडर को एक साथ रखना: लिट और सिंपल लिट जैसे यूबर-शेडर में, कई #multi_compile डायरेक्टिव शामिल होते हैं. साथ ही, कई वैरिएंट जनरेट करने पर, ये ज़्यादा मेमोरी इस्तेमाल करते हैं:

  • एक साथ इस्तेमाल की प्रोफ़ाइल बनाना: यह देखने के लिए कि दोनों शेडर एक ही समय पर ज़्यादा मेमोरी का इस्तेमाल करते हैं या नहीं, मेमोरी का इस्तेमाल की प्रोफ़ाइल बनाएं. साथ ही, यह भी देखें कि आपके प्रोजेक्ट को दोनों की ज़रूरत है या नहीं.
  • एक ही शेडर का इस्तेमाल करना: जब फ़िज़िकली बेस्ड रेंडरिंग (पीबीआर) की ज़रूरत न हो, तो अपनी ऐसेट को कॉन्फ़िगर करें, ताकि लिट शेडर का रिडंडेंट इस्तेमाल न हो. साथ ही, सिंपल लिट का इस्तेमाल करें. उदाहरण के लिए, देखें कि एक्सपोर्ट किए गए FBX मॉडल में, इंपोर्ट के दौरान अनजाने में डिफ़ॉल्ट लिट मटीरियल लागू तो नहीं किए गए हैं.

कैटगरी C: गैर-ज़रूरी जीसी एलोकेशन हटाना

मैनेज्ड हीप में गार्बेज कलेक्शन (जीसी) एलोकेशन से, मेमोरी फ़्रैगमेंटेशन, हीप एक्सपैंशन होता है. साथ ही, जीसी पॉज़ के दौरान फ़्रेम ड्रॉप होते हैं.

1. लैम्डा क्लोज़र एलोकेशन से बचना

जब कोई लैम्डा एक्सप्रेशन, आउटर लोकल वैरिएबल कैप्चर करता है, तो C#, हीप पर एक इंप्लिसिट डिसप्ले क्लास जनरेट करता है. Update के अंदर इसे एक्ज़ीक्यूट करने पर, हर फ़्रेम में क्लोज़र इंस्टेंस एलोकेट होते हैं.

// Bad: Capturing local variable 'targetId' allocates a new closure object on the Heap every frame
void Update()
{
    int targetId = 100;
    Monster target = monsterList.Find(m => m.Id == targetId);
}

// Good 1: Replace with a standard 'for' loop (Recommended: 0 B allocation)
void Update()
{
    int targetId = 100;
    Monster target = null;
    for (int i = 0; i < monsterList.Count; i++)
    {
        if (monsterList[i].Id == targetId)
        {
            target = monsterList[i];
            break;
        }
    }
}

// Good 2: Use a static lambda (C# 9.0+) if no outer variables are captured
Monster target = monsterList.Find(static m => m.Id == 100);

2. stackalloc और Span का इस्तेमाल करना

स्टैक मेमोरी का इस्तेमाल करके, थोड़े समय के लिए इस्तेमाल होने वाली अस्थायी कलेक्शन के लिए, हीप एलोकेशन से बचें.

// Before: Allocates an array on the Heap every call (GC Target)
Vector2[] pos = new Vector2[4];

// After: Utilizes Stack memory using System.Span (0 B Heap Allocation)
System.Span<Vector2> pos = stackalloc Vector2[4];

3. कलेक्शन इटरेट करना ऑप्टिमाइज़ करना

LINQ एक्सटेंशन या ReadOnlyCollection ऐक्सेसर की वजह से होने वाले बॉक्सिंग और एन्यूमरेटर हीप एलोकेशन से बचें.

// Before: LINQ Count() causes internal GetEnumerator() heap allocations
bool hasData = component != null && component.parameters.Count(parameter => parameter.overrideState) > 0;

// After: Replaced with indexer and direct loop iteration
bool hasData = HasDataOptimized(component);

private bool HasDataOptimized(TestComponent component)
{
    if (component == null) return false;

    var count = component.parameters.Count;
    for (var i = 0; i < count; ++i)
    {
        if (component.parameters[i].overrideState)
            return true;
    }
    return false;
}

4. जीसी एलोकेशन से बचने के लिए अन्य नियम

  • Camera.allCameras का इस्तेमाल करने से बचें. ऐसा इसलिए, क्योंकि हर कॉल पर, यह हीप पर एक नया Camera[] कलेक्शन जनरेट करता है. इसके बजाय, कैमरा कलेक्शन को कैश करें और उसे Camera.GetAllCameras(_allCameras) को पास करें.
  • पर IReadOnlyList<T> का foreach इस्तेमाल करने से बचें: इंटरफ़ेस पर इटरेट करने से, स्ट्रक्ट एन्यूमरेटर बॉक्सिंग होती है. इससे, जीसी एलोकेशन जनरेट होते हैं. इसके बजाय, स्टैंडर्ड for लूप का इस्तेमाल करें.
  • कोरोटीन ऑब्जेक्ट कैश करना: WaitForSeconds इंस्टेंस को कैश करें बजाय इसके कि yield return new WaitForSeconds(time); को बार-बार इंस्टैंशिएट करें.
  • डिक्शनरी में कस्टम स्ट्रक्ट की: डिक्शनरी की के तौर पर कस्टम स्ट्रक्ट का इस्तेमाल करने पर, डिफ़ॉल्ट Equals कॉल होता है. इससे, ऑब्जेक्ट बॉक्सिंग ट्रिगर होती है. IEqualityComparer<T> लागू करें और इसे डिक्शनरी कंस्ट्रक्टर को पास करें.
public struct TypeKey
{
    public int v1;
    public int v2;

    public class TypeKeyComparer : IEqualityComparer<TypeKey>
    {
        public bool Equals(TypeKey x, TypeKey y) => x.v1 == y.v1 && x.v2 == y.v2;
        public int GetHashCode(TypeKey obj) => obj.v1.GetHashCode() ^ obj.v2.GetHashCode();
    }
}

// Pass custom comparer during Dictionary initialization to prevent boxing
public readonly Dictionary<TypeKey, int> _typeKeyDictionary = new(new TypeKey.TypeKeyComparer());

5. जेनेरिक और रिफ़्लेक्शन का इस्तेमाल कम करना

जेनेरिक तरीके, कोड को फिर से इस्तेमाल करने और उसे बनाए रखने के लिए बेहतरीन होते हैं. हालांकि, Unity IL2CPP (इंटरमीडिएट लैंग्वेज से C++) बैकएंड के संदर्भ में, इनका ज़्यादा इस्तेमाल करने से आपके प्रोजेक्ट पर बुरा असर पड़ सकता है.

  • IL2CPP कोड में ज़्यादा डेटा सेव होना: हर यूनीक जेनेरिक टाइप कॉम्बिनेशन के लिए, IL2CPP कोड का एक खास वर्शन जनरेट करता है. मुश्किल जेनेरिक का ज़्यादा इस्तेमाल करने से, जनरेट किए गए C++ कोड का "कॉम्बिनेटरियल एक्सप्लोज़न" हो सकता है. इससे, ऐप्लिकेशन का बाइनरी साइज़ और नेटिव मेमोरी फ़ुटप्रिंट काफ़ी बढ़ जाता है.
  • रिफ़्लेक्शन ओवरहेड: रिफ़्लेक्शन का इस्तेमाल करने वाले तरीके, जैसे कि System.Reflection एपीआई, स्वाभाविक रूप से धीमे होते हैं. साथ ही, रनटाइम के दौरान अक्सर हीप एलोकेशन की वजह बनते हैं.
  • सबसे सही तरीका: जेनेरिक का इस्तेमाल सोच-समझकर करें. इन्हें आर्किटेक्चरल क्लैरिटी के लिए प्राथमिकता दें. साथ ही, इन्हें बिना सोचे-समझे हर जगह इस्तेमाल न करें. जहां परफ़ॉर्मेंस ज़रूरी है वहां, कॉन्क्रीट टाइप या इंटरफ़ेस-बेस्ड पॉलीमॉर्फ़िज़्म का इस्तेमाल करें. रिफ़्लेक्शन के लिए, अपडेट लूप में क्वेरी करने के बजाय, इनिशियलाइज़ेशन के दौरान MethodInfo या FieldInfo जैसे नतीजों को कैश करें.

6. मैनेज्ड शेल लीक होने से बचना

MonoBehaviour, Texture या GameObject जैसे हर UnityEngine.Object में, C# "मैनेज्ड शेल" रैपर होता है. यह रैपर, नेटिव C++ इंजन के साथ कम्यूनिकेट करता है.

  • समस्या: अगर किसी मैनेज्ड शेल को स्टैटिक रेफ़रंस, परसिस्टेंट इवेंट सदस्यता या अनक्लीन्ड क्लोज़र की मदद से मेमोरी में सेव रखा जाता है, तो जीसी उस मेमोरी को वापस नहीं ले सकता. नेटिव ऑब्जेक्ट के डिस्ट्रॉय होने के बाद भी, मैनेज्ड रैपर बना रहता है. इससे, "घोस्ट" मेमोरी लीक होती है. इससे, मैनेज्ड हीप में ज़्यादा डेटा सेव होता है.
  • समाधान: हमेशा मज़बूत क्लीनअप पैटर्न लागू करें. ऑब्जेक्ट डिस्ट्रॉय करते समय या सीन ट्रांज़िशन के दौरान, -= ऑपरेटर का इस्तेमाल करके, इवेंट से साफ़ तौर पर अनसब्सक्राइब करें. साथ ही, UnityEngine.Object टाइप के स्टैटिक रेफ़रंस को नल करें. इस क्लीनअप से, जीसी रैपर को आसानी से इकट्ठा कर सकता है. ऐसा तब होता है, जब नेटिव इंजन अपना हैंडल रिलीज़ कर देता है.

7. Addressables के लिए बाइनरी कैटलॉग का इस्तेमाल करना

Unity 2022 और इससे पहले के वर्शन में, Addressables डिफ़ॉल्ट रूप से JSON कैटलॉग का इस्तेमाल करता है. वहीं, Unity 2023 और इसके बाद के वर्शन में, डिफ़ॉल्ट रूप से बाइनरी कैटलॉग का इस्तेमाल किया जाता है. अगर आपका प्रोजेक्ट JSON कैटलॉग का इस्तेमाल करता है, तो स्टार्टअप के दौरान बड़ा कैटलॉग लोड करने पर, पूरी फ़ाइल को एक बड़े C# स्ट्रिंग में पढ़ा जाता है. साथ ही, JSON पार्सिंग और Base64 डिकोडिंग के दौरान, हज़ारों मैनेज्ड स्ट्रिंग और अस्थायी byte[] कलेक्शन एलोकेट होते हैं. इससे, जीसी एलोकेशन स्पाइक होता है. इससे, मैनेज्ड हीप हमेशा के लिए बढ़ जाता है. इसलिए, सीधे कंपैक्ट डेटा स्ट्रीम करने के लिए, बाइनरी कैटलॉग पर स्विच करें.