هل هناك ممارسة شائعة كيفية جعل الذاكرة المجانية لجامع القمامة أسهل في .NET؟

StackOverflow https://stackoverflow.com/questions/2714811

سؤال

لقد كنت أفكر إذا كانت هناك طريقة كيفية تسريع تحرير الذاكرة في .NET. أقوم بإنشاء لعبة في .NET (رمز مُدار فقط) حيث لا توجد حاجة إلى رسومات مهمة ولكن ما زلت أرغب في كتابتها بشكل صحيح حتى لا أفقد الأداء مقابل لا شيء.

على سبيل المثال ، هل من المفيد تعيين قيمة خالية للكائنات التي لم تعد مطلوبة؟ أرى هذا في بعض العينات عبر الإنترنت.

هل كانت مفيدة؟

المحلول

هل من المفيد تعيين قيمة خالية للكائنات التي لم تعد مطلوبة؟

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

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

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

نصائح أخرى

يجب ألا تقلق كثيرًا ولا تتحسن قبل الأوان. إدارة الذاكرة في .NET هي تلقائية وفعالة للغاية. إذا بدأت في "تحسين" ، فأنت بحاجة إلى معرفة ما تفعله بالضبط أو قد تبطئه.

لذا ، أنهى لعبتك أولاً ، ثم إذا كان بطيئًا جدًا ، فاستخدم Pforler للعثور على المشكلة. على الأرجح لن تكون مشكلة في الذاكرة.

معظم الكائنات ذات الصلة بالرسومات في .NET (الصورة وذات أحفادها ، الرسومات ، القلم ، الفرشاة ، إلخ) تنفذ idisposable. إذا كنت ستستخدم كائنًا لعملية معينة ، فلا مرة أخرى ، استخدم النمط التالي:

using(var g = Graphics.FromBitmap(bmp))
{
    //Do some stuff with the graphics object
}

إن القيام بذلك مثل هذا سيضمن أن يتم إطلاق سراح أي موارد غير مُدارة عندما تسقط خارج نطاقها.

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

internal class ListFactory<T> 
    where T: IRecyclable, new()
{
    private List<T> _internalList;
    private int _pointer;

    public ListFactory()
    {
        _internalList = new List<T>();
        _pointer = 0;
    }

    public void Clear()
    {
            _pointer = 0;
    }

    public int Count
    {
        get
        {
            return _pointer;
        }
    }

    public List<T> InnerList
    {
        get
        {
            return _internalList;
        }
    }

    //Either return T form the list or add a new one
    //Clear T when it is being reused
    public T Create()
    {
        T t;

        //If the pointer is less than the object count then return a recycled object
        //Else return a new object 
        if (_pointer < _internalList.Count )
        {
            t = _internalList[_pointer];
            t.Recycle();
        }
        else
        {
            t = new T();
            _internalList.Add(t);
        }
        _pointer ++;
        return t;
    }
}

لخوارزمية توجيه الخط ، أحتاج إلى الحفاظ باستمرار على العديد من القيم ك Routenode الذي ينفذ الواجهة التالية:

public interface IRecyclable
{
    void Recycle();
}

هذه يتم إنشاؤها وتدميرها باستمرار. لإعادة تدوير هذه الكائنات ، قم بإنشاء مصنع جديد:

nodeFactory = new ListFactory<RouteNode>();

عندما تحتاج إلى كائن ، اتصل بالطريقة إنشاء:

RouteNode start = nodeFactory.Create();
RouteNode goal = nodeFactory.Create();

عندما تنتهي من الكائنات في القائمة ، قم بمسح القائمة. يتم إعادة تعيين المؤشر إلى بداية القائمة ، لكن الكائنات نفسها لم يتم تدميرها. في الواقع ، تسمى طريقة إعادة التدوير فقط عندما يتم استرداد الكائن مرة أخرى. (قد ترغب في القيام بذلك في وقت سابق إذا كان لديك مراجع كائنات أخرى ، انظر التعليقات أدناه)

هذا تطبيق ساذج إلى حد ما ، لكنه مكان للبدء.

قد يكون من المفيد في بعض الحالات تعيين كائنات على NULL التي لم تعد مطلوبة. غالبًا ما يكون عديمة الفائدة أو عكسية ، ولكن ليس دائمًا. أربع حالات رئيسية عندما تكون مفيدة:

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

  • للقيام بذلك (واضطررت إلى القيام بذلك بتخصيصات متكررة> 50 ميجابايت) ، اتصل:

            myObj = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
    

    لقد لاحظت أن بصمة الذاكرة للتطبيق ستقل إلى حد كبير. من الناحية النظرية ، يجب ألا تحتاج إلى القيام بذلك. ومع ذلك ، في الممارسة العملية ، مع نظام التشغيل Windows 32 بت ، قد تحصل على كتلتين متجاورة من> 300 ميجابايت خالية في أي وقت معين ، وتولي هذه المساحة الكثير من المخصصات الصغيرة أو سلسلة من الكبار يمكن أن تعني أن التخصيصات الكبيرة الأخرى سوف فشل دون داع. يعمل جامع القمامة في الخلفية عندما يكون الأمر كذلك ، ولكن إذا كان عليك تمامًا تقديم مخصصات كبيرة في الوقت الحالي ، فإن مجموعة الخطوط تساعد على جعل ذلك ممكنًا بالنسبة لي.

    تحرير: من ما أضعه في التعليقات ، للهبوطين.

    إذا قرأت كامل بعد مجموعة القمامة من قبل ريكو مارياني, ، ستلاحظ أن تخصيصات الذاكرة الكبيرة غير المتكررة وغير القابلة للتنفيذ تقع في السيناريو رقم 2. إلى وايت:

    القاعدة رقم 2

    فكر في استدعاء gc.collect () إذا حدث بعض الأحداث غير المتكررة للتو ومن المرجح أن يكون هذا الحدث قد تسبب في وفاة الكثير من الأشياء القديمة.

    مثال كلاسيكي على ذلك هو إذا كنت تكتب تطبيق عميل وعرضت نموذجًا كبيرًا ومعقدًا للغاية يحتوي على الكثير من البيانات المرتبطة به. لقد تفاعل المستخدم للتو مع هذا النموذج الذي يحتمل أن يقوم بإنشاء بعض الكائنات الكبيرة ... أشياء مثل مستندات XML ، أو مجموعة بيانات كبيرة أو اثنين. عندما يغلق النموذج ، فإن هذه الكائنات قد ماتت ، وبالتالي فإن GC.Collect () سوف يسترجع الذاكرة المرتبطة بها.

    الآن لماذا أقترح هذا كوقت محتمل للاتصال بالجمع؟ أعني ، أن نصيحتي المعتادة تذهب إلى "جامع هو صقل ذاتي ، لذلك لا تعبث معها". لماذا تغيير الموقف الذي قد تسأله؟

    حسنًا ، هنا موقف حيث من المحتمل أن يكون ميل جامع [كذا] التنبؤ بالمستقبل بناءً على الماضي غير ناجح.

    إذا قمت بتخصيص مخصصات كبيرة في لعبتك ، فستحتاج إلى توخي الحذر بشأن كيفية معالجة الذاكرة. يعمل جامع القمامة على التنبؤ بناءً على الأحداث الماضية ، ويمكن أن تكون كتل الذاكرة الكبيرة على آلة 32 بت أن تكون مدمرة للمخصصات المستقبلية إذا لم تتم إدارتها بشكل صحيح. إذا لم تكن قد فعلت ذلك ، فلا تفترض تلقائيًا أنني مخطئ ؛ إذا كنت قد فعلت ذلك ، أرحب بتوضيح لكيفية القيام بذلك بشكل صحيح (أي ، كيفية تحرير الذاكرة للتأكد من أنه يمكنني دائمًا تخصيص الذاكرة من 50 إلى 100 ميجابايت في وقت معين).

    مرخصة بموجب: CC-BY-SA مع الإسناد
    لا تنتمي إلى StackOverflow
    scroll top