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

يريد مؤلف المادة ، التي ننشر ترجمتها اليوم ، أن يتحدث عن كيف وقع ذات مرة في فخ التحسين السابق لأوانه ، وكيف فهم من تجربته المريرة أن التحسين السابق لأوانه هو أصل كل الشرور.
لعبة GeoArena على الانترنت
قبل بضع سنوات كنت أعمل على لعبة GeoArena Online على الإنترنت (ثم بعتها ، قام الملاك الجدد بنشرها على
geoarena.io ). لقد كانت لعبة متعددة اللاعبين بأسلوب "آخر الناجين". هناك ، كان اللاعب يسيطر على السفينة ، ويقاتل أحدهما ضد لاعب آخر.
لعبة GeoArena على الانترنتلعبة GeoArena على الانترنتتتطلب اللعبة الديناميكية ، المليئة بالجزيئات والآثار ، موارد حاسوبية جدية. نتيجة لذلك ، تباطأت اللعبة على بعض أجهزة الكمبيوتر القديمة في لحظات متوترة بشكل خاص. أنا ، رجل لا يبالي بقضايا الإنتاجية ، تناول حل هذه المشكلة باهتمام. "كيف يمكنني تسريع جزء جافا سكريبت من جانب العميل GeoArena ،" سألت نفسي.
مكتبة Fast.js
بعد البحث قليلاً على الإنترنت ، اكتشفت مكتبة
fast.js. لقد كانت "مجموعة من التحسينات الجزئية التي تهدف إلى تبسيط تطوير برامج JavaScript سريعة للغاية." تم تسريع هذه المكتبة من خلال توفر تطبيقات أسرع للأساليب القياسية
المضمنة مثل
Array.prototype.forEach () .
لقد وجدت هذا مثير للاهتمام للغاية. استخدم GeoArena العديد من المصفوفات ، وأجرى العديد من العمليات باستخدام المصفوفات ، لذا فإن استخدام fast.js يمكن أن يساعدني جيدًا في تسريع اللعبة. تم تضمين النتائج التالية لدراسة أداء
forEach()
في
README لـ fast.js.
Native .forEach() vs fast.forEach() (10 items) ✓ Array::forEach() x 8,557,082 ops/sec ±0.37% (97 runs sampled) ✓ fast.forEach() x 8,799,272 ops/sec ±0.41% (97 runs sampled) Result: fast.js is 2.83% faster than Array::forEach().
كيف يمكن أن تكون الطريقة المطبقة في بعض المكتبات الخارجية أسرع من الإصدار القياسي؟ الشيء هو أنه كانت هناك خدعة واحدة (هذه الحيل موجودة في كل مكان تنظر إليه). كانت المكتبة مناسبة فقط للعمل مع المصفوفات التي لم تكن متفرقة.
فيما يلي بعض الأمثلة البسيطة لهذه المصفوفات:
من أجل فهم سبب عدم قدرة المكتبة على العمل بشكل طبيعي مع المصفوفات المتفرقة ، نظرت إلى الكود المصدر. اتضح أن تطبيق
forEach()
في fast.js يعتمد على الحلقات. قد يبدو التنفيذ السريع
forEach()
كالتالي:
استدعاء الأسلوب
fastForEach()
ثلاث قيم:
1 undefined 2
استدعاء
sparseArray.forEach()
يؤدي فقط إلى استنتاج قيمتين:
1 2
يظهر هذا الاختلاف نظرًا لحقيقة أن مواصفات JS المتعلقة باستخدام وظائف رد الاتصال تشير إلى أنه
لا ينبغي استدعاء هذه الوظائف
على مؤشرات صفيف بعيد أو غير مهيأ (تسمى أيضًا "فتحات"). لم
fastForEach()
تطبيق
fastForEach()
وجود مجموعة من الثقوب. هذا أدى إلى زيادة في السرعة على حساب العمل الصحيح مع صفائف متفرق. كان هذا مثاليًا بالنسبة لي ، حيث لم يتم استخدام المصفوفات المتفرقة في GeoArena.
في هذه المرحلة ، يجب أن أجرب اختبارًا سريعًا على fast.js. يجب أن أقوم بتثبيت المكتبة وتغيير الأساليب القياسية لكائن
Array
إلى أساليب من fast.js واختبار أداء اللعبة. لكن بدلاً من ذلك ، انتقلت في اتجاه مختلف تمامًا.
تطوري يسمى أسرع. js
أراد هوسي الكمال الذي يعيش في لي أن ضغط كل شيء تماما من تحسين أداء اللعبة. مكتبة fast.js ببساطة لا يبدو لي أنها حل جيد بما فيه الكفاية ، لأن استخدامها يعني استدعاء أساليبها. ثم فكرت: "ماذا لو استبدلت الطرق القياسية للصفائف بمجرد تضمين تطبيقات جديدة أسرع في هذه الأساليب في الكود؟ هذا من شأنه أن يوفر لي الحاجة إلى مكالمات طريقة المكتبة. "
كانت هذه الفكرة هي التي دفعتني إلى الفكرة العبقرية ، والتي كانت لإنشاء مترجم ، والتي وصفتها بوقاحة بشكل
أسرع .
js . خططت لاستخدامها بدلاً من fast.js. على سبيل المثال ، فيما يلي مقتطف شفرة المصدر:
سيقوم المحول البرمجي بشكل أسرع. js بتحويل هذا الرمز إلى التالي - بشكل أسرع ، ولكن يبدو أسوأ:
تم إنشاء إنشاء أسرع. js بواسطة نفس الفكرة التي قامت عليها fast.js. أي ، نحن نتحدث عن التحسينات الدقيقة للأداء بسبب رفض الدعم للصفائف المتفرقة.
للوهلة الأولى ، بدا لي أسرع. js تطورا ناجحا للغاية. فيما يلي بعض النتائج من دراسة أداء بشكل أسرع.
array-filter large ✓ native x 232,063 ops/sec ±0.36% (58 runs sampled) ✓ faster.js x 1,083,695 ops/sec ±0.58% (57 runs sampled) faster.js is 367.0% faster (3.386μs) than native array-map large ✓ native x 223,896 ops/sec ±1.10% (58 runs sampled) ✓ faster.js x 1,726,376 ops/sec ±1.13% (60 runs sampled) faster.js is 671.1% faster (3.887μs) than native array-reduce large ✓ native x 268,919 ops/sec ±0.41% (57 runs sampled) ✓ faster.js x 1,621,540 ops/sec ±0.80% (57 runs sampled) faster.js is 503.0% faster (3.102μs) than native array-reduceRight large ✓ native x 68,671 ops/sec ±0.92% (53 runs sampled) ✓ faster.js x 1,571,918 ops/sec ±1.16% (57 runs sampled) faster.js is 2189.1% faster (13.926μs) than native
نتائج الاختبار الكاملة يمكن العثور عليها
هنا . تم احتجازهم في Node v8.16.1 ، على جهاز MacBook Pro 2018 بحجم 15 بوصة.
هل تطوري أسرع بنسبة 2000٪ من التنفيذ القياسي؟ مثل هذه الزيادة الخطيرة في الإنتاجية هي ، بلا شك ، شيء يمكن أن يكون له أقوى تأثير إيجابي على أي برنامج. أليس كذلك؟
لا ، ليس صحيحا.
النظر في مثال بسيط.
- تخيل أن لعبة GeoArena المتوسطة تتطلب 5000 مللي ثانية (مللي ثانية) من الحساب.
- يقوم برنامج التحويل البرمجي بشكل أسرع. js بتسريع تنفيذ أساليب الصفيف بمعدل 10 مرات (وهذا تقدير تقريبي ، كما أنه يتم تقديره بشكل مبالغ فيه ؛ وفي معظم التطبيقات الحقيقية لا يوجد تسارع مزدوج).
وهنا السؤال الذي يهمنا حقًا: "أي جزء من 5000 مللي ثانية يتم إنفاقه على تنفيذ أساليب الصفيف؟".
لنفترض نصف. بمعنى ، يتم إنفاق 2500 مللي ثانية على أساليب الصفيف ، أما الـ 2500 مللي ثانية المتبقية في كل شيء آخر. إذا كان الأمر كذلك ، فسيوفر استخدام أسرع. js زيادة هائلة في الأداء.
مثال شرطي: يتم تقليل وقت تنفيذ البرنامج كثيرًاونتيجة لذلك ، اتضح أن إجمالي الوقت الحسابي قد انخفض بنسبة 45 ٪.
لسوء الحظ ، كل هذه الحجج بعيدة جدًا عن الواقع. GeoArena ، بالطبع ، يستخدم العديد من أساليب الصفيف. لكن التوزيع الفعلي لوقت تنفيذ التعليمات البرمجية للمهام المختلفة يبدو كما يلي.
حقيقة قاسيةللأسف ، ماذا يمكنني أن أقول.
هذا هو بالضبط الخطأ الذي حذر منه دونالد نوث. لم أبذل قصارى جهدي فيما ينبغي تقديمه لهم ، ولم أفعل ذلك عندما كان الأمر يستحق القيام به.
هنا الرياضيات البسيطة تأتي في اللعب. إذا استغرق شيء ما 1٪ فقط من وقت تنفيذ البرنامج ، فإن تحسينه سيعطي ، في أفضل الأحوال ، زيادة 1٪ فقط في الإنتاجية.
هذا هو بالضبط ما كان يدور في خلد دونالد نوث عندما قال "ليس حيث هو مطلوب". وإذا فكرت في "المكان الذي تحتاج إليه" ، اتضح أن هذه هي أجزاء البرامج التي تمثل اختناقات في الأداء. هذه هي أجزاء الكود التي تساهم مساهمة كبيرة في الأداء العام للبرنامج. هنا يتم استخدام مفهوم "الإنتاجية" بمعنى واسع للغاية. قد يشمل وقت تشغيل البرنامج وحجم الكود المترجم الخاص به وشيء آخر. أي تحسن بنسبة 10٪ في هذا الجزء من البرنامج الذي يؤثر بشكل كبير على الأداء أفضل من تحسن بنسبة 100٪ في بعض الأشياء الصغيرة.
تحدث كنوت أيضًا عن تطبيق الجهود "ليس عند الضرورة". الهدف من ذلك هو أنك تحتاج إلى تحسين شيء فقط عندما يكون ذلك ضروريًا. بالطبع ، كان لدي سبب وجيه للتفكير في التحسين. ولكن تذكر أنني بدأت في التطور بشكل أسرع. js وقبل ذلك ، لم أحاول حتى اختبار مكتبة fast.js في GeoArena؟ الدقائق التي قضاها في اختبار fast.js في لعبتي ستوفر لي أسابيع من العمل. آمل ألا تسقط في نفس المصيدة التي وقعت فيها.
النتائج
إذا كنت مهتمًا بتجربة أسرع. js ، يمكنك إلقاء نظرة على
هذا العرض التوضيحي. تعتمد النتائج التي تحصل عليها على جهازك ومتصفحك. هنا ، على سبيل المثال ، ما حدث في Chrome 76 على جهاز MacBook Pro 2018 بحجم 15 بوصة.
نتائج الاختبار Faster.jsقد تكون مهتمًا بمعرفة النتائج الفعلية لاستخدام بشكل أسرع. js في GeoArena. أنا ، عندما كانت اللعبة لا تزال لي (كما قلت ، لقد بعتها) ، أجرت بعض الأبحاث الأساسية. نتيجة لذلك ، اتضح ما يلي:
- يؤدي استخدام أسرع. js إلى تسريع تنفيذ دورة اللعبة الرئيسية في لعبة نموذجية بنسبة 1٪ تقريبًا.
- نظرًا لاستخدام أسرع. js ، زاد حجم حزمة اللعبة بنسبة 0.3٪. هذا تباطأ تحميل صفحة اللعبة قليلا. زاد حجم الحزمة نظرًا لأن أسرع. js يحول الرمز القصير القياسي إلى رمز أسرع ، ولكن أيضًا أطول.
بشكل عام ، يمتلك أسرع. js إيجابيات وسلبيات ، لكن هذا التطوير الخاص بي لم يكن له تأثير كبير على أداء GeoArena. كنت أفهم هذا قبل ذلك بكثير لو كنت قد ازعجت اختبار اللعبة أولاً باستخدام fast.js.
قد قصتي بمثابة تحذير لك.
أعزائي القراء! هل وقعت في فخ التحسين السابق لأوانه؟
