c# - مقارنة زائدة و "إذا" قبل التعيين




.net if-statement (5)

هنا هو المثال:

if(value != ageValue) {
  ageValue = value;
}

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

هذا يربكني. هنا هو السياق الأوسع:

private double ageValue;
public double Age {
  get {
    return ageValue;
  }

  set {
    if(value != ageValue) {
      ageValue = value;
    }
  }
}

الأداء ليس مشكلة كبيرة ، يعتمد فقط على احتياجاتك المنطقية.


في عنصر تحكم winforms ، قمنا بتعيين BackgroundColor على لون معين:

myControl.BackgroundColor = Color.White

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

if (myControl.BackgroundColor != Color.White)
    myControl.BackgroundColor = Color.White

وقد عاد أداء أداتنا إلى المسار الصحيح (ثم استبعدنا سبب الحلقة الضيقة).

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


لقد اكتسب هذا السؤال بعض التعليقات ولكن حتى الآن تحاول جميع الإجابات إعادة صياغة السؤال لمعالجة المشكلات المتعلقة بالحمل الزائد للمشغل أو الآثار الجانبية للمضبط.

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

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

يعرض نموذج التطبيق التالي هذا التأثير الذي يحتوي أيضًا على التحقق قبل شرط الكتابة:

 if (tmp1 != checkValue)  // set only if not equal to checkvalue
 {
    values[i] = checkValue;
 }

هنا هو الكود الكامل:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        const int N = 500_000_000;
        int[] values = new int[N]; // 2 GB
        for (int nThreads = 1; nThreads < Environment.ProcessorCount; nThreads++)
        {
            SetArray(values, checkValue: 1, nTimes: 10, nThreads: nThreads);
            SetArray(values, checkValue: 2, nTimes: 10, nThreads: nThreads);
            SetArrayNoCheck(values, checkValue: 2, nTimes: 10, nThreads: nThreads);
        }
    }

    private static void SetArray(int[] values, int checkValue, int nTimes, int nThreads)
    {
        List<double> ms = new List<double>();

        for (int k = 0; k < nTimes; k++)  // set array values to 1
        {
            for (int i = 0; i < values.Length; i++)
            {
                values[i] = 1;
            }

            var sw = Stopwatch.StartNew();
            Action acc = () =>
            {
                int tmp1 = 0;
                for (int i = 0; i < values.Length; i++)
                {
                    tmp1 = values[i];
                    if (tmp1 != checkValue)  // set only if not equal to checkvalue
                    {
                        values[i] = checkValue;
                    }
                }
            };

            Parallel.Invoke(Enumerable.Repeat(acc, nThreads).ToArray());  // Let this run on 3 cores

            sw.Stop();
            ms.Add(sw.Elapsed.TotalMilliseconds);
            //  Console.WriteLine($"Set {values.Length * 4 / (1_000_000_000.0f):F1} GB of Memory in {sw.Elapsed.TotalMilliseconds:F0} ms. Initial Value 1. Set Value {checkValue}");
        }
        string descr = checkValue == 1 ? "Conditional Not Set" : "Conditional Set";
        Console.WriteLine($"{descr}, {ms.Average():F0}, ms, nThreads, {nThreads}");

    }

    private static void SetArrayNoCheck(int[] values, int checkValue, int nTimes, int nThreads)
    {
        List<double> ms = new List<double>();
        for (int k = 0; k < nTimes; k++)  // set array values to 1
        {
            for (int i = 0; i < values.Length; i++)
            {
                values[i] = 1;
            }

            var sw = Stopwatch.StartNew();
            Action acc = () =>
            {
                for (int i = 0; i < values.Length; i++)
                {
                        values[i] = checkValue;
                }
            };

            Parallel.Invoke(Enumerable.Repeat(acc, nThreads).ToArray());  // Let this run on 3 cores

            sw.Stop();
            ms.Add(sw.Elapsed.TotalMilliseconds);
            //Console.WriteLine($"Unconditional Set {values.Length * 4 / (1_000_000_000.0f):F1} GB of Memory in {sw.Elapsed.TotalMilliseconds:F0} ms. Initial Value 1. Set Value {checkValue}");
        }
        Console.WriteLine($"Unconditional Set, {ms.Average():F0}, ms, nThreads, {nThreads}");
    }
}

إذا تركت ذلك المدى ، فستحصل على قيم مثل:

// Value not set
Set 2.0 GB of Memory in 439 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 420 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 429 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 393 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 404 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 395 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 419 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 421 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 442 ms. Initial Value 1. Set Value 1
Set 2.0 GB of Memory in 422 ms. Initial Value 1. Set Value 1
// Value written
Set 2.0 GB of Memory in 519 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 582 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 543 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 484 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 523 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 540 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 552 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 527 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 535 ms. Initial Value 1. Set Value 2
Set 2.0 GB of Memory in 581 ms. Initial Value 1. Set Value 2

ينتج عن ذلك أداء أسرع بنسبة 22٪ مما قد يكون مهمًا في سيناريوهات رقم الأداء العالي.

للإجابة على السؤال كما هو مكتوب:

يمكنك إزالة عبارة if إذا كان الوصول إلى الذاكرة مترابطًا فقط. إذا كانت مؤشرات ترابط متعددة تعمل على نفس البيانات أو في مكان قريب ، فقد تحدث مشاركة خاطئة مما قد يكلفك ما يصل إلى 20 ٪ من أداء الوصول إلى الذاكرة.

تحديث 1 لقد قمت بإجراء مزيد من الاختبارات وقمت بإنشاء مخطط لإظهار الدردشة المتقاطعة الأساسية. يعرض هذا مجموعة بسيطة ( مجموعة غير مشروطة ) كما لاحظها المعلق فرانك هوبكنز. يحتوي الشرط غير المشروط على القيمة if التي لا تحدد القيمة مطلقًا. وأخيرًا وليس آخرًا ، ستقوم المجموعة الشرطية بتعيين القيمة في حالة الشرط.


لقد قمت بالفعل بترميز مثل هذه الأشياء عدة مرات ، لأسباب مختلفة. من الصعب تفسير ذلك ، لذا احمل معي.

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

أحاول تقسيم حالات الاستخدام مثل هذا:

  1. القيمة هي نوع بيانات مجردة ، حيث قد يكون لديك مثيلات مبنية مختلفة تمثل نفس القيمة المنطقية.

    • يحدث هذا كثيرًا في برامج الرياضيات ، مثل Mathematica ، حيث لا يمكنك استخدام الأرقام البدائية ، مما يتيح لك أن تنتهي بكائنات مختلفة تهدف إلى تمثيل نفسه.
  2. مرجع value مفيد لمنطق التخزين المؤقت.

    • يمكن أن يظهر ذلك أيضًا عند استخدام الأرقام المجردة. على سبيل المثال ، إذا كنت تتوقع أن تقوم أجزاء أخرى من البرنامج بتخزين بيانات مخزنة مؤقتًا عن مرجع ، فلا ترغب في استبدالها بمرجع مكافئ منطقيًا ، حيث إنه سيؤدي إلى إبطال ذاكرات التخزين المؤقت المستخدمة في مكان آخر.
  3. أنت تستخدم مقيم تفاعلي ، حيث قد يؤدي تعيين قيمة جديدة إلى فرض سلسلة من ردود الفعل على التحديثات.

    • بالضبط كيف ولماذا يختلف هذا الأمر حسب السياق.

تتمثل النقطة المفاهيمية الكبيرة في أنه في بعض الحالات ، يمكن أن يكون لديك نفس القيمة المنطقية المخزنة في مراجع مختلفة ، لكنك ترغب في محاولة تقليل عدد المراجع المنحطة لسببين رئيسيين:

  1. وجود نفس القيمة المنطقية المخزنة عدة مرات يخزن المزيد من الذاكرة.

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

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

حالة استخدام أخرى ، مرة أخرى مع أنواع البيانات المجردة ، هي المكان الذي قد يكون لديك فيه خصائص تم تقييمها بتكاسل. على سبيل المثال ، لنفترض أن لديك abstract class Number يحتوي على خصائص مثل .IsRational ، .IsEven ، وما إلى ذلك. ثم ، قد لا تقوم بحساب تلك فورًا ، ولكن يمكنك .IsRational ، .IsEven . في سيناريو مثل هذا ، قد تميل إلى تفضيل الاحتفاظ بالأرقام القديمة بنفس القيمة المنطقية حيث قد تحتوي على المزيد من الأشياء المرتبطة بها ، في حين أن value الجديدة قد تحتوي على معلومات أقل مرتبطة بها ، حتى لو كانت منطقية == .

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


if ، عند التفتيش ، ليست زائدة عن الحاجة. ذلك يعتمد على التنفيذ المتبقي. لاحظ أنه في C # ، != يمكن زيادة التحميل ، مما يعني أن التقييم يمكن أن يكون له آثار جانبية. علاوة على ذلك ، يمكن تنفيذ المتغيرات المحددة كخصائص ، والتي يمكن أن يكون لها أيضًا آثار جانبية على التقييم.







if-statement