Godot: استخدام أبطأ بنية بيانات في كل مرة تقريبًا.

تم إنشاؤها على ٢٦ نوفمبر ٢٠١٨  ·  61تعليقات  ·  مصدر: godotengine/godot

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

نفس الشيء مع كون RID_Owner عبارة عن شجرة حيث يمكن أن تكون خريطة تجزئة أو خريطة فتحة. كما أن تنفيذ Octree للإعدام له نفس المشكلة بالضبط.

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

يمنع استخدام هياكل البيانات هذه أيضًا أي نوع من التوازي "السهل" في جزء كبير من الكود ويؤدي إلى التخلص من ذاكرة التخزين المؤقت تمامًا.

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

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

discussion enhancement core

التعليق الأكثر فائدة

أنت قد تكون مهتم. لقد كنت أعمل على الشوكة الفاخرة لفترة طويلة. النتائج الحالية هي كما يلي:
تم التقاطها على كمبيوتر محمول كبير للألعاب مزود بوحدة معالجة مركزية رباعية النواة و GTX 1070
جودو عادي
image

شوكة بلدي على https://github.com/vblanco20-1/godot/tree/ECS_Refactor
image

في كلا الملفين ، فإن اللون الأحمر هو في الأساس "انتظر GPU". تم اختناق الزجاجة حاليًا بسبب عدد كبير جدًا من عمليات السحب على opengl. لا يمكن حلها حقًا بدون فولكان أو إعادة كتابة العارض بأكمله.

على جانب وحدة المعالجة المركزية ، ننتقل من إطارات 13 مللي ثانية إلى إطارات 5 مللي ثانية. تنتقل الإطارات "السريعة" من 5.5 مللي ثانية إلى 2.5 مللي ثانية
على جانب "الإطار الإجمالي" ، ننتقل من 13 مللي ثانية إلى 8-9 مللي ثانية
~ 75 إطارًا في الثانية إلى 115 إطارًا في الثانية

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

الكثير من هذه التحسينات قابلة للدمج في godot ، IF كان godot يدعم C ++ الحديثة وكان لديه نظام متعدد الخيوط يسمح بمهام "صغيرة" رخيصة جدًا.
يتم تحقيق أكبر مكاسب من خلال عمل ظلال متعددة الخيوط وقراءة خريطة ضوئية متعددة الخيوط على كائنات ديناميكية # 25013. كلاهما يعمل بنفس الطريقة. العديد من الأجزاء الأخرى من العارض متعددة الخيوط أيضًا.
المكاسب الأخرى هي Octree التي تكون أسرع من 10 إلى 20 مرة من godot one ، وتحسينات على بعض تدفق التصيير ، مثل تسجيل كل من قائمة عرض العمق والممر العادي في وقت واحد ، بدلاً من تكرار قائمة الإعدام 2 مرات ، وتغيير كبير في كيفية عمل اتصال الضوء> الشبكة (لا توجد قائمة مرتبطة!)

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

ال 61 كومينتر

من يحتاج إلى الأداء؟ :وجه القزم:

فضولي لمعرفة ما تقيسه.

لذلك يمكن أن يفسر هذا لماذا يمكن لعقدة Light2D بسيطة في Godot أن تحرق جهاز الكمبيوتر الخاص بك؟

تضمين التغريدة من المرجح أن يكون أداء الإضاءة السيئ مرتبطًا بكيفية أداء Godot للإضاءة ثنائية الأبعاد في الوقت الحالي: عرض كل كائن n-times (مع كون n عدد الأضواء التي تؤثر عليه).

تحرير: انظر # 23593

للتوضيح ، يتعلق الأمر بأوجه القصور في جانب وحدة المعالجة المركزية في جميع أنحاء الكود. هذا لا علاقة له بميزات godot أو عرض godot GPU نفسه.

@ vblanco20-1 في جزء من الظل الجانبي ، تحدثت أنا وأنت عن العقد التي تم تصميمها على أنها كيانات ECS بدلاً من ذلك. أتساءل عما إذا كانت الحيلة هي عمل فرع مميز من godot مع وحدة ent جديدة تعمل تدريجياً جنبًا إلى جنب مع الشجرة. مثل get_tree () و get_registry (). من المحتمل أن تفقد الوحدة النمطية ent مثل 80٪ من وظائف الشجرة / المشهد ، ولكنها قد تكون مفيدة كقاعدة اختبار ، خاصة بالنسبة لأشياء مثل تكوين مستويات ثابتة كبيرة مع الكثير من الكائنات (الانتقاء ، والتدفق ، وعرض الدُفعات). انخفاض الوظائف والمرونة ولكن أداء أفضل.

قبل الذهاب إلى ECS الكامل (الذي قد أفعله) ، أريد العمل على بعض الفواكه المعلقة المنخفضة كتجربة. قد أحاول أن أذهب إلى البيانات الكاملة لاحقًا.

إذن ، التحديثات الأولى:

update_dirty_instances: من 0.2-0.25 ميلي ثانية إلى 0.1 ميلي ثانية
octree_cull (العرض الرئيسي): من 0.35 ميلي ثانية إلى 0.1 ميلي ثانية

الجزء الممتع؟ استبدال octree cull لا يستخدم أي بنية تسريع ، إنه يتكرر فقط عبر مجموعة غبية مع جميع AABBs.

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

سأستمر في تنفيذ عملية الإعدام الجديدة الخاصة بي باستخدام الأضواء ، لمعرفة مقدار السرعة المتراكمة.

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

يوجد مستودع اختبارات godotengine / godot ، ولكن لم يستخدمه الكثير من الأشخاص حتى الآن.

تحديث جديد:

image

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

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

إنه يوضح أن إعادة إنشاء هيكل التسارع بالكامل أسرع من عملية قتل واحدة قديمة ذات ثمانية أوكتونات.
كما أن عمليات الإعدام الإضافية تميل إلى أن تكون حوالي نصف إلى ثلث الثماني الحالي.

لم أجد حالة واحدة يكون فيها بنيتي الغبية أبطأ من الأوكتان الحالي ، بخلاف تكلفة الإنشاء الأولي.

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

كما أنه يفحص الذاكرة بشكل متجاور تمامًا ، لذلك من الممكن جعلها تعمل SIMD دون الكثير من المتاعب.

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

أنت تسبب المزيد من الضجيج من نهاية لعبة العروش ....

image

عدّلوا الطحالب قليلاً حتى يتمكنوا الآن من استخدام C ++ 17 خوارزمية متوازية ، والتي تسمح للمبرمج فقط بإخباره بالقيام بالتوازي مع ، أو الفرز المتوازي.
تسريع حوالي x2 في ذبذبة الإحباط الرئيسية ، ولكن بالنسبة للأضواء هي نفس السرعة تقريبًا كما كانت من قبل.

إن جهاز culler الخاص بي الآن أسرع 10 مرات من godot octree بالنسبة إلى العرض الرئيسي.

إذا كنت تريد إلقاء نظرة على التغييرات ، فإن الشيء المهم الرئيسي هنا:
https://github.com/vblanco20-1/godot/blob/4ab733200faa20e0dadc9306e7cc93230ebc120a/servers/visual/visual_server_scene.cpp#L387
هذه هي وظيفة الانتقاء الجديدة. توجد بنية التسارع في الوظيفة التي تعلوها مباشرةً.

هذا يجب أن يتراجم مع VS2015؟ تقوم وحدة التحكم بإلقاء مجموعة من الأخطاء حول الملفات الموجودة داخل entt \

Ranoller إنه c ++ 17 ، لذا تأكد من أنك تستخدم

أه أعتقد أن Godot لم ينتقل إلى C ++ 17 بعد؟ على الأقل أتذكر بعض المناقشات حول هذا الموضوع؟

جودو هو C ++ 03. ستكون هذه الخطوة مثيرة للجدل. أوصي @ vblanco20-1 بالتحدث مع Juan عندما ينتهي أيام هولي ... هذا التحسين سيكون رائعًا ولا نريد على الفور "هذا لن يحدث أبدًا TM

@ vblanco20-1

octree_cull (العرض الرئيسي): من 0.35 ميلي ثانية إلى 0.1 ميلي ثانية

كم عدد الأشياء؟

@ nem0

@ vblanco20-1

octree_cull (العرض الرئيسي): من 0.35 ميلي ثانية إلى 0.1 ميلي ثانية

كم عدد الأشياء؟

حوالي عام 2200. أداء جودو أوكتري أفضل إذا كان الشيك صغيرًا جدًا ، لأنه بدأ مبكرًا. كلما كان الاستعلام أكبر ، تتم مقارنة Godot octree الأبطأ بحل. إذا كان Godot octree غير قادر على الخروج مبكرًا بنسبة 90 ٪ من المشهد ، فسيكون ذلك بطيئًا بشكل مخيف ، لأن قوائمه المرتبطة المتكررة بشكل أساسي لكل كائن على حدة ، في حين أن نظامي عبارة عن هيكل من المصفوفات حيث يحتوي كل سطر من ذاكرة التخزين المؤقت على كمية جيدة من AABBs ، مما يقلل بشكل كبير يخطئ ذاكرة التخزين المؤقت.

يعمل نظامي من خلال وجود مستويين من AABBs ، وهي عبارة عن مجموعة من الكتل ، حيث يمكن لكل كتلة استيعاب 128 مثيلاً.

المستوى الأعلى هو في الغالب مصفوفة من AABBs + مصفوفة من الكتل. إذا نجح فحص AABB ، فقم بتكرار الكتلة. الكتلة عبارة عن مجموعة من بنية AABBs والقناع ومؤشر للمثيل. بهذه الطريقة كل شيء هو faaaaast.

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

لقد راجعت أرقامًا مختلفة ، ويبدو أن 128 لكل كتلة لا تزال تمثل مكانًا للحلويات بطريقة ما.

من خلال تغيير الخوارزمية لفصل الكائنات "الكبيرة" عن الأشياء الصغيرة ، تمكنت ive من الحصول على ترقية أخرى للسرعة بنسبة 30٪ ، معظمها في حالة عدم النظر إلى مركز الخريطة. إنه يعمل لأن الأشياء الكبيرة لا تنتفخ كتلة AABBs التي تتضمن أيضًا أشياء صغيرة. أعتقد أن 3 أحجام من المحتمل أن تعمل بشكل أفضل.

لا يزال إنشاء الكتلة غير الأمثل على الإطلاق. تقوم الكتل الكبيرة بإخراج حوالي 10 إلى 20٪ فقط من الحالات عند النظر إلى المركز ، وما يصل إلى 50٪ عند النظر خارج الخريطة ، لذلك تقوم بإجراء عدد كبير من عمليات التحقق من AABB أكثر مما يجب أن تحتاجه.

أعتقد أنه من المحتمل أن يكون التحسين هو إعادة استخدام الأوكتري الحالي ولكن "تسويته".

لا توجد الآن حالة واحدة تكون فيها الخوارزمية الخاصة بي مساوية أو أسوأ من الثماني الحالي ، حتى بدون تنفيذ مواز.

@ vblanco20-1 أفترض أنك تجري مقارنتك مع godot المترجم في الوضع release (الذي يستخدم -O3 ) وليس المحرر العادي الذي تم إنشاؤه في التصحيح (الذي لا يحتوي على تحسينات وهو افتراضي) صحيح ؟
آسف إذا كان سؤالًا سخيفًا ولكني لم أجد أي ذكر لذلك في الموضوع.
عمل جيد على أي حال :)

Faless Its

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

إنه مستوحى من كيفية عمل إحدى تجاربي ، حيث أقوم بعمل 400.000 كائن فيزيائي يرتد من بعضها البعض.

image

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

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

@ LikeLakers2 يمكنك رؤية كل من التنفيذ الحالي وتنفيذه في لقطة الشاشة.

ركض هذا على شوكة رئيسية

يوم الأربعاء ، 12 ديسمبر 2018 ، MichiRecRoom [email protected]
كتب:

neikeq https://github.com/neikeq اشرح؟ اعتقدت لقطات
التي تم نشرها كانت فقط من تنفيذه ، بالنظر إلى كيفية المشاركات
مع لقطات شاشة تمت صياغتها حتى الآن.

-
أنت تتلقى هذا لأنك علقت.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/godotengine/godot/issues/23998#issuecomment-446831151 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/ADLZ9pRh9Lksse9KBfWV0z5GmHRXf5P2ks5u4crCgaJpZM4YziJp
.

هل هناك أي أخبار عن هذا الموضوع؟

هل هناك أي أخبار عن هذا الموضوع؟

لقد تحدثت مع reduz. لن يتم دمجها أبدًا فهي لا تناسب المشروع. ذهبت لإجراء تجارب أخرى بدلاً من ذلك. ربما أقوم بترقية عملية الإعدام لاحقًا لـ 4.0

هذا عار ، بالنظر إلى أنه بدا واعدًا حقًا. نأمل أن يتم العمل على هذه القضايا في الوقت المناسب.

ربما ينبغي إعطاء هذا 4.0 معلما؟ نظرًا لأن Reduz يخطط للعمل على Vulkan لـ 4.0 ، وهذا الموضوع ، بينما يتعلق بالأداء ، يركز على التقديم ، أو على الأقل OP هو.

@ vblanco20-1 هل قال كيف أنها غير مناسبة؟ أفترض بسبب الانتقال إلى C ++ 17؟

على الرغم من وجود مشكلة أخرى ذات صلة في الحدث الرئيسي: # 25013

أنت قد تكون مهتم. لقد كنت أعمل على الشوكة الفاخرة لفترة طويلة. النتائج الحالية هي كما يلي:
تم التقاطها على كمبيوتر محمول كبير للألعاب مزود بوحدة معالجة مركزية رباعية النواة و GTX 1070
جودو عادي
image

شوكة بلدي على https://github.com/vblanco20-1/godot/tree/ECS_Refactor
image

في كلا الملفين ، فإن اللون الأحمر هو في الأساس "انتظر GPU". تم اختناق الزجاجة حاليًا بسبب عدد كبير جدًا من عمليات السحب على opengl. لا يمكن حلها حقًا بدون فولكان أو إعادة كتابة العارض بأكمله.

على جانب وحدة المعالجة المركزية ، ننتقل من إطارات 13 مللي ثانية إلى إطارات 5 مللي ثانية. تنتقل الإطارات "السريعة" من 5.5 مللي ثانية إلى 2.5 مللي ثانية
على جانب "الإطار الإجمالي" ، ننتقل من 13 مللي ثانية إلى 8-9 مللي ثانية
~ 75 إطارًا في الثانية إلى 115 إطارًا في الثانية

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

الكثير من هذه التحسينات قابلة للدمج في godot ، IF كان godot يدعم C ++ الحديثة وكان لديه نظام متعدد الخيوط يسمح بمهام "صغيرة" رخيصة جدًا.
يتم تحقيق أكبر مكاسب من خلال عمل ظلال متعددة الخيوط وقراءة خريطة ضوئية متعددة الخيوط على كائنات ديناميكية # 25013. كلاهما يعمل بنفس الطريقة. العديد من الأجزاء الأخرى من العارض متعددة الخيوط أيضًا.
المكاسب الأخرى هي Octree التي تكون أسرع من 10 إلى 20 مرة من godot one ، وتحسينات على بعض تدفق التصيير ، مثل تسجيل كل من قائمة عرض العمق والممر العادي في وقت واحد ، بدلاً من تكرار قائمة الإعدام 2 مرات ، وتغيير كبير في كيفية عمل اتصال الضوء> الشبكة (لا توجد قائمة مرتبطة!)

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

@ vblanco20-1 هذا رائع تمامًا. شكرا للمشاركة!

أوافق أيضًا على أنه على الرغم من أن Vulkan رائع ، فإن الحصول على أداء GLES3 لائق سيكون بمثابة فوز مطلق لـ Godot. من المحتمل ألا يهبط دعم Vulkan الكامل في أي وقت قريبًا بينما يعد تصحيح GLES3 أمرًا أثبت الرجال هنا أنه يمكن تنفيذه بالكامل في الوقت الحالي.

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

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

@ vblanco20-1 بصراحة ، أنا أفكر حتى في استخدام شوكة الخاص بك كقاعدة لعبتنا في الإنتاج.

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

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

هذا ليس له معنى بالنسبة لي. من المحتمل أن تكون فكرة أفضل أن تحصل على إصلاحات وتحسينات في الأداء مباشرة في عارض GLES3 و GLES2 (طالما أنها لا تكسر العرض بطرق رئيسية في المشاريع الحالية).

المكاسب الأخرى هي Octree التي تكون أسرع من 10 إلى 20 مرة من godot one ، وتحسينات على بعض تدفق التصيير ، مثل تسجيل كل من قائمة عرض العمق والممر العادي في وقت واحد ، بدلاً من تكرار قائمة الإعدام 2 مرات ، وتغيير كبير في كيفية عمل اتصال الضوء> الشبكة (لا توجد قائمة مرتبطة!)

@ vblanco20-1 هل سيكون من الممكن تحسين عمق التمهيد أثناء البقاء على C ++ 03؟

Calinou ، تعد

@ vblanco20-1 بصراحة ، أنا أفكر حتى في استخدام شوكة الخاص بك كقاعدة لعبتنا في الإنتاج.

@ and3rson من فضلك لا. هذه الشوكة عبارة عن مشروع بحثي بحت ، وهي ليست مستقرة تمامًا. في الواقع ، لن يتم تجميعها حتى خارج بعض المجمعين المحددين للغاية بسبب استخدام STL المتوازي. تعتبر الشوكة في الغالب بمثابة اختبار لأفكار وممارسات التحسين العشوائية.

ما الأدوات التي تستخدمها لملف تعريف Godot؟ هل احتجت إلى إضافة علامات Profiler.Begin و Profiler.End إلى مصدر godot لإنشاء هذه العينات؟

ما الأدوات التي تستخدمها لملف تعريف Godot؟ هل احتجت إلى إضافة علامات Profiler.Begin و Profiler.End إلى مصدر godot لإنشاء هذه العينات؟

باستخدام ملف تعريف Tracy ، فإنه يحتوي على أنواع مختلفة من علامة ملف تعريف النطاق ، والتي أستخدمها هنا. على سبيل المثال ، تضيف ZoneScopedNC ("قائمة التعبئة" ، 0x123abc) علامة ملف تعريف بهذا الاسم واللون السداسي الذي تريده.

@ vblanco20-1 - هل سنشهد إدخال بعض تحسينات الأداء هذه إلى فرع Godot الرئيسي؟

@ vblanco20-1 - هل سنشهد إدخال بعض تحسينات الأداء هذه إلى فرع Godot الرئيسي؟

ربما عندما يتم دعم C ++ 11. لكن reduz لا يريد حقًا المزيد من العمل على العارض قبل حدوث vulkan ، لذلك قد لا يكون أبدًا. قد تكون بعض النتائج من عمل التحسين مفيدة للعارض 4.0.

@ vblanco20-1 لمشاركة تفاصيل المجمّع والبيئة للفرع ، أعتقد أنه يمكنني معرفة معظمها ولكن لا تضيع الوقت في اكتشاف المشكلات إذا لم أضطر إلى ذلك.

لا يمكننا أيضًا إضافة تغيير AABB إلى Godot main دون تكلفة كبيرة؟

@ vblanco20-1 لمشاركة تفاصيل المجمّع والبيئة للفرع ، أعتقد أنه يمكنني معرفة معظمها ولكن لا تضيع الوقت في اكتشاف المشكلات إذا لم أضطر إلى ذلك.

لا يمكننا أيضًا إضافة تغيير AABB إلى Godot main دون تكلفة كبيرة؟

swarnimarun Visual Studio 17 أو 19 ، أحد التحديثات اللاحقة ، يعمل بشكل جيد. (شبابيك)
تم تعديل البرنامج النصي scons لإضافة علم cpp17. أيضًا ، بدء تشغيل المحرر معطل الآن. أنا أعمل على معرفة ما إذا كان بإمكاني استعادته.

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

@ vblanco20-1 هل لديك Patreon؟ أود أن أضع بعض المال في شوكة تركز على الأداء وهذا لا يقتصر على C ++ القديمة وربما لست الوحيد. أرغب في اللعب مع Godot للمشاريع ثلاثية الأبعاد ولكني أفقد الأمل في أن تصبح قابلة للتطبيق بدون دليل كبير وهو مفهوم يثبت نهجك في الأداء وسهولة الاستخدام للفريق الأساسي.

mixedCase سأكون مترددًا للغاية في دعم أي نوع من

السيناريو الأفضل ، والأرجح الآن أنه تم إثبات أن C ++ الأحدث يسمح بإجراء تحسينات كبيرة ، هو أن يقوم Godot بالترقية رسميًا إلى إصدار أحدث من C ++. أتوقع أن يحدث هذا في Godot 4.0 لتقليل مخاطر الانكسار في 3.2 ، وفي الوقت المناسب لاستخدام ميزات C ++ الجديدة مع إضافة Vulkan إلى Godot 4.0. لا أتوقع أيضًا إجراء أي تغييرات مهمة على عارض GLES 3 ، حيث يريد reduz حذفه.

(لكنني لا أتحدث باسم مطوري جودو ، هذه مجرد تكهناتي)

aaronfranke أوافق ، أعتقد أن الشوكة الدائمة لن تكون مثالية. ولكن مما جمعته مما قاله (من فضلك صححني إذا كنت مخطئًا) هو أن

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

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

هذا جزء من سلسلة من المقالات التي أعمل عليها حول التحسينات ، وبالتحديد حول استخدام بنية البيانات.

يحظر Godot حاويات المحكمة الخاصة بلبنان و C ++ 03. هذا يعني أنه لا يحتوي على مشغلين متحركين في حاوياته ، وأن حاوياته تميل إلى أن تكون أسوأ من حاويات STL نفسها. لدي هنا نظرة عامة على هياكل بيانات godot والمشكلات المتعلقة بها.

مصفوفة C ++ مخصصة مسبقًا. شائع جدًا حول المحرك بأكمله. تميل أحجامها إلى تعيين بعض خيارات التكوين. هذا شائع جدًا في العارض ، وستعمل فئة المصفوفة الديناميكية المناسبة بشكل رائع هنا.
مثال على إساءة الاستخدام: https://github.com/godotengine/godot/blob/master/servers/visual/visual_server_scene.h#L442
هذه المصفوفات تهدر الذاكرة بدون سبب وجيه ، فإن استخدام مصفوفة ديناميكية سيكون خيارًا أفضل بكثير هنا لأنه سيجعلها بالحجم الذي تحتاجه فقط ، بدلاً من الحجم الأقصى بواسطة ثابت التجميع. تستخدم كل مصفوفات مثيل * التي تستخدم MAX_INSTANCE_CULL نصف ميغا بايت من الذاكرة

Vector (vector.h) مكافئ للصفيف الديناميكي المتجه std :: vector ، لكن به خلل عميق.
يتم إعادة عد المتجه ذريًا باستخدام تطبيق النسخ عند الكتابة. عندما تفعل

المتجه a = build_vector () ؛
المتجه ب = أ ؛

سيذهب refcount إلى 2. يشير B و A الآن إلى نفس الموقع في الذاكرة ، وسوف ينسخ المصفوفة بمجرد إجراء تعديل عليها. إذا قمت الآن بتعديل المتجه A أو B ، فسيؤدي ذلك إلى تشغيل نسخة متجهية.

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

PoolVector (pool_vector.h). يشبه إلى حد ما المتجه ، ولكن كمجمع بدلاً من ذلك. لديها نفس المخاطر مثل Vector ، مع النسخ عند الكتابة بدون سبب وتقليص الحجم تلقائيًا.

List (list.h) مكافئ std :: list. تحتوي القائمة على مؤشرين (الأول والأخير) + متغير عدد العناصر. كل عقدة في القائمة مرتبطة بشكل مزدوج ، بالإضافة إلى مؤشر إضافي لحاوية القائمة نفسها. حجم الذاكرة لكل عقدة في القائمة هو 24 بايت. يتم استخدامه بشكل مفرط في godot نظرًا لأن Vector ليس جيدًا بشكل عام. إساءة الاستخدام بشكل فظيع على المحرك بأكمله.

أمثلة على سوء الاستخدام الكبير:
https://github.com/godotengine/godot/blob/master/servers/visual/visual_server_scene.h#L129
لا يوجد سبب على الإطلاق لجعل هذا قائمة ، يجب أن يكون ناقلًا
https://github.com/godotengine/godot/blob/master/servers/visual/visual_server_scene.h#L242
كل حالة من هذه الحالات خاطئة. مرة أخرى ، يجب أن يكون متجهًا أو مشابهًا. هذا يمكن أن يسبب مشاكل في الأداء.
أكبر خطأ هو استخدام قائمة في الثماني المستخدمة للإعدام. لقد دخلت بالفعل في التفاصيل حول جودو أوكتري في هذا الموضوع.

قائمة ذاتية (self_list.h) قائمة تدخلية ، شبيهة بـ boost :: intrusive_list. يخزن هذا دائمًا 32 بايتًا من الحمل لكل عقدة لأنه يشير أيضًا إلى الذات. إساءة الاستخدام بشكل فظيع على المحرك بأكمله.
أمثلة:
https://github.com/godotengine/godot/blob/master/servers/visual/visual_server_scene.h#L159
هذا واحد سيء بشكل خاص. واحدة من أسوأ ما في المحرك بأكمله. هذا يضيف 8 مؤشرات للسمنة لكل كائن يمكن عرضه في المحرك بدون سبب على الإطلاق. ليست هناك حاجة لأن تكون هذه قائمة. مرة أخرى ، يمكن أن تكون خريطة فتحة أو متجه ، وهذا ما استبدلت به.

خريطة (map.h) شجرة حمراء-سوداء. يساء استخدامه باعتباره علامة التجزئة في جميع استخداماته تقريبًا. يجب حذفه من المحرك لصالح OAHashmap أو Hashmap حسب الاستخدام.
كل استخدام للخريطة رأيته كان خاطئًا. لا يمكنني العثور على مثال لخريطة يتم استخدامها حيث يكون ذلك منطقيًا.

Hashmap (hash_map.h) مكافئ تقريبًا لـ std :: unordered_map ، وهي علامة تجزئة عناوين مغلقة تستند إلى مجموعات قوائم مرتبطة. يمكن أن تتكرر عند إزالة العناصر.

OAHashMap (oa_hash_map.h) مكتوب حديثًا سريع فتح العناوين. يستخدم تجزئة robinhood. على عكس hashmap ، لن يتم تغيير حجمه عند إزالة العناصر. هيكل جيد التنفيذ حقًا ، أفضل من std :: unordered_map.

CommandQueueMT (command_queue_mt.h) قائمة انتظار الأوامر المستخدمة للتواصل مع الخوادم المختلفة ، مثل الخادم المرئي. إنه يعمل من خلال وجود مصفوفة صلبة 250 كيلو بايت لتعمل كمجمع مخصص ، ويخصص كل أمر ككائن مع وظيفة استدعاء افتراضية () ووظيفة post (). يستخدم كائنات المزامنة (mutexes) لحماية عمليات الدفع / الفرقعة. يعد قفل كائنات المزامنة مكلفًا للغاية ، أوصي باستخدام قائمة انتظار Moodycamel بدلاً من ذلك ، والتي يجب أن تكون ترتيبًا من حيث الحجم بشكل أسرع. من المحتمل أن يصبح هذا عنق الزجاجة للألعاب التي تؤدي الكثير من العمليات مع الخادم المرئي ، مثل الكثير من الكائنات المتحركة.

هذه هي إلى حد كبير المجموعة الأساسية لهياكل البيانات في Godot. لا يوجد مكافئ مناسب لـ std :: vector. إذا كنت تريد بنية بيانات "المصفوفة الديناميكية" ، فأنت عالق في Vector ، وعيوبها مع النسخة عند الكتابة وتقليل الحجم. أعتقد أن بنية بيانات DynamicArray هي الشيء الذي يحتاجه godot أكثر من غيره في الوقت الحالي.

بالنسبة إلى مفترقتي ، أستخدم STL والحاويات الأخرى من المكتبات الخارجية. أتجنب حاويات godot لأنها أسوأ من STL للأداء ، باستثناء 2 hashmaps.


تم العثور على مشاكل في تطبيق Vulkan في 4.0. أعلم أن العمل جارٍ ، لذا لا يزال هناك متسع من الوقت لإصلاحه.

استخدام الخريطة في واجهة برمجة التطبيقات الخاصة بالتقديم. https://github.com/godotengine/godot/blob/vulkan/drivers/vulkan/rendering_device_vulkan.h#L350
كما علقت ، لا يوجد استخدام جيد للخريطة ولا يوجد سبب لوجودها. يجب أن يكون مجرد hashmap في تلك الحالات.
الإفراط في استخدام القائمة المرتبطة بدلاً من مجرد المصفوفات أو ما شابه ذلك.
https://github.com/godotengine/godot/blob/vulkan/drivers/vulkan/rendering_device_vulkan.h#L680
لحسن الحظ ، من المحتمل ألا يكون هذا في الحلقات السريعة ، لكنه لا يزال مثالاً على استخدام القائمة حيث لا ينبغي استخدامه

استخدام PoolVector و Vector في واجهة برمجة التطبيقات الخاصة بالتقديم. https://github.com/godotengine/godot/blob/vulkan/drivers/vulkan/rendering_device_vulkan.h#L747
لا يوجد سبب وجيه حقيقي لاستخدام هذين الهيكلين المعيبين كجزء من تجريد واجهة برمجة التطبيقات الخاصة بالتقديم. باستخدامهم ، يضطر المستخدمون إلى استخدام هذين ، مع عيوبهم ، بدلاً من القدرة على استخدام أي بنية بيانات أخرى. التوصية هي استخدام المؤشر + الحجم في تلك الحالات ، ولا يزال لديك إصدار من الوظيفة يأخذ المتجه إذا لزم الأمر.

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

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

إذا كنت مهتمًا بمعرفة المزيد عن هذا الموضوع ، فإن أفضل حديث أعرفه هو هذا الحديث من CppCon. https://www.youtube.com/watch؟v=fHNmRkzxHWs
المحادثات الرائعة الأخرى هي: https://www.youtube.com/watch؟v=-8UZhDjgeZU ، حيث تشرح بعض هياكل البيانات مثل slotmap التي ستكون مفيدة للغاية لاحتياجات godot.

لدى Godot مشكلة كبيرة في الأداء ... ستكون إعلانًا جيدًا لفريق godot ، يرجى مراعاة ذلك!

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

@ vblanco20-1 مرة أخرى ، أقدر حقًا أن لديك نوايا حسنة ، لكنك لا تفهم أيًا من الأعمال الداخلية للمحرك ، أو الفلسفة الكامنة وراءه. معظم تعليقاتك تجاه الأشياء التي لا تفهمها حقًا كيف يتم استخدامها ، أو مدى أهميتها للأداء ، أو ما هي أولوياتها بشكل عام.

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

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

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

مرة أخرى ، عندما يتم طرح إصدار ألفا من Godot 4.0 (نأمل قبل نهاية هذا العام) ، فنحن نرحب بك لتوصيف كل الكود الجديد وتقديم ملاحظات حول كيفية تحسينه بشكل أكبر. أنا متأكد من أننا سنستفيد جميعًا. في الوقت الحالي ، ليس هناك الكثير من النقاط في مناقشة أي شيء آخر هنا لأن الكود الحالي سيختفي في 4.0 ، ولن يتم دمج أي شيء في فرع 3.x ، حيث يكون الاستقرار في هذه المرحلة أكثر أهمية من التحسينات.

نظرًا لأن الكثيرين قد يكون لديهم فضول بشأن التفاصيل الفنية:

  • سيتم استبدال جميع رموز الفهرسة المكانية (ما أعيد كتابته vblanco) بخوارزمية خطية للإعدام باستخدام خيوط متعددة ، جنبًا إلى جنب مع ثماني من أجل انتقاء الانسداد الهرمي و SAP لعمليات التحقق المتداخلة ، والتي هي على الأرجح أفضل خوارزميات مستديرة تضمن الخير الأداء في أي نوع من الألعاب. سيكون التخصيص لهذه الهياكل في نفس سياق RID_Allocator الجديد ، وهو O (1)). لقد ناقشت هذا الأمر مع @ vblanco20-1 من قبل ووضحت أن نهجه لا يتناسب جيدًا مع جميع أنواع الألعاب ، حيث يتطلب من المستخدم أن يكون لديه درجة معينة من الخبرة لتعديل ما لا يتوقع أن يمتلكه مستخدم Godot النموذجي. لم يكن أيضًا أسلوبًا جيدًا لإضافة انتقاء الانسداد.
  • لن أستخدم المصفوفات عندما يمكن استخدام القوائم لأن القوائم تقوم بتخصيصات زمنية صغيرة مع عدم وجود مخاطر تجزئة. في بعض الحالات ، أفضل تخصيص مصفوفة مقسمة (محاذاة للصفحات ، بحيث تتسبب في 0 تجزئة) تنمو دائمًا ولا تتقلص أبدًا (كما هو الحال في RID_Allocator أو CanvasItem الجديد في فرع Vulkan للمحرك ثنائي الأبعاد ، والذي يسمح لك الآن بإعادة رسم العناصر مع الكثير من الأوامر بكفاءة عالية) ، ولكن يجب أن يكون هناك سبب أداء لذلك. عند استخدام القوائم في Godot ، يرجع ذلك إلى تفضيل التخصيصات الصغيرة على الأداء (وهي في الواقع تجعل نية الكود أكثر وضوحًا ليقرأها الآخرون).
  • تم تصميم PoolVector لعمليات التخصيص الكبيرة جدًا باستخدام صفحات ذاكرة متتالية. حتى الإصدار 2.1 من Godot ، كانت تستخدم مجموعة ذاكرة مخصصة مسبقًا ، ولكن تمت إزالتها في 3.x والآن أصبح السلوك الحالي خاطئًا. بالنسبة إلى الإصدار 4.0 ، سيتم استبداله بذاكرة افتراضية ، وهو مدرج في قائمة الأشياء التي يجب القيام بها.
  • لا جدوى من مقارنة ناقل Godot's Vector <> بـ std :: vector لأن لهما حالات استخدام مختلفة. نستخدمها في الغالب لتمرير البيانات ونغلقها للوصول السريع (عبر طرق ptr () أو ptrw ()). إذا استخدمنا std :: vector ، فسيكون Godot أبطأ كثيرًا بسبب النسخ غير الضروري. نحن أيضًا نستفيد كثيرًا من النسخة على ميكانيكي الكتابة للعديد من الاستخدامات المختلفة.
  • الخريطة <> هي مجرد أبسط وأكثر ودية بالنسبة لكمية كبيرة من العناصر ولا داعي للقلق بشأن النمو المتسارع / التذبذب الذي يؤدي إلى التجزئة. عندما يكون الأداء مطلوبًا ، يتم استخدام HashMap بدلاً من ذلك (على الرغم من أنه صحيح ، فمن المحتمل أن يستخدم OAHashMap أكثر ، لكنه جديد جدًا ولم يكن لديه وقت للقيام بذلك). كفلسفة عامة ، عندما لا يكون الأداء هو الأولوية ، يُفضل دائمًا التخصيصات الصغيرة على الكبيرة لأنه من الأسهل لمخصص الذاكرة من نظام التشغيل الخاص بك أن يجد ثغرات صغيرة لوضعها فيها (وهو ما تم تصميمه للقيام به إلى حد كبير) ، تستهلك كومة أقل بشكل فعال.

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

أنا متأكد أيضًا من أن الكثيرين ممن يقرؤون هذا الموضوع يتساءلون عن سبب بطء المفهرس المكاني في البداية. والسبب هو أنك ربما تكون جديدًا على Godot ، ولكن حتى وقت قريب جدًا ، كان عمر المحرك ثلاثي الأبعاد أكثر من 10 سنوات وكان قديمًا للغاية. تم العمل على تحديثه في OpenGL ES 3.0 ، ولكن كان علينا التوقف بسبب المشكلات التي وجدناها في OpenGL وحقيقة أنه تم إهماله لـ Vulkan (وتخلت عنه Apple).

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

أيضًا ، كان لا بد من تأجيل العديد من التحسينات التي أردنا القيام بها (نقل العديد من كود كائن المزامنة إلى الجزيئات الذرية للحصول على أداء أفضل) حتى نتمكن من نقل Godot إلى C ++ 11 (والذي يحتوي على دعم أفضل بكثير للذرات المضمنة وهو ليس كذلك إعادة العرض لك لتضمين عناوين windows ، والتي تلوث مساحة الاسم بالكامل) ، وهو أمر لا يمكننا القيام به في فرع مستقر. سيتم الانتقال إلى C ++ 11 بعد أن يتم تشعب Godot 3.2 وتجميد الميزة ، وإلا فسيكون الحفاظ على مزامنة فروع Vulkan الرئيسية بمثابة ألم كبير. ليس هناك الكثير من التسرع ، حيث أن التركيز حاليًا هو على Vulkan نفسها.

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

مرحبًا reduz ،
بينما أرى في الغالب أن نقاطك صحيحة ، أود التعليق على اثنتين لا أوافق عليهما:

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

أشك بشدة في أن القائمة المرتبطة ستتمتع بأداء إجمالي أفضل ، فليكن السرعة أو كفاءة الذاكرة ، من مجموعة ديناميكية ذات نمو أسي. هذا الأخير مضمون لاحتلال مساحة تبلغ ضعف المساحة التي يستخدمها بالفعل ، بينما يستخدم List<some pointer> ثلاثة أضعاف مساحة التخزين (المحتوى الفعلي ، المؤشر التالي والمؤشر السابق). بالنسبة للصفيف المقسم ، تبدو الأشياء أفضل.

عندما يتم تغليفها بشكل صحيح (وبقدر ما يمكنني معرفة ما رأيته بالفعل من كود godot ، فهي كذلك) ، فإنها تبدو متشابهة إلى حد كبير بالنسبة للمبرمج ، لذلك لا أفهم ما تقصد بـ "هم [القوائم ] اجعل القصد من الشفرة أكثر وضوحًا ".

IMHO ، القوائم صالحة تمامًا في حالتين:

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

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

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

لكن reduz ، ما رأيك في قياس كل هذه التغييرات التي اقترحها @ vblanco20-1 (ربما حتى الآن ، في 3.1). إذا كان @ vblanco20-1 (أو أي شخص آخر) على استعداد لاستثمار الوقت في كتابة مثل هذه المجموعة المعيارية وتقييم أداء Godot3.1 (من حيث السرعة و "استهلاك الكومة بالنظر إلى التجزئة") مقابل تغييرات vblanco؟ قد ينتج عنه تلميحات قيمة للتغييرات 4.0 الفعلية.

أعتقد أن مثل هذه المنهجية تناسبك "تتم [الأشياء] بشكل صحيح بدلاً من التعجيل بها" بشكل جيد.

@ vblanco20-1: في الحقيقة ، أنا أقدر عملك. هل سيكون لديك الدافع لإنشاء مثل هذه المعايير ، حتى نتمكن بالفعل من قياس ما إذا كانت تغييراتك هي تحسينات فعلية للأداء؟ انا سوف اكون مثارا للغايه.

Windfisch أقترح عليك إعادة قراءة رسالتي أعلاه ، لأنك أساءت قراءة بعض الأشياء أو أسأت فهمها. سأوضحها لك.

  • تُستخدم القوائم بالضبط لحالة الاستخدام التي تصفها ، ولم أزعم أبدًا أن لديها أداءً أفضل. إنها أكثر كفاءة من المصفوفات لإعادة استخدام الكومة لمجرد أنها تتكون من تخصيصات صغيرة. على نطاق واسع (عندما تستخدمها كثيرًا لحالة الاستخدام المقصودة) ، فإن هذا يحدث فرقًا حقًا. عندما يكون الأداء مطلوبًا ، يتم بالفعل استخدام الحاويات الأخرى التي تكون أسرع أو أكثر حزمًا أو بها تماسك أفضل لذاكرة التخزين المؤقت. من أجل الخير أو السيئ ، ركز فيكتور في الغالب على واحدة من مناطق المحرك الأقدم (إن لم تكن الأقدم في الواقع) والتي لم يتم تحسينها أبدًا منذ أن كانت محركًا داخليًا يستخدم لنشر الألعاب لـ PSP. كانت هناك إعادة كتابة معلقة منذ وقت طويل ، ولكن كانت هناك أولويات أخرى. كان التحسين الرئيسي له هو تتبع مخروط فوكسل المعتمد على وحدة المعالجة المركزية الذي أضفته مؤخرًا ، ولكي أكون صادقًا ، فقد قمت بعمل سيئ في ذلك لأنه كان سريعًا جدًا بالقرب من الإصدار 3.0 ، لكن الإصلاح المناسب لهذا هو خوارزمية مختلفة تمامًا ، وليس مضيفًا معالجة موازية كما فعل.
  • لم أجادل أبدًا بشأن أداء عمل @ vblanco20-1 ، وبصراحة لا أهتم بهذا الأمر (لذلك لا تحتاج إلى إضاعة الوقت في إجراء المعايير). أسباب عدم دمج عمله هي لأن 1) الخوارزميات التي يستخدمها تحتاج إلى تعديل يدوي حسب متوسط ​​حجم الكائنات في اللعبة ، وهو أمر سيحتاج معظم مستخدمي Godot إلى القيام به. أميل إلى تفضيل الخوارزميات التي قد تكون أبطأ قليلاً ولكن مقياسها أفضل ، دون الحاجة إلى تعديلات. 2) الخوارزمية التي يستخدمها ليست جيدة لاستبعاد الانسداد (الثماني البسيط هو الأفضل بسبب الطبيعة الهرمية). 3) الخوارزمية التي يستخدمها ليست جيدة للإقران (غالبًا ما يكون SAP أفضل). 4) يستخدم C ++ 17 والمكتبات التي لست مهتمًا بدعمها ، أو lambdas التي أعتقد أنها غير ضرورية 5) أنا أعمل بالفعل على تحسين هذا لـ 4.0 ، وفرع 3.x لديه الاستقرار كأولوية ونحن تنوي إصدار 3.2 في أقرب وقت ممكن ، لذلك لن يتم تعديل هذا أو العمل عليه هناك. قد تكون الشفرة الحالية أبطأ ، لكنها مستقرة جدًا وتم اختبارها. إذا تم دمج هذا النملة ، فهناك تقارير أخطاء ، وانحدارات ، وما إلى ذلك. لن يكون لدى أحد الوقت للعمل على ذلك أو مساعدة فيكتور لأننا مشغولون بالفعل في الغالب بفرع 4.0. تم شرح كل هذا أعلاه ، لذا أقترح عليك إعادة قراءة المنشور.

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

Godot هو المصدر المفتوح وكذلك شوكة له. نحن جميعًا منفتحون ونشارك هنا ، فلا شيء يمنعك من استخدام عمله إذا كنت بحاجة إليه.

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

شكرا reduz للتوضيح الخاص بك. لقد أوضح بالفعل نقاطك أكثر من المنشورات السابقة.

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

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

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

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

reduz للإشارة إلى الفهرسة المكانية. تمت إزالة الانتقاء الأصلي القائم على البلاط واستبداله بـ Octree في منتصف الطريق تقريبًا خلال هذا الخيط. الأوكتري الذي حصلت عليه هناك هو العمل قيد التقدم (يفتقد بعض وظائف إعادة الملاءمة) ، لكن النتائج جيدة جدًا للنموذج الأولي. رمزها ليس جيدًا من طبيعة النموذج الأولي ، لذلك من المفيد فقط التحقق من كيفية أداء هذا النوع من الأوكتري في مشهد معقد مثل tps-demo.
إنه مستوحى من الطريقة التي يعمل بها المحرك غير الواقعي الثماني ، ولكن مع بعض التعديلات مثل إمكانية التكرار المسطح.

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

من خلال القيام بذلك ، يمكننا تقليل اختبار الوقت على العقد ، حيث لن ينتهي الأمر بالأوكتري إلى أن تكون عميقة جدًا.

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

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

لدي أيضًا عدد من الأوكتارات في مفترقتي ، والتي تُستخدم لتحسين بعض الأشياء. على سبيل المثال ، لديّ ثمانية فقط لكائنات Shadowcaster ، مما يسمح بتخطي كل المنطق المتعلق بـ "يمكن أن يلقي الظلال" عند استبعاد خرائط الظل.


فيما يتعلق بمخاوفي بشأن Vector والآخرين في واجهة برمجة التطبيقات الخاصة بالتقديم ، تشرح هذه المشكلة ما كنت قلقًا بشأنه. https://github.com/godotengine/godot/issues/24731


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


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


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

@ vblanco20-1 يبدو هذا رائعًا ، والطريقة التي تذكر بها أعمال الأوكتري منطقية جدًا. أنا بصراحة لا أمانع في تنفيذ قائمة انتظار متوازية (يبدو الأمر بسيطًا بما يكفي لعدم الحاجة إلى تبعية خارجية ، وأحتاج إلى C ++ 11 للقيام بذلك بشكل صحيح ، لذا خمن أن هذا سيحدث فقط في فرع Vulkan). إذا كنت قد التزمت بهذا ، فسأريد بالتأكيد إلقاء نظرة عليه ، حتى أتمكن من استخدامه كأساس لإعادة كتابة المفهرس في فرع Vulkan (أنا فقط أعيد كتابة معظم الأشياء هناك ، لذلك يجب إعادة كتابتها على أي حال ). بالطبع إذا كنت ترغب في المساعدة في التنفيذ (ربما عندما يكون هناك المزيد من الأشياء في مكانها مع واجهة برمجة التطبيقات الجديدة) ، فهذا موضع ترحيب كبير.

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

أجد أيضًا تطبيق Bullet 3 لـ dbvt مثيرًا للاهتمام ، والذي يقوم بموازنة ذاتية إضافية وتخصيص خطي ، إنه أحد الأشياء التي أردت البحث عنها أكثر (لقد رأيت هذا النهج مطبقًا في محركات خاصة لا يمكنني ذكرها: P) ، من حيث الخوارزمية ، فإن الشجرة الثنائية المتوازنة هي حسب التصميم أقل زائدة عن الحاجة من ثمانٍ ، وقد تعمل بشكل أفضل في كل من اختبارات الاقتران والانسداد ، لذلك قد يكون الطور العريض / الطرد القابل للتوصيل فكرة جيدة ، نظرًا لأننا نجعلها مفيدة. مشاهد معيارية.

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

من يحتاج إلى الأداء؟ :وجه القزم:

الأداء ونموذج المصدر هو ما يبقيني مع Godot.

تحرير: عذرًا ، ربما أكون بعيدًا عن الموضوع ولكني أردت توضيح فوائد المصدر المفتوح.

mixedCase سأكون مترددًا للغاية في دعم أي نوع من

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

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

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

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات