
هذه مقدمة مختصرة عن المكدس التكنولوجي الجديد الموجه نحو البيانات (
DOTS ). سوف نشارك بعض الأفكار لمساعدتك في فهم كيف ولماذا أصبحت الوحدة هكذا اليوم ، ونخبرك أيضًا بالاتجاه الذي نخطط لتطويره. في المستقبل ، نخطط لنشر مقالات جديدة على مدونة DOTS على مدونة الوحدة.
دعنا نتحدث عن C ++. هذه هي اللغة التي كتبت بها الوحدة الحديثة.
واحدة من أكثر المشكلات تعقيدًا التي يتعين على مطور اللعبة معالجتها بطريقة أو بأخرى هي: يجب على المبرمج توفير ملف قابل للتنفيذ مع إرشادات واضحة للمعالج الهدف ، وعندما ينفذ المعالج هذه التعليمات ، يجب أن تبدأ اللعبة.
في الجزء من الشفرة الحساسة للأداء ، نعلم مقدمًا ما هي الإرشادات النهائية. نحتاج فقط إلى طريقة بسيطة تسمح لنا بوصف منطقنا باستمرار ، ومن ثم التحقق والتأكد من إنشاء التعليمات التي نحتاجها.
نعتقد أن لغة C ++ ليست جيدة جدًا لهذه المهمة. على سبيل المثال ، أريد أن يتم توجيه حلقي ، ولكن قد يكون هناك مليون سبب لعدم تمكن المترجم من توجيهه. إما اليوم ، فإنه يتم توجيهه ، وغدًا لا يتم ذلك ، بسبب بعض التغييرات التي يبدو أنها تافهة. من الصعب حتى التأكد من أن جميع المترجمين C / C ++ الخاص بي يبدأوا حتى في توجيه الكود الخاص بي.
لقد قررنا تطوير "طريقة ملائمة تمامًا لإنشاء رمز الجهاز" بحيث يلبي جميع رغباتنا. قد يكون من الممكن قضاء الكثير من الوقت لثني السلسلة الكاملة لتصميم C ++ قليلاً في الاتجاه الذي نحتاج إليه ، لكننا قررنا أنه من المعقول استثمار قوتنا في تطوير سلسلة أدوات من شأنها أن تحل جميع مشكلات التصميم التي تواجهنا تمامًا. سنقوم بتطويرها مع مراعاة تلك المهام التي يتعين على مطور اللعبة حلها بدقة.
ما هي العوامل التي نوليها الأولوية؟
- الأداء = صحيح. يجب أن أكون قادرًا على القول: "إذا لم يتم توجيه هذه الحلقة لسبب ما ، فيجب أن يكون ذلك خطأً في برنامج التحويل البرمجي ، وليس موقفًا من الفئة" أوه ، بدأ الرمز في العمل أبطأ ثماني مرات فقط ، ولكنه لا يزال يعطي القيم الحقيقية ، عمل شيء! "
- عبر منصة. يجب أن يظل رمز الإدخال الذي أكتبه كما هو تمامًا بغض النظر عن النظام الأساسي الهدف - سواء أكان iOS أو Xbox
- يجب أن يكون لدينا حلقة تكرار أنيقة يمكن أن أرى فيها بسهولة رمز الماكينة الذي تم إنشاؤه لأي بنية أثناء تغيير شفرة المصدر الخاصة بي. يجب أن يكون "العارض" لرمز الماكينة مفيدًا للغاية في التدريب / الشرح عندما تحتاج إلى فهم ما تفعله جميع إرشادات الماكينة هذه.
- الأمن. كقاعدة عامة ، لا يضع مطورو الألعاب السلامة في مكانة عالية في قائمة أولوياتهم ، لكننا نعتقد أن واحدة من أروع ميزات Unity هي أنه من الصعب جدًا تلف الذاكرة الموجودة بها. يجب أن يكون هناك مثل هذا الوضع الذي يتم فيه تشغيل أي رمز - ونقوم بإصلاح الخطأ بشكل لا لبس فيه والذي يعرض رسالة كبيرة رسالة حول ما حدث هنا: على سبيل المثال ، لقد تجاوزت الحدود عند القراءة / الكتابة أو حاولت dereference صفر.
لذا ، بعد معرفة ما هو مهم بالنسبة لنا ، دعنا ننتقل إلى السؤال التالي: في أي لغة من الأفضل أن نكتب البرامج التي سيتم من خلالها إنشاء كود الآلة هذا؟ دعنا نقول لدينا الخيارات التالية:
- اللغة الخاصة
- بعض التكيف / مجموعة فرعية من C أو C ++
- مجموعة فرعية من ج #
ما ، C #؟ لحلقاتنا الداخلية التي أدائها مهم بشكل خاص؟ نعم. C # هو خيار طبيعي تمامًا ، حيث يوجد في سياق الوحدة الكثير من الأشياء اللطيفة:
- هذه هي اللغة التي يعمل بها مستخدمونا بالفعل اليوم.
- إنه يحتوي على IDE ممتاز ، لكل من التحرير / إعادة البناء ، وتصحيح الأخطاء.
- يوجد بالفعل مترجم يحول C # إلى IL وسيط (نحن نتحدث عن برنامج التحويل البرمجي Roslyn لـ C # من Microsoft) ، ويمكنك ببساطة استخدامه بدلاً من كتابتك الخاصة. لدينا تجربة غنية في تحويل لغة وسيطة إلى IL ، لذلك نحن بحاجة فقط إلى إجراء إنشاء التعليمات البرمجية و postprocessing برنامج معين.
- C # خالية من العديد من مشكلات C ++ (الجحيم مع تضمين الرؤوس وأنماط PIMPL ووقت التجميع الطويل)
أنا نفسي حقا أحب كتابة التعليمات البرمجية في C #. ومع ذلك ، C التقليدية ليست هي أفضل لغة من حيث الأداء. حقق فريق التطوير C # والفرق المسؤولة عن المكتبة القياسية ووقت التشغيل على مدار العامين الماضيين تقدماً هائلاً في هذا المجال. ومع ذلك ، أثناء العمل مع C # ، من المستحيل التحكم في مكان بياناتك بالضبط في الذاكرة. وهذه هي المشكلة التي نحتاج إلى حلها لزيادة الإنتاجية.
بالإضافة إلى ذلك ، يتم تنظيم المكتبة القياسية لهذه اللغة حول "الكائنات على الكومة" و "الكائنات التي تحتوي على مؤشرات إلى كائنات أخرى".
في الوقت نفسه ، من خلال العمل باستخدام جزء من الشفرة يكون فيه الأداء حرجًا ، يمكنك الاستغناء عن مكتبة قياسية (وداعًا لـ Linq ، و StringFormatter ، و List ، و Dictionary) ، وحظر عمليات التحديد (= لا توجد فصول ، وهياكل فقط) ، والتفكير ، وتعطيل أداة تجميع مجمعي البيانات المهملة والظاهرية المكالمات ، وإضافة عدد قليل من الحاويات الجديدة المسموح باستخدامها (NativeArray and company). في هذه الحالة ، تبدو العناصر المتبقية للغة C # جيدة جدًا بالفعل. راجع مدونة Aras للحصول على أمثلة ، حيث تصف مشروع تتبع مسار مؤقت.
سوف تساعدنا مجموعة فرعية كهذه بسهولة في التعامل مع جميع المهام ذات الصلة عند العمل مع الدورات الساخنة. نظرًا لأن هذه مجموعة فرعية كاملة من C # ، يمكنك العمل معها كما هو الحال مع C # العادية. يمكن أن نتلقى أخطاء مرتبطة بالسفر إلى الخارج عند محاولة الوصول ، وسوف نحصل على رسائل خطأ ممتازة ، وسيكون لدينا مصحح أخطاء مدعوم ، وستكون سرعة الترجمة إلى درجة أنك نسيت بالفعل العمل مع C ++. نشير غالبًا إلى هذه المجموعة الفرعية باسم "الأداء العالي C #" أو "HPC #".
مترجم انفجر: ماذا اليوم؟
لقد كتبنا رمز مولد / مترجم يسمى انفجار. وهي متوفرة في
Unity الإصدار
2018.1 وأعلى كحزمة في وضع "المعاينة". ما زال هناك الكثير من العمل الذي يتعين القيام به معه ، لكننا سعداء به اليوم.
في بعض الأحيان ، نتمكن من العمل بشكل أسرع من C ++ ، في كثير من الأحيان - لا يزال أبطأ من C ++. الفئة الثانية تتضمن أخطاء في الأداء ، والتي نحن مقتنعون بأنها ستكون قادرة على التعامل معها.
ومع ذلك ، ببساطة مقارنة الأداء ليست كافية. ما لا يقل أهمية هو ما يجب القيام به لتحقيق هذا الأداء. مثال: أخذنا رمز الإعدام من عارض C ++ الحالي الخاص بنا ونقلناه إلى Burst. لم يتغير الأداء ، ولكن في إصدار C ++ ، كان علينا أن نفعل إجراء موازنة لا يصدق لإقناع المترجمين C ++ الخاصين بنا بالقيام بالتوجيه. وكان الإصدار مع انفجار حوالي أربع مرات أكثر إحكاما.
بصراحة ، القصة الكاملة مع "يجب عليك إعادة كتابة الكود المهم بالنسبة للأداء في C #" للوهلة الأولى لم تروق لأي شخص في فريق الوحدة الداخلي. بالنسبة لمعظمنا ، بدا الأمر وكأنه "أقرب إلى الأجهزة!" عند العمل مع C ++. ولكن الآن تغير الوضع. باستخدام C # ، نحن نتحكم بشكل كامل في العملية برمتها من تجميع الكود المصدري إلى توليد كود الآلة ، وإذا كنا لا نرغب في أي تفاصيل ، فنحن نأخذها ونصلحها.
سنقوم ببطء ولكن بثبات بتطبيق جميع التعليمات البرمجية الهامة للأداء من C ++ إلى HPC #. في هذه اللغة ، من الأسهل تحقيق الأداء الذي نحتاجه ، وأصعب في كتابة الأخطاء ، وسهولة التعامل معه.
فيما يلي لقطة شاشة لـ Burst Inspector ، حيث يمكنك بسهولة معرفة إرشادات التجميع التي تم إنشاؤها لمختلف الحلقات الساخنة:

الوحدة لديها العديد من المستخدمين المختلفين. يمكن لبعضهم أن يتذكر مجموعة كاملة من تعليمات arm64 من الذاكرة ، في حين أن البعض الآخر ببساطة يخلق بحماس ، حتى بدون شهادة الدكتوراه في علوم الكمبيوتر.
يربح جميع المستخدمين عندما يسرع جزء من الإطار الزمني الذي يقضيه في تنفيذ رمز المحرك (عادة 90 ٪ +). إن حصة العمل مع الكود القابل للتنفيذ لحزمة Asset Store تتسارع بالفعل ، حيث يعتمد مؤلفو حزمة Asset Store على HPC #.
سيستفيد المستخدمون المتقدمون أيضًا من حقيقة أنه يمكنهم كتابة التعليمات البرمجية عالية الأداء الخاصة بهم على HPC #.
نقطة التحسين
في C ++ ، من الصعب جدًا الحصول على برنامج التحويل البرمجي لاتخاذ قرارات تسوية مختلفة حول تحسين الكود في أجزاء مختلفة من مشروعك. التحسين الأكثر تفصيلاً الذي يمكنك الاعتماد عليه هو عبارة عن مؤشر لكل ملف على مستوى التحسين.
تم تصميم ميزة Burst بحيث يمكنك قبول الطريقة الوحيدة لهذا البرنامج كمدخل ، وهي: نقطة الدخول إلى الحلقة الساخنة. تقوم ميزة Burst بتجميع هذه الوظيفة ، وكذلك كل ما تستدعي (يجب ضمان أن تكون العناصر المسماة معروفة مسبقًا: لا نسمح بالوظائف الافتراضية أو مؤشرات الوظائف).
نظرًا لأن Burst لا يعمل إلا على جزء صغير نسبيًا من البرنامج ، فقد قمنا بتعيين مستوى التحسين على 11. Burst يقوم بتضمين كل موقع مكالمة تقريبًا. قم بإزالة if-check ، والتي لن يتم حذفها ، لأننا في النموذج المضمن نحصل على معلومات أكثر اكتمالا حول وسائط الوظيفة.
كيف يساعد في حل مشاكل الترابط الشائعة؟
لا تساعد C ++ (وكذلك C #) المطورين بشكل خاص في كتابة تعليمات برمجية آمنة لمؤشر الترابط.
حتى اليوم ، بعد مرور أكثر من عقد من الزمان على بدء تشغيل معالج لعبة نموذجي مع اثنين أو أكثر من النوى ، من الصعب للغاية كتابة البرامج التي تستخدم العديد من النوى بكفاءة.
تعد سباقات البيانات وعدم الحتمية والمأزق هي التحديات الرئيسية التي تجعل من الصعب للغاية كتابة شفرة متعددة الخيوط. في هذا السياق ، نحتاج إلى ميزات من فئة "تأكد من أن هذه الوظيفة وكل ما تدعو إليه لن يبدأ أبدًا في قراءة أو كتابة الحالة العالمية". نريد من جميع انتهاكات هذه القاعدة إعطاء أخطاء مترجم ، وألا تظل "قواعد نأمل أن يلتزم بها جميع المبرمجين." انفجار يلقي خطأ ترجمة.
نوصي بشدة أن يكتب مستخدمو الوحدة (ونحافظ على نفس الشيء في دائرتهم) التعليمات البرمجية بحيث يتم تقسيم جميع تحويلات البيانات المخطط لها إلى مهام. كل مهمة هي "وظيفية" ، وكأثر جانبي ، حرة. يشير بوضوح إلى المخازن المؤقتة للقراءة فقط و المخازن المؤقتة للقراءة / الكتابة التي يجب أن تعمل. أي محاولة للوصول إلى البيانات الأخرى سوف تسبب خطأ في الترجمة.
يضمن برنامج جدولة المهام عدم قيام أي شخص بالكتابة إلى المخزن المؤقت للقراءة فقط أثناء تشغيل المهمة. ونحن نضمن أنه طوال مدة المهمة لن يقرأ أحد من المخزن المؤقت الخاص بك ، المصمم للقراءة والكتابة.
كلما قمت بتعيين مهمة تنتهك هذه القواعد ، ستتلقى خطأ في الترجمة. ليس فقط في مثل هذا الحدث المؤسف مثل ظروف السباق. ستوضح رسالة الخطأ أنك تحاول تعيين مهمة يجب قراءتها من المخزن المؤقت "أ" ، ولكنك سبق أن عينت مهمة ستكتب إلى أ. لذلك ، إذا كنت تريد فعل ذلك بالفعل ، فيجب تحديد المهمة السابقة كتبعية .
نعتقد أن آلية السلامة هذه تساعد على التقاط الكثير من الأخطاء قبل إصلاحها ، وبالتالي تضمن الاستخدام الفعال لجميع النوى. يصبح من المستحيل إثارة ظروف السباق أو الجمود. النتائج مضمونة لتكون محددة ، بغض النظر عن عدد سلاسل الرسائل التي لديك ، أو عدد المرات التي تمت مقاطعة سلسلة الرسائل بسبب تدخل بعض العمليات الأخرى.
السيطرة على المكدس كله
عندما نتمكن من الوصول إلى أسفل كل هذه المكونات ، يمكننا أيضًا التأكد من إدراكهم لبعضهم البعض. على سبيل المثال ، أحد الأسباب الشائعة لفشل vectorization هو: لا يمكن للمترجم أن يضمن ألا يشير المؤشران إلى نفس نقطة الذاكرة (التعرج). نحن نعلم أن اثنين من NativeArray لن يتداخلا على هذا النحو بأي حال من الأحوال ، لأنهما كتبا مكتبة مجموعة ، ويمكننا استخدام هذه المعرفة في Burst ، لذلك لن نرفض التحسين فقط خشية أن يتم توجيه مؤشرين إلى واحد نفس قطعة الذاكرة.
وبالمثل ، كتبنا مكتبة الرياضيات
Unity.Mathematics . الاندفاع المعروف أنها "بدقة" ستتمكن Burst (في المستقبل) من إلغاء الاشتراك في التحسين في حالات مثل math.sin (). نظرًا لأن Burst math.sin () ليست مجرد طريقة C # عادية تحتاج إلى تجميع ، فسوف تفهم أيضًا الخصائص المثلثية للخطيئة () ، ستفهم أن sin (x) == x لقيم x الصغيرة (والتي يمكن لـ Burst إثباتها بشكل مستقل ) ، سوف نفهم أنه يمكن استبداله بالتوسع في سلسلة تايلور ، والتضحية جزئياً بالدقة. في المستقبل ، تخطط Burst أيضًا لتنفيذ الحتمية الشاملة للمنصة والتصميم بنقطة عائمة - نعتقد أن هذه الأهداف قابلة للتحقيق.
الفروق بين رمز محرك اللعبة ورمز اللعبة غير واضحة
عندما نكتب رمز وقت تشغيل الوحدة في HPC # ، تتم كتابة كل من محرك اللعبة واللعبة بنفس اللغة. يمكننا توزيع أنظمة وقت التشغيل التي حولناها إلى HPC # كرمز مصدر. يمكن للجميع التعلم منها وتحسينها وتكييفها لأنفسهم. سيكون لدينا ملعب بمستوى معين ، ولن يمنع أي شيء مستخدمينا من كتابة نظام جسيم أو فيزياء ألعاب أو عارض أفضل مما كتبنا. من خلال تقريب عمليات التطوير الداخلية لدينا من عمليات تطوير المستخدم ، يمكننا أيضًا أن نشعر بتحسن في حذاء المستخدم ، لذلك سنضع كل جهودنا في بناء سير عمل واحد ، بدلاً من عمليتين مختلفتين.