كيف يتم استدعاء هذه الطريقة الافتراضية بشكل أسرع من استدعاء الطريقة المختومة؟

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

سؤال

أقوم ببعض العبث على أداء الأعضاء الافتراضية VS المختومة.

أدناه هو رمز الاختبار الخاص بي.

الإخراج هو

virtual total 3166ms
per call virtual 3.166ns
sealed total 3931ms
per call sealed 3.931ns

يجب أن أفعل شيئًا خاطئًا لأنه وفقًا لهذا ، فإن المكالمة الافتراضية أسرع من المكالمة المختومة.

أقوم بتشغيل وضع الإصدار مع تشغيل "تحسين الكود".

تحرير: عند الركض خارج VS (كتطبيق وحدة تحكم) ، تكون الأوقات قريبة من الحرارة الميتة. لكن الافتراضية دائما يخرج في المقدمة.

[TestFixture]
public class VirtTests
{

    public class ClassWithNonEmptyMethods
    {
        private double x;
        private double y;

        public virtual void VirtualMethod()
        {
            x++;
        }
        public void SealedMethod()
        {
            y++;
        }
    }

    const int iterations = 1000000000;


    [Test]
    public void NonEmptyMethodTest()
    {

        var foo = new ClassWithNonEmptyMethods();
        //Pre-call
        foo.VirtualMethod();
        foo.SealedMethod();

        var virtualWatch = new Stopwatch();
        virtualWatch.Start();
        for (var i = 0; i < iterations; i++)
        {
            foo.VirtualMethod();
        }
        virtualWatch.Stop();
        Console.WriteLine("virtual total {0}ms", virtualWatch.ElapsedMilliseconds);
        Console.WriteLine("per call virtual {0}ns", ((float)virtualWatch.ElapsedMilliseconds * 1000000) / iterations);


        var sealedWatch = new Stopwatch();
        sealedWatch.Start();
        for (var i = 0; i < iterations; i++)
        {
            foo.SealedMethod();
        }
        sealedWatch.Stop();
        Console.WriteLine("sealed total {0}ms", sealedWatch.ElapsedMilliseconds);
        Console.WriteLine("per call sealed {0}ns", ((float)sealedWatch.ElapsedMilliseconds * 1000000) / iterations);

    }

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

المحلول

أنت تختبر آثار محاذاة الذاكرة على كفاءة الكود. يواجه برنامج التحويل البرمجي JIT 32 بت مشكلة في توليد كود فعال لأنواع القيمة التي تزيد عن 32 بت في الحجم ، طويل ومزدوج في رمز C#. جذر المشكلة هو تخصيص كومة GC 32 بت ، إنه يعد فقط بمحاذاة الذاكرة المخصصة على عناوين مضاعفة من 4. وهذا يمثل مشكلة هنا ، فأنت تزداد الزوجي. يكون مزدوج فعالًا فقط عند محاذاة العنوان الذي يمثل مضاعفًا من 8. نفس المشكلة مع المكدس ، في حالة المتغيرات المحلية ، يتم محاذاة أيضًا إلى 4 فقط على جهاز 32 بت.

يتم تنظيم ذاكرة التخزين المؤقت L1 CPU داخليًا في كتل تسمى "خط ذاكرة التخزين المؤقت". هناك عقوبة عندما يقرأ البرنامج مزدوجًا محاذاة. لا سيما تلك التي تتجول في نهاية خط ذاكرة التخزين المؤقت ، يجب قراءة البايتات من خطين ذاكرة التخزين المؤقت واللصق معًا. سوء المحاذاة ليس من غير المألوف في الارتعاش 32 بت ، إنه فقط 50-50 احتمالات أن يتم تخصيص حقل "X" على عنوان مضاعف 8. إذا لم يكن "X" ثم "X" "y" سوف يتم اختلالها ، وقد يتجول أحدهم في خط ذاكرة التخزين المؤقت. الطريقة التي كتبت بها الاختبار ، ستجعل إما VirtualMethod أو SealedMethod أبطأ. تأكد من السماح لهم باستخدام نفس الحقل للحصول على نتائج مماثلة.

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

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

العقوبة شديدة الانحدار ، تقرأ مزدوجًا يمتد خط ذاكرة التخزين المؤقت تقريبًا ثلاثة مرات أبطأ من قراءة واحدة محاذاة. كما يتم تخصيص السبب الأساسي الذي يخصصه مزدوج [] (مجموعة من الزوجي) في كومة الكائنات الكبيرة حتى عندما يكون لديه 1000 عنصر فقط ، جنوب العتبة العادية البالغة 80 كيلو بايت ، فإن LOH لديها ضمان محاذاة لـ 8. مشاكل المحاذاة هذه تختفي بالكامل في الكود الناتج عن ارتعاش X64 ، كل من المكدس وكومة GC لهما محاذاة 8.

نصائح أخرى

أولاً ، عليك وضع علامة على الطريقة sealed.

ثانيا ، تقديم override إلى الطريقة الافتراضية. إنشاء مثيل للفئة المشتقة.

كاختبار ثالث ، قم بإنشاء أ sealed override طريقة.

الآن يمكنك البدء في المقارنة.

يحرر: ربما يجب عليك تشغيل هذا خارج VS.

تحديث:

مثال على ما أعنيه.

abstract class Foo
{
  virtual void Bar() {}
}

class Baz : Foo
{
  sealed override void Bar() {}
}

class Woz : Foo
{
  override void Bar() {}
}

الآن اختبر سرعة الاتصال Bar لمثيل Baz و Woz. كما أشك في أن الأعضاء والرؤية الطبقية خارج الجمعية يمكن أن تؤثر على تحليل JIT.

قد ترى بعض تكلفة بدء التشغيل. حاول لف رمز Test-A/Test-B في حلقة وقم بتشغيله عدة مرات. قد ترى أيضًا نوعًا من تأثيرات الطلب. لتجنب ذلك (وأعلى/أسفل تأثيرات الحلقة) ، قم بفصله 2-3 مرات.

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