كيف ضاعفنا سرعة العمل مع Float في Mono


صديقي آراس كتب مؤخرًا جهاز تتبع الأشعة نفسه بلغات مختلفة ، بما في ذلك C ++ و C # ومترجم Unity Burst. بالطبع ، من الطبيعي أن نتوقع أن يكون C # أبطأ من C ++ ، لكن بدا لي مثيرًا للاهتمام أن Mono أبطأ من .NET Core.

كانت مؤشراته المنشورة ضعيفة:

  • C # (.NET Core): Mac 17.5 Mray / s ،
  • C # (الوحدة ، Mono): Mac 4.6 Mray / s ،
  • C # (الوحدة ، IL2CPP): Mac 17.1 Mray / s

قررت أن أرى ما يحدث وأماكن المستندات التي يمكن تحسينها.

نتيجة لهذا المعيار ودراسة هذه المشكلة ، وجدنا ثلاثة مجالات يمكن فيها تحسين:

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

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

النتائج على منزلي iMac لـ Mono و .NET Core كانت كما يلي:

بيئة العملالنتائج ، MRay / ثانية
.NET Core 2.1.4 ، dotnet run build debug3.6
إصدار .NET Core 2.1.4 build dotnet run -c Release21.7
Vanilla Mono ، mono Maths.exe6.6
الفانيليا مونو مع LLVM و float3215.5

في عملية دراسة هذه المشكلة ، وجدنا اثنين من المشاكل ، بعد التصحيح الذي تم الحصول على النتائج التالية:

بيئة العملالنتائج ، MRay / ثانية
أحادية مع LLVM و float3215.5
المتقدمة مونو مع LLVM ، float32 ومضمنة ثابت29.6

الصورة الكبيرة:


فقط عن طريق تطبيق LLVM و float32 ، يمكنك زيادة أداء رمز النقطة العائمة بمقدار 2.3 مرة تقريبًا. وبعد الضبط ، الذي أضفناه إلى Mono كنتيجة لهذه التجارب ، يمكنك زيادة الإنتاجية بمقدار 4.4 مرة مقارنةً بـ Mono القياسي - ستصبح هذه المعلمات في الإصدارات المستقبلية من Mono هي المعلمات الافتراضية.

في هذه المقالة سأشرح نتائجنا.

32 بت و 64 بت تعويم


يستخدم Aras أرقام الفاصلة العائمة 32 بت للجزء الرئيسي من العمليات الحسابية (اكتب float في C # أو System.Single في .NET). في Mono ، ارتكبنا خطأ منذ وقت طويل - تم إجراء جميع حسابات الفاصلة العائمة 32 بت على أنها 64 بت ، ولا تزال البيانات مخزنة في مناطق 32 بت.

اليوم ، ذاكرتي ليست حادة كما كانت من قبل ، ولا أستطيع أن أتذكر بالضبط سبب اتخاذ هذا القرار.

لا أستطيع إلا أن أفترض أنه تأثر بالاتجاهات والأفكار في ذلك الوقت.

ثم هالة إيجابية تحوم حول الحوسبة العائمة مع زيادة الدقة. على سبيل المثال ، استخدمت معالجات Intel x87 دقة 80 بت لحسابات الفاصلة العائمة ، حتى عندما تكون المعاملات مزدوجة ، مما يوفر للمستخدمين نتائج أكثر دقة.

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

في المراحل الأولية من تطوير Mono ، يمكن أن تتلقى معظم العمليات الرياضية المنفذة على جميع المنصات ضعفًا فقط عند الإدخال. تمت إضافة إصدارات 32 بت إلى C99 و Posix و ISO ، لكن في تلك الأيام لم تكن متوفرة على نطاق واسع للصناعة بأكملها (على سبيل المثال ، sinf هو الإصدار fabsf من sin ، fabsf هو إصدار fabs ، وهكذا).

باختصار ، كانت أوائل العقد الأول من القرن العشرين فترة من التفاؤل.

دفعت التطبيقات ثمناً باهظًا لزيادة وقت الحساب ، لكن Mono كان يستخدم بشكل أساسي لتطبيقات Linux لسطح المكتب التي تقدم صفحات HTTP وبعض عمليات الخادم ، لذلك لم تكن سرعة الفاصلة العائمة هي المشكلة التي واجهناها يوميًا. أصبح ملحوظًا فقط في بعض المعايير العلمية ، وفي عام 2003 نادراً ما تم تطويرها على .NET.

اليوم ، جعلت الألعاب والتطبيقات ثلاثية الأبعاد ومعالجة الصور و VR و AR والتعلم الآلي عمليات الفاصلة العائمة نوعًا شائعًا من البيانات. المشكلة لا تأتي وحدها ، ولا توجد استثناءات. لم يعد Float هو نوع البيانات المألوف المستخدم في الكود في مكانين فقط. لقد تحولوا إلى انهيار جليدي ، لا يوجد مكان للاختباء منه. هناك الكثير منهم ولا يمكن وقف انتشارهم.

علامة مساحة العمل float32


لذلك ، قبل عامين قررنا إضافة دعم لتنفيذ عمليات تعويم 32 بت باستخدام عمليات 32 بت ، كما هو الحال في جميع الحالات الأخرى. أطلقنا على هذه الميزة من مساحة العمل "float32". في Mono ، يتم تمكينه عن طريق إضافة الخيار --O=float32 في بيئة العمل ، وفي تطبيقات Xamarin يتم تغيير هذه المعلمة في إعدادات المشروع.

لقيت هذه العلامة الجديدة استحسانًا جيدًا من قِبل مستخدمي الأجهزة المحمولة لدينا ، نظرًا لأن الأجهزة المحمولة لا تزال غير قوية جدًا ، ويُفضل معالجة البيانات بشكل أسرع من زيادة الدقة. نوصي مستخدمي الهواتف المتحركة بتشغيل برنامج التحويل البرمجي المحسن LLVM وعلم float32 في نفس الوقت.

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

الآن سوف نستخدم Mono float32 ، ويمكن تتبع التقدم هنا: https://github.com/mono/mono/issues/6985 .

في غضون ذلك ، عدت إلى مشروع صديقي أراس. لقد استخدم واجهات برمجة التطبيقات الجديدة التي تمت إضافتها إلى .NET Core. على الرغم من قيام .NET Core دائمًا بإجراء عمليات تعويم 32 بت أثناء تعويم 32 بت ، لا يزال API System.Math ينفذ التحويلات من float إلى double في هذه العملية. على سبيل المثال ، إذا كنت بحاجة إلى حساب دالة الجيب لقيمة تعويم ، فإن الخيار الوحيد هو استدعاء Math.Sin (double) ، وسيكون عليك التحويل من float إلى double.

لإصلاح ذلك ، System.MathF إضافة نوع جديد من System.MathF إلى .NET Core والذي يحتوي على عمليات حسابية بنقطة عائمة مفردة الدقة ، والآن نقلنا هذا [System.MathF] إلى Mono .

تحسن الانتقال من تعويم 64 بت إلى 32 بت بشكل ملحوظ الأداء ، والذي يمكن رؤيته من هذا الجدول:

بيئة العمل والخياراتالعفاريت / الثانية
أحادية مع System.Math6.6
أحادي مع System.Math و -O=float328.1
أحادية مع System.MathF6.5
أحادية مع System.MathF و -O=float328.2

وهذا يعني أن استخدام float32 في هذا الاختبار يحسن الأداء حقًا ، و MathF له تأثير ضئيل.

إعداد LLVM


في عملية هذا البحث ، وجدنا أنه على الرغم من أن برنامج التحويل البرمجي Fast JIT Mono لديه دعم float32 ، إلا أننا لم نقم بإضافة هذا الدعم إلى الواجهة الخلفية LLVM. هذا يعني أن Mono مع LLVM كانت لا تزال تقوم بتحويلات مكلفة من التعويم إلى الضعف.

لذلك ، أضاف Zoltan دعم float32 إلى محرك إنشاء كود LLVM.

ثم لاحظ أن لدينا inliner يستخدم نفس الاستدلال ل Fast JIT كتلك المستخدمة ل LLVM. عند العمل مع Fast JIT ، من الضروري تحقيق توازن بين سرعة JIT وسرعة التنفيذ ، وبالتالي قمنا بتحديد كمية التعليمات البرمجية المدمجة لتقليل مقدار عمل محرك JIT.

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

فيما يلي النتائج مع إعدادات LLVM المعدلة:

بيئة العمل والخياراتصراخ / ثانية
أحادية مع System.Math --llvm -O=float3216.0
أحادية مع System.Math --llvm -O=float32 ، الاستدلال المستمر29.1
أحادية مع System.MathF --llvm -O=float32 ، الاستدلال المستمر29.6

الخطوات التالية


كانت هناك حاجة إلى بذل جهد قليل لجعل كل هذه التحسينات. وقد قادت هذه التغييرات مناقشات دورية في سلاك. تمكنت حتى من جعل بضع ساعات مساء واحد إلى ميناء System.MathF إلى مونو.

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

نحن نفكر أيضًا في تحديث LLVM ، واستخدام التحسينات المضافة الجديدة.

مذكرة منفصلة


دقة اضافية لها آثار جانبية لطيفة. على سبيل المثال ، عند قراءة طلبات التجميع الخاصة بمحرك Godot ، رأيت أن هناك مناقشة نشطة حول ما إذا كان يجب جعل دقة عمليات الفاصلة العائمة قابلة للتخصيص في وقت الترجمة ( https://github.com/godotengine/godot/pull/17134 ).

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

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

بعد وقت قصير من حديثنا ، في خلاصتي على Twitter ، رأيت منشورًا يوضح هذه المشكلة: http://pharr.org/matt/blog/2018/03/02/rendering-in-camera-space.html

تظهر المشكلة في الصور أدناه. هنا نرى نموذج سيارة رياضية من حزمة pbrt-v3-scenes ** . تقع كل من الكاميرا والمشهد بالقرب من الأصل ، ويبدو كل شيء رائعًا.


** (مؤلف ياسوتوشي موري .)

ثم ننقل الكاميرا والمشهد 200000 وحدة في xx و yy و zz من الأصل. يمكن ملاحظة أن طراز الآلة أصبح مجزأ تمامًا ؛ هذا فقط بسبب نقص الدقة في أرقام الفاصلة العائمة.


إذا انتقلنا إلى أبعد من 5 × 5 × 5 مرات ، أي مليون وحدة من الأصل ، يبدأ النموذج في التفكك ؛ تتحول الآلة إلى تقريب فوكسل خام للغاية ، مثير للاهتمام ومخيف. (سأل كيانو السؤال: هل Minecraft مكعبة للغاية لأن كل شيء بعيد جدًا عن الأصل؟)


** (أعتذر لياسوتوشي موري على ما فعلناه بنموذجه الجميل.)

Source: https://habr.com/ru/post/ar432176/


All Articles