سؤال

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

لنفترض أن لدي فصل دراسي


class Calculator
{
  int Value1;
  int Value2;
  //.......... 
  int ValueN;

  void DoCalc()
  {
    if (Value1 > 0)
    {
      DoValue1RelatedStuff();    
    }
    if (Value2 > 0)
    {
      DoValue2RelatedStuff();    
    }
    //....
    //....
    //....
    if (ValueN > 0)
    {
      DoValueNRelatedStuff();    
    }
  }
}

تكون طريقة DoCalc في أدنى مستوى ويتم استدعاؤها عدة مرات أثناء الحساب.جانب آخر مهم هو أن ValueN يتم تعيينها فقط في البداية ولا تتغير أثناء الحساب.الكثير من ifs في طريقة DoCalc غير ضرورية، حيث أن العديد من ValueN هي 0.لذلك كنت آمل أن يساعد إنشاء التعليمات البرمجية الديناميكية في تحسين الأداء.

على سبيل المثال إذا قمت بإنشاء طريقة


  void DoCalc_Specific()
  {
    const Value1 = 0;
    const Value2 = 0;
    const ValueN = 1;

    if (Value1 > 0)
    {
      DoValue1RelatedStuff();    
    }
    if (Value2 > 0)
    {
      DoValue2RelatedStuff();    
    }
    ....
    ....
    ....
    if (ValueN > 0)
    {
      DoValueNRelatedStuff();    
    }
  }

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

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

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

هناك أيضًا Reflection.Emit، لكنني لا أريد الاستمرار فيه لأنه سيكون من الصعب جدًا صيانته.

بالمناسبة.أنا لست مقيدًا بـ C#.لذلك أنا منفتح على اقتراحات لغات البرمجة الأكثر ملاءمة لهذا النوع من المشاكل.باستثناء LISP لعدة أسباب.

توضيح واحد مهم.DoValue1RelatedStuff() ليس استدعاء أسلوب في الخوارزمية الخاصة بي.إنها مجرد عملية حسابية تعتمد على الصيغة وهي سريعة جدًا.كان يجب أن أكتبها هكذا


if (Value1 > 0)
{
  // Do Value1 Related Stuff
}

لقد قمت بإجراء بعض اختبارات الأداء ويمكنني أن أرى أنه مع وجود حالتين عند تعطيل أحدهما، تكون الطريقة المحسنة أسرع بحوالي 2 مرات من حالة if المتكررة.

إليك الرمز الذي استخدمته للاختبار:


    public class Program
    {
        static void Main(string[] args)
        {
            int x = 0, y = 2;

            var if_st = DateTime.Now.Ticks;
            for (var i = 0; i  < 10000000; i++)
            {
                WithIf(x, y);
            }
            var if_et = DateTime.Now.Ticks - if_st;
            Console.WriteLine(if_et.ToString());

            var noif_st = DateTime.Now.Ticks;
            for (var i = 0; i  < 10000000; i++)
            {
                Without(x, y);
            }
            var noif_et = DateTime.Now.Ticks - noif_st;
            Console.WriteLine(noif_et.ToString());

            Console.ReadLine();

        }

        static double WithIf(int x, int y)
        {
            var result = 0.0;
            for (var i = 0; i  < 100; i++)
            {
                if (x > 0)
                {
                    result += x * 0.01;
                }
                if (y > 0)
                {
                    result += y * 0.01;
                }
            }
            return result;
        }

        static double Without(int x, int y)
        {
            var result = 0.0;
            for (var i = 0; i < 100; i++)
            {
                result += y * 0.01;
            }
            return result;
        }
    }
هل كانت مفيدة؟

المحلول

عادةً لا أفكر في مثل هذا التحسين.كم من العمل يفعل DoValueXRelatedStuff() يفعل؟أكثر من 10 إلى 50 دورة معالج؟نعم؟هذا يعني أنك ستقوم ببناء نظام معقد جدًا لتوفير أقل من 10% من وقت التنفيذ (وهذا يبدو متفائلًا جدًا بالنسبة لي).ويمكن أن ينخفض ​​هذا بسهولة إلى أقل من 1%.

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

هل يمكن أن تعطي أمر N, وتعقيد الطريقة النموذجية، ونسبة التعبيرات التي يتم تقييمها عادةً إلى الحقيقة؟

نصائح أخرى

سوف يفاجئني إيجاد سيناريو حيث يقيس النفقات العامة لو البيانات تستحق الجهد المبذول لإبعاد رمز ديناميكي.

دعم وحدة المعالجة المركزية الحديثة التنبؤ الفرع و فرع التنبؤ, ، مما يجعل النفقات العامة للفروع في شرائح صغيرة من الكود نهج الصفر.

هل حاولت تحديد نسختين مشفرتين يدويًا من الكود ، أحدهما يحتوي على جميع الاختبارات IF في مكانه ولكنه يوفر قيمًا صفرًا لمعظم ، وتلك التي تزيل كل تلك الفروع نفسها إذا كانت الفروع؟

إذا كنت حقًا في تحسين التعليمات البرمجية - قبل أن تفعل أي شيء - قم بتشغيل Profiler! سوف يوضح لك مكان عنق الزجاجة والمجالات التي تستحق التحسين.

أيضًا - إذا كان اختيار اللغة غير محدود (باستثناء LISP) ، فلن يتغلب أي شيء على التجميع من حيث الأداء ؛)

أتذكر تحقيق بعض السحر الأداء عن طريق إعادة كتابة بعض الوظائف الداخلية (مثل الواحدة التي لديك) باستخدام Assembler.

قبل أن تفعل أي شيء ، هل لديك بالفعل مشكلة?

أي هل تم تشغيله لفترة كافية لإزعاجك؟

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

الآن ، أنت تتحدث عن التفسير مقابل التجميع. الرمز المفسر هو عادة 1-2 أوامر ذات حجم أبطأ من الكود المترجم. والسبب هو أن المترجمين الفوريين يكتشفون باستمرار ما يجب القيام به بعد ذلك ، ثم نسيان, ، بينما تم تجميع الكود فقط يعرف.

إذا كنت في هذه الحالة ، فقد يكون من المنطقي دفع سعر الترجمة للحصول على سرعة الكود المترجم.

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