سنلقي نظرة اليوم على مكانين آخرين يمضي فيه pbrt الكثير من الوقت في تحليل المشاهد من كارتون ديزني
"Moana" . دعونا نرى ما إذا كان من الممكن تحسين الإنتاجية هنا. ويختتم هذا بما هو حكيم في pbrt-v3. في منشور آخر ، سأتعامل مع المدى الذي يمكن أن نقطعه إذا تخلينا عن الحظر على التغييرات. في هذه الحالة ، سيكون كود المصدر مختلفًا جدًا عن النظام الموصوف في كتاب
التقديم المادي .
تحسين المحلل اللغوي
بعد تحسينات الأداء المقدمة في
المقالة السابقة ، زادت نسبة الوقت الذي يقضيه المحلل اللغوي pbrt ، والمهم للغاية منذ البداية ، بشكل طبيعي. في الوقت الحالي ، يقضي المحلل اللغوي عند بدء التشغيل معظم الوقت.
جمعت أخيرًا قوتي وقمت
بتطبيق رمز مميز ومحلل مكتوب يدويًا لمشاهد pbrt.
إن تنسيق ملفات مشهد pbrt بسيط للغاية
للتحليل : إذا لم تأخذ في الاعتبار الأسطر المقتبسة ، فإن الرموز المميزة مفصولة بمسافات ، والقواعد واضحة للغاية (ليس هناك حاجة أبدًا للبحث عن أكثر من رمز مميز) ، ولكن لا يزال المحلل اللغوي الخاص بك يحتوي على ألف سطر من الرمز الذي تحتاجه الكتابة والتصحيح. ساعدني أنه يمكن اختباره في العديد من المشاهد. بعد إصلاح مواطن الخلل الواضحة ، واصلت العمل حتى تمكنت من تقديم نفس الصور تمامًا كما كان من قبل: لا ينبغي أن يكون هناك أي اختلافات في البكسل بسبب استبدال المحلل اللغوي. في هذه المرحلة ، كنت متأكدًا تمامًا من أن كل شيء تم بشكل صحيح.
لقد حاولت جعل الإصدار الجديد فعالاً قدر الإمكان ، مع إخضاع ملفات الإدخال إلى
mmap()
قدر الإمكان واستخدام التنفيذ الجديد لـ
std::string_view
من C ++ 17 لتقليل إنشاء نسخ السلاسل من محتويات الملف. بالإضافة إلى ذلك ، نظرًا لأن
strtod()
استغرق الكثير من الوقت في الآثار السابقة ، فقد كتبت
parseNumber()
بعناية خاصة: تتم معالجة الأعداد الصحيحة أحادية الرقم والأعداد الصحيحة بشكل منفصل ، وفي الحالة القياسية عندما يتم تجميع pbrt لاستخدام عوامات 32 بت ، استخدم
strtof()
بدلاً من
strtod()
1 .
في عملية إنشاء تطبيق المحلل الجديد ، كنت أخشى قليلاً أن يكون المحلل القديم أسرع: في النهاية ، تم تطوير المرن والبسون وتحسينه لسنوات عديدة. لم أتمكن من معرفة مقدمًا ما إذا كانت كتابة نسخة جديدة طوال الوقت ستضيع حتى أكملتها وأعملها بشكل صحيح.
ومن دواعي سروري ، أن المحلل اللغوي الخاص بنا قد حقق انتصارًا كبيرًا: فقد أدى تعميم المرن والبيسون إلى تقليل الأداء كثيرًا لدرجة أن النسخة الجديدة تفوقت عليهم بسهولة. بفضل المحلل الجديد ، انخفض وقت الإطلاق إلى 13 دقيقة و 21 ثانية ، أي أنه تسارع 1.5 مرة أخرى! علاوة إضافية أنه أصبح من الممكن الآن إزالة كل دعم المرن والبيسون من نظام بناء pbrt. لقد كان دائمًا صداعًا ، خاصة في Windows ، حيث لا يقوم معظم الأشخاص بتثبيته بشكل افتراضي.
إدارة حالة الرسومات
بعد تسريع المحلل بشكل ملحوظ ، ظهرت تفاصيل مزعجة جديدة: في هذه المرحلة ، تم إنفاق حوالي 10٪ من وقت الإعداد على الدالتين
pbrtAttributeBegin()
و
pbrtAttributeEnd()
، وتم تخصيص معظم هذا الوقت وإصدار ذاكرة ديناميكية. خلال الجولة الأولى ، التي استغرقت 35 دقيقة ، استغرقت هذه الوظائف حوالي 3٪ فقط من وقت التنفيذ ، لذلك يمكن تجاهلها. ولكن مع التحسين ، يكون الأمر دائمًا هكذا: عندما تبدأ في التخلص من المشاكل الكبيرة ، تصبح المشاكل الصغيرة أكثر أهمية.
يعتمد وصف مشهد pbrt على الحالة الهرمية للرسم ، والتي تشير إلى التحول الحالي ، والمواد الحالية ، وما إلى ذلك. في ذلك ، يمكنك إنشاء لقطات للحالة الحالية (
pbrtAttributeBegin()
) ، وإجراء تغييرات عليها قبل إضافة شكل هندسي جديد إلى المشهد ، ثم العودة إلى الحالة الأصلية (
pbrtAttributeEnd()
).
يتم تخزين حالة الرسومات في بنية باسم غير متوقع ...
GraphicsState
. لتخزين نسخ من كائنات
GraphicsState
في رزمة حالات الرسومات المحفوظة ،
std::vector
. بالنظر إلى أعضاء
GraphicsState
، يمكننا أن نفترض مصدر المشاكل - ثلاثة
std::map
، من الأسماء إلى حالات المواد والمواد:
struct GraphicsState {
عند فحص ملفات المشهد هذه ، وجدت أنه يتم تنفيذ معظم حالات حفظ حالة الرسومات واستعادتها في الأسطر التالية:
AttributeBegin ConcatTransform [0.981262 0.133695 -0.138749 0.000000 -0.067901 0.913846 0.400343 0.000000 0.180319 -0.383420 0.905800 0.000000 11.095301 18.852249 9.481399 1.000000] ObjectInstance "archivebaycedar0001_mod" AttributeEnd
وبعبارة أخرى ، فإنه يقوم بتحديث التحول الحالي ويعيد إنشاء الكائن ؛ لم يتم إجراء أية تغييرات على محتويات هذه
std::map
. إن إنشاء نسخة كاملة منها - تخصيص عقد شجرة حمراء حمراء ، وزيادة أعداد المراجع للمؤشرات الشائعة ، وتخصيص مساحة ونسخ السلاسل - غالبًا ما يكون مضيعة للوقت. يتم تحرير كل هذا عند استعادة الحالة السابقة للرسومات.
لقد استبدلت كل من هذه الخرائط
std::shared_ptr
لتعيين وتنفيذ نهج النسخ عند الكتابة ، حيث لا يحدث النسخ داخل كتلة البداية / النهاية للسمة إلا عندما تحتاج إلى تغيير محتوياتها. لم يكن
التغيير صعبًا بشكل خاص ، لكنه قلل وقت الإطلاق بأكثر من دقيقة ، مما منحنا معالجة لمدة 12 دقيقة و 20 ثانية قبل بدء العرض - مرة أخرى تسارع 1.08 مرة.
ماذا عن تقديم الوقت؟
سوف يلاحظ القارئ اليقظ أنني حتى الآن لم أقل أي شيء عن وقت التقديم. لدهشتي ، تبين أنه كان مقبولًا تمامًا حتى خارج الصندوق: يمكن لـ pbrt تقديم صور لمشاهد ذات جودة سينمائية مع عدة مئات من العينات لكل بكسل على اثني عشر معالجًا لمدة ساعتين إلى ثلاث ساعات. على سبيل المثال ، هذه الصورة ، واحدة من أبطأ ، يتم تقديمها في ساعتين و 51 دقيقة و 36 ثانية:
الكثبان المأخوذة من Moana بواسطة pbrt-v3 بدقة 2048x858 عند 256 عينة لكل بكسل. بلغ إجمالي وقت العرض على مثيل Google Compute Engine مع 12 نواة / 24 خيطًا بتردد 2 جيجا هرتز وأحدث إصدار من pbrt-v3 ساعتين 51 دقيقة و 36 ثانية.في رأيي ، يبدو أن هذا مؤشر معقول بشكل مدهش. أنا متأكد من أن التحسينات لا تزال ممكنة ، وسوف تكشف دراسة متأنية للأماكن التي يقضي فيها معظم الوقت الكثير من الأشياء "المثيرة للاهتمام" ، ولكن حتى الآن لا توجد أسباب خاصة لها.
عند التنميط ، اتضح أن ما يقرب من 60 ٪ من وقت التقديم قضى عند تقاطع الأشعة مع الكائنات (تم تنفيذ معظم العمليات تجاوز BVH) ، وتم إنفاق 25 ٪ في البحث عن مواد ptex. تتشابه هذه النسب مع مؤشرات المشاهد الأبسط ، لذلك للوهلة الأولى لا يوجد أي مشكلة واضحة هنا. (ومع ذلك ، أنا متأكد من أن Embree سيكون قادرًا على تتبع هذه الأشعة في وقت أقل قليلاً.)
لسوء الحظ ، فإن قابلية التوسع الموازية ليست جيدة. أرى عادةً أن 1400٪ من موارد وحدة المعالجة المركزية تُنفق على العرض ، مقارنةً بالمثالية البالغة 2400٪ (على 24 وحدة معالجة مركزية افتراضية في Google Compute Engine). يبدو أن المشكلة تتعلق بالصراعات أثناء الأقفال في ptex ، لكنني لم أحقق فيها بمزيد من التفاصيل حتى الآن. من المحتمل جدًا أن pbrt-v3 لا يحسب فرق الأشعة للأشعة غير المباشرة في جهاز تتبع الأشعة ؛ في المقابل ، تحصل هذه الحزم دائمًا على الوصول إلى مستوى MIP الأكثر تفصيلاً من القوام ، وهو ليس مفيدًا جدًا للتخزين المؤقت للنسيج.
الخلاصة (ل pbrt-v3)
بعد تصحيح إدارة حالة الرسومات ، واجهت حدًا ، وبعد ذلك أصبح التقدم دون إجراء تغييرات كبيرة على النظام غير واضح ؛ استغرق الباقي الكثير من الوقت ولم يكن له علاقة تذكر بالتحسين. لذلك ، سوف أتطرق إلى هذا ، على الأقل فيما يتعلق pbrt-v3.
بشكل عام ، كان التقدم خطيرًا: انخفض وقت الإطلاق قبل العرض من 35 دقيقة إلى 12 دقيقة 20 ثانية ، أي أن إجمالي التسارع كان 2.83 مرة. علاوة على ذلك ، وبفضل العمل الذكي مع ذاكرة التخزين المؤقت للتحويل ، انخفض استخدام الذاكرة من 80 جيجا بايت إلى 69 جيجا بايت. كل هذه التغييرات متاحة الآن إذا كنت تقوم بالمزامنة مع أحدث إصدار من pbrt-v3 (أو إذا كنت قد فعلت ذلك خلال الأشهر القليلة الماضية). وقد توصلنا إلى فهم مدى
Primitive
الذاكرة
Primitive
لهذا المشهد ؛ اكتشفنا كيفية حفظ 18 جيجابايت أخرى من الذاكرة ، لكننا لم ننفذها في pbrt-v3.
إليك ما تنفقه هذه الـ 12 دقيقة و 20 ثانية بعد كل التحسينات التي قمنا بها:
الوظيفة / العملية | نسبة وقت التشغيل |
---|
بناء BVH | 34٪ |
التحليل (باستثناء strtof() ) | 21٪ |
strtof() | 20٪ |
ذاكرة التخزين المؤقت للتحويل | 7٪ |
قراءة ملفات PLY | 6٪ |
تخصيص الذاكرة الديناميكية | 5٪ |
عكس التحويل | 2٪ |
إدارة حالة الرسومات | 2٪ |
أخرى | 3٪ |
في المستقبل ، سيكون أفضل خيار لتحسين الأداء هو المزيد من مؤشرات الترابط لمرحلة الإطلاق: كل شيء تقريبًا أثناء تحليل المشهد يكون مترابطًا فرديًا ؛ هدفنا الأول الطبيعي هو بناء BVH. سيكون من المثير للاهتمام أيضًا تحليل أشياء مثل قراءة ملفات PLY وتوليد BVH لحالات فردية من الكائنات وتنفيذها بشكل غير متزامن في الخلفية ، بينما سيتم إجراء التحليل في سلسلة المحادثات الرئيسية.
في مرحلة ما ،
strtof()
إذا كانت هناك تطبيقات أسرع من
strtof()
؛ يستخدم pbrt فقط ما يوفره النظام. ومع ذلك ، يجب أن تكون حذرًا عند اختيار البدائل التي لم يتم اختبارها جيدًا: تحليل القيم العائمة هو أحد تلك الجوانب التي يجب أن يكون المبرمج متأكدًا منها تمامًا.
يبدو أيضًا جذابًا لزيادة تقليل الحمل على المحلل: لا يزال لدينا 17 غيغابايت من ملفات إدخال النص للتحليل. يمكننا إضافة دعم ترميز ثنائي لملفات إدخال pbrt (ربما مشابهة
لنهج RenderMan ) ، لكن لدي مشاعر مختلطة حول هذه الفكرة ؛ إن القدرة على فتح وتعديل ملفات وصف المشهد في محرر نصوص أمر مفيد للغاية ، وأنا قلق من أن الترميز الثنائي أحيانًا سيؤدي إلى إرباك الطلاب باستخدام pbrt في عملية التعلم. هذه إحدى الحالات التي قد يختلف فيها الحل الصحيح لـ pbrt عن الحلول الخاصة بالتقديم التجاري لمستوى الإنتاج.
كان من المثير للاهتمام للغاية تتبع كل هذه التحسينات وفهم الحلول المختلفة بشكل أفضل. اتضح أن pbrt لديه افتراضات غير متوقعة تتداخل مع مشهد هذا المستوى من التعقيد. كل هذا مثال رائع على مدى أهمية وصول مجتمع واسع من الباحثين إلى مشاهد الإنتاج الحقيقي بدرجة عالية من التعقيد ؛ أقول مرة أخرى جزيل الشكر لديزني على الوقت الذي أمضيته في معالجة هذا المشهد ووضعه في المجال العام.
في
المقالة التالية ، سنلقي نظرة على الجوانب التي يمكن أن تحسن الأداء إذا سمحنا لـ pbrt بإجراء تغييرات أكثر جذرية.
ملاحظة
- في نظام Linux الذي كنت
strtof()
، لم strtof()
أسرع من strtod()
. من الجدير بالذكر أنه على OS X strtod()
أسرع مرتين تقريبًا ، وهو أمر غير منطقي تمامًا. لأسباب عملية ، واصلت استخدام strtof()
.