c# - متى تكون البنيات هي الجواب؟




performance struct (8)

أقوم بمشروع هواية raytracer ، وكنت أصلا باستخدام هياكل لأجاصتي Vector و Ray ، واعتقدت أن raytracer كان الوضع المثالي لاستخدامها: أنت تخلق الملايين منهم ، لا يعيشون أطول من واحد الطريقة ، فهي خفيفة الوزن. ومع ذلك ، ببساطة عن طريق تغيير "البنية" إلى "الفئة" على Vector و Ray ، حصلت على مكسب كبير جدًا للأداء.

ما يعطي؟ انهم على حد سواء صغيرة (3 يطفو ل Vector ، 2 Vectors لراي) ، لا يتم نسخها بشكل مفرط. أقوم بتمريرهم إلى الأساليب عند الحاجة بالطبع ، لكن هذا أمر لا مفر منه. فما هي المخاطر الشائعة التي تقتل الأداء عند استخدام البنى؟ لقد قرأت this المقالة MSDN التي تقول ما يلي:

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

على الرغم من أنه قديم جدا (2001) و "وضعهم في صفيف يتسبب في ملاكمة / فك الصندوق". هل هذا صحيح؟ ومع ذلك ، قمت بحساب الأشعة الأولية ووضعها في صفيف ، لذا تناولت هذا المقال وحسبت الأشعة الأولية عندما احتجت إليها ولم أقم بإضافتها إلى مصفوفة ، لكنها لم تغير أي شيء: مع الطبقات ، كان لا يزال أسرع 1.5x.

أقوم بتشغيل .NET 3.5 SP1 الذي أعتقد أنه مشكلة ثابتة حيث لم تكن أي من أساليب الإنشاء مبطنة ، لذا لا يمكن ذلك أيضًا.

ذلك أساسا: أي نصائح ، وأشياء للنظر فيها وما يجب تجنبه؟

تعديل: كما هو مقترح في بعض الإجابات ، قمت بإعداد مشروع اختبار حاولت فيه تمرير المراجع كمرجع. طرق لإضافة اثنين من المتجهات:

public static VectorStruct Add(VectorStruct v1, VectorStruct v2)
{
  return new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

public static VectorStruct Add(ref VectorStruct v1, ref VectorStruct v2)
{
  return new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

public static void Add(ref VectorStruct v1, ref VectorStruct v2, out VectorStruct v3)
{
  v3 = new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

لكل واحد حصلت على تباين من الأسلوب القياسي التالي:

VectorStruct StructTest()
{
  Stopwatch sw = new Stopwatch();
  sw.Start();
  var v2 = new VectorStruct(0, 0, 0);
  for (int i = 0; i < 100000000; i++)
  {
    var v0 = new VectorStruct(i, i, i);
    var v1 = new VectorStruct(i, i, i);
    v2 = VectorStruct.Add(ref v0, ref v1);
  }
  sw.Stop();
  Console.WriteLine(sw.Elapsed.ToString());
  return v2; // To make sure v2 doesn't get optimized away because it's unused. 
}

كل يبدو لأداء متطابقة إلى حد كبير. هل من الممكن أن يتم تحسينها من خلال JIT إلى أي طريقة هي الطريقة المثلى لتمرير هذه البنية؟

EDIT2: يجب أن أشير إلى أن استخدام البنى في مشروع الاختبار الخاص بي هو أسرع بنسبة 50٪ من استخدام الفصل الدراسي. لماذا هذا يختلف عن رايتيكر بلدي لا أعرف.


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

مع تمرير المزيد من الذاكرة ، من المحتم أن تسبب ذاكرة التخزين المؤقت.


أستخدم البُنى بشكل أساسي لأجسام المعلمات ، وإرجاع أجزاء متعددة من المعلومات من إحدى الوظائف ، و ... شيء آخر. لا أعرف ما إذا كان "صحيح" أو "خطأ" ، ولكن هذا ما أفعله.


أول شيء أود أن أبحث عنه هو التأكد من أنك قمت بتطبيق Equals و GetHashCode بشكل صريح. الفشل في القيام بذلك يعني أن تنفيذ وقت التشغيل لكل من هذه العمليات يقوم بعمليات مكلفة للغاية لمقارنة حالتين من البنى (داخليًا يستخدم الانعكاس لتحديد كل حقل من الحقول الخاصة ثم يتحقق منها للمساواة ، وهذا يتسبب في قدر كبير من التخصيص) .

عموما ، أفضل شيء يمكنك القيام به هو تشغيل التعليمات البرمجية الخاصة بك تحت منشئ ملفات التعريف ونرى أين الأجزاء البطيئة. يمكن أن يكون تجربة فتح العين.


إذا كانت البنيات صغيرة ، ولم تكن موجودة كثيرًا في وقت واحد ، فيجب أن تضعها على المكدس (طالما كان متغيرًا محليًا وليس عضوًا في الفصل الدراسي) وليس على الكومة ، فهذا يعني أن GC isn ' ر يجب أن يتم استحضارها ويجب أن يكون تخصيص الذاكرة / إلغاء التوزيع فوريًا تقريبًا.

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

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


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

على وجه التحديد ، يجب أن تستوفي أنواع البنية جميع هذه المعايير:

  • يمثل منطقيا قيمة واحدة
  • له حجم مثيل أقل من 16 بايت
  • لن تتغير بعد الخلق
  • لن يتم إرسالها إلى نوع مرجع

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

ومع ذلك ، تحتوي البُنى على دلالات تخصيص مختلفة تمامًا ولا يمكن أيضًا أن تكون موروثة. لذلك ، عادة ما أتجنب البنى باستثناء أسباب الأداء المحددة عند الحاجة.

البنية

يشبه صفيف القيم v المشفرة بواسطة struct (نوع القيمة) هذا في الذاكرة:

VVVV

صف دراسي

تظهر مجموعة من القيم v التي تم ترميزها بواسطة فئة (نوع المرجع) كالتالي:

PPPP

..v..v ... ت ت.

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


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


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

بالنسبة لتباطؤك المحدد - ربما نحتاج إلى رؤية بعض الرموز.







struct-vs-class