
مرحبا يا هبر!
غالبًا ما نكتب ونتحدث عن أداء PHP:
كيف نتعامل معه بشكل عام ،
وكيف وفرنا مليون دولار عند التبديل إلى PHP 7.0 ، وأيضًا
ترجمة المواد المختلفة حول هذا الموضوع. هذا يرجع إلى حقيقة أن جمهور منتجاتنا ينمو ، وتوسيع نطاق الواجهة الخلفية لـ PHP بالحديد مكلف للغاية - لدينا 600 خادم مع PHP-FPM. لذلك ، فإن استثمار الوقت في التحسين مفيد لنا.
من قبل ، تحدثنا بشكل أساسي عن الطرق المعتادة والمحددة بالفعل للعمل مع الإنتاجية. لكن مجتمع PHP في حالة تأهب! ستظهر JIT في PHP 8 ، وستظهر التحميل المسبق في PHP 7.4 ، وسيتم تطوير الأطر خارج إطار تطوير PHP الأساسي ، والتي تفترض أن PHP يعمل كخفي. حان الوقت لتجربة شيء جديد ومعرفة ما يمكن أن يعطينا هذا.
نظرًا لأن إصدار PHP 8 لا يزال بعيد المنال ، والأطر غير المتزامنة غير مناسبة لمهامنا (لماذا - سأقول أدناه) ، سنركز اليوم على التحميل المسبق ، الذي سيظهر في PHP 7.4 ، وإطار عمل شيطاني PHP ، RoadRunner.
هذه هي النسخة النصية من تقريري مع
Badoo PHP Meetup # 3 . فيديو لجميع الخطب التي
جمعناها في هذا المنصب .
تعمل PHP-FPM و Apache mod_php وطرق مماثلة لتشغيل البرامج النصية لـ PHP وطلبات المعالجة (التي تديرها الغالبية العظمى من المواقع والخدمات ؛ من أجل البساطة ، سأدعوهم "كلاسيكيين" PHP) للعمل على أساس
لا شيء مشترك بالمعنى الواسع للكلمة:
- لا يتم تفتيش الدولة بين عمال PHP ؛
- لا يتم تفتيش الدولة بين الطلبات المختلفة.
النظر في هذا مع مثال على برنامج نصي بسيط:
لكل طلب ، يتم تنفيذ البرنامج النصي من الأول إلى السطر الأخير: على الرغم من حقيقة أن التهيئة ، على الأرجح ، لن تختلف عن الطلب إلى الطلب ويمكن تنفيذه على الأرجح مرة واحدة (توفير الموارد) ، لا يزال يتعين عليك تكراره لكل طلب. لا يمكننا فقط أخذ المتغيرات وحفظها (على سبيل المثال ،
$app
) بين الطلبات بسبب خصائص كيفية عمل PHP "الكلاسيكي".
كيف سيبدو إذا تجاوزنا نطاق PHP "الكلاسيكي"؟ على سبيل المثال ، يمكن تشغيل البرنامج النصي لدينا بغض النظر عن الطلب ، والتهيئة والحصول على حلقة استعلام بداخله ، والتي بداخلها سينتظر الحل التالي ، ومعالجته وتكرار الحلقة دون تنظيف البيئة (يشار إليها فيما يلي باسم "PHP كـ daemon") ").
لم نتمكن فقط من التخلص من التهيئة المتكررة لكل طلب ، ولكن أيضًا حفظ قائمة المدن مرة واحدة إلى متغير
$cities
واستخدامها من طلبات مختلفة دون الوصول إلى أي مكان باستثناء الذاكرة (هذه هي أسرع طريقة للحصول على أي بيانات).
أداء مثل هذا الحل يحتمل أن يكون أعلى بكثير من أداء PHP "الكلاسيكي". لكن عادةً لا يتم إعطاء الزيادة في الإنتاجية مجانًا - عليك دفع بعض الثمن مقابل ذلك. دعونا نرى ما يمكن أن يكون في حالتنا.
للقيام بذلك ، لنقم بتعقيد البرنامج النصي الخاص بنا قليلاً وبدلاً من عرض متغير
$name
، سنقوم بتعبئة المصفوفة:
- $name = $cities[$req->getCookie('city_id')]; + $names[] = $cities[$req->getCookie('city_id')];
في حالة PHP "الكلاسيكية" ، لن تنشأ أي مشاكل - في نهاية الاستعلام ، سيتم إتلاف متغير
$name
وسيعمل كل طلب لاحق كما هو متوقع. في حالة بدء تشغيل PHP كخفي ، سيضيف كل طلب مدينة أخرى إلى هذا المتغير ، مما سيؤدي إلى نمو غير منضبط للصفيف حتى نفاد الذاكرة على الجهاز.
بشكل عام ، قد لا تنتهي الذاكرة فقط - قد تحدث بعض الأخطاء الأخرى التي تؤدي إلى موت العملية. في مثل هذه المشكلات ، يتعامل PHP "الكلاسيكي" تلقائيًا. في حالة بدء تشغيل PHP كخفي ، نحتاج إلى مراقبة هذا البرنامج الخفي بطريقة ما ، وإعادة تشغيله إذا تعطل.
الأخطاء من هذا النوع غير سارة ، ولكن توجد حلول فعالة لها. يكون الأمر أسوأ بكثير إذا لم ينخفض البرنامج النصي ، بسبب وجود خطأ ، ولكن بشكل غير متوقع يغير قيم بعض المتغيرات (على سبيل المثال ، فإنه يمسح صفيف
$cities
city). في هذه الحالة ، ستعمل جميع الطلبات اللاحقة مع بيانات غير صحيحة.
للتلخيص ، من الأسهل كتابة كود لـ PHP "الكلاسيكي" (PHP-FPM و Apache mod_php وما شابه ذلك) - إنه يحررنا من عدد من المشاكل والأخطاء. ولكن لهذا نحن ندفع مع الأداء.
من الأمثلة أعلاه ، نرى أنه في بعض أجزاء الكود ، ينفق PHP الموارد التي لم يكن من الممكن إنفاقها (أو إهدارها مرة واحدة) في معالجة كل طلب من الطلب "الكلاسيكي". هذه هي المجالات التالية:
- اتصال الملف (تشمل ، تتطلب ، وما إلى ذلك) ؛
- التهيئة (الإطار ، المكتبات ، حاوية DI ، إلخ) ؛
- طلب بيانات من وحدة التخزين الخارجية (بدلاً من التخزين في الذاكرة).
ظل PHP موجودًا منذ سنوات عديدة وربما أصبح يتمتع بشعبية بفضل هذا النموذج من العمل. خلال هذا الوقت ، تم تطوير العديد من الطرق بدرجات متفاوتة من النجاح لحل المشكلة الموصوفة. ذكرت بعضها في مقالتي السابقة. اليوم سنناقش حلين جديدين إلى حد ما للمجتمع: التحميل المسبق و RoadRunner.
التحميل المسبق
من بين النقاط الثلاث المذكورة أعلاه ،
تم تصميم
التحميل المسبق للتعامل مع النفقات العامة الأولى عند توصيل الملفات. للوهلة الأولى ، قد يبدو هذا غريبًا وبلا معنى ، لأن PHP يحتوي بالفعل على OPcache ، والذي تم إنشاؤه لهذا الغرض فقط. لفهم الجوهر ، دعنا نتعرف على حقيقة الأمر بمساعدة
perf
، التي يتم تمكين OPcache عليها ، مع معدل ضرب يساوي 100٪.

على الرغم من OPcache ، نرى أن
persistent_compile_file
يستغرق 5.84٪ من وقت تنفيذ الاستعلام.
لفهم سبب حدوث ذلك ، يمكننا أن ننظر إلى مصادر
zend_accel_load_script . يمكن أن نرى منهم أنه على الرغم من وجود OPcache ، مع كل مكالمة
include/require
يتم نسخ توقيعات الفئات والوظائف من الذاكرة المشتركة إلى ذاكرة العملية المنفذة ، ويتم إنجاز العديد من الأعمال المساعدة. ويجب أن يتم هذا العمل لكل طلب ، لأنه في نهاية الأمر يتم مسح ذاكرة العملية المنفذة.

يتفاقم هذا بسبب العدد الكبير من مكالمات التضمين / التي نطلبها عادةً في طلب واحد. على سبيل المثال ، يتضمن Symfony 4 حوالي 310 ملفات قبل تنفيذ السطر المفيد الأول من التعليمات البرمجية. يحدث هذا ضمنيًا في بعض الأحيان: لإنشاء مثيل للفئة A ، كما هو موضح أدناه ، سيقوم PHP تلقائيًا بتحميل جميع الفئات الأخرى (B و C و D و E و F و G). وبصفة خاصة في هذا الصدد ، تبرز تبعيات الملحن التي تعلن عن وظائف: للتأكد من أن هذه الوظائف ستكون متاحة أثناء تنفيذ رمز المستخدم ، يضطر الملحن دائمًا إلى توصيلها بغض النظر عن الاستخدام ، نظرًا لأن PHP لا يحتوي على وظائف التحميل التلقائي ولا يمكن تحميلها في وقت المكالمة.
class A extends \B implements \C { use \D; const SOME_CONST = \E::E1; private static $someVar = \F::F1; private $anotherVar = \G::G1; }
كيف يعمل التحميل المسبق
يحتوي التحميل المسبق على إعداد رئيسي واحد ، opcache.preload ، يتم فيه تمرير المسار إلى البرنامج النصي PHP. سيتم تنفيذ هذا البرنامج النصي مرة واحدة عند بدء تشغيل PHP-FPM / Apache / ، وما إلى ذلك ، وستكون جميع توقيعات الفئات والطرق والوظائف التي سيتم الإعلان عنها في هذا الملف متاحة لجميع النصوص التي تعالج الطلبات من السطر الأول من تنفيذها (مهم ملاحظة: هذا لا ينطبق على المتغيرات والثوابت العالمية - سيتم إعادة تعيين قيمها إلى الصفر بعد نهاية مرحلة التحميل المسبق). لم تعد بحاجة إلى إجراء / طلب مكالمات ونسخ تواقيع الوظيفة / الفئة من الذاكرة المشتركة إلى ذاكرة العملية: تم إعلانها جميعًا
غير قابلة للتغيير وبسبب هذا ، يمكن لجميع العمليات الرجوع إلى موقع الذاكرة نفسه الذي يحتوي عليها.
عادة ما تكون الفئات والوظائف التي نحتاجها في ملفات مختلفة ومن غير المريح دمجها في برنامج نصي للتحميل المسبق. ولكن هذا لا يلزم القيام به: نظرًا لأن التحميل المسبق هو برنامج نصي PHP عادي ، يمكننا فقط استخدام include / requ أو opcache_compile_file () من البرنامج النصي للتحميل المسبق لجميع الملفات التي نحتاجها. بالإضافة إلى ذلك ، نظرًا لأن كل هذه الملفات سيتم تحميلها مرة واحدة ، فسيتمكن PHP من إجراء تحسينات إضافية لا يمكن القيام بها أثناء قيامنا بتوصيل هذه الملفات بشكل منفصل في وقت الاستعلام. يقوم PHP بإجراء تحسينات فقط في إطار كل ملف منفصل ، ولكن في حالة التحميل المسبق ، لكل الكود الذي تم تحميله في مرحلة التحميل المسبق.
معايير التحميل المسبق
لإثبات فوائد التحميل المسبق في الممارسة العملية ، أخذت نقطة نهاية واحدة مرتبطة بـ CPU بوحدة المعالجة المركزية. تتميز الواجهة الخلفية لدينا عمومًا بالتحميل المرتبط بوحدة المعالجة المركزية. هذه الحقيقة هي إجابة السؤال لماذا لم نفكر في الأطر غير المتزامنة: فهي لا تعطي أي ميزة في حالة التحميل المرتبط بوحدة المعالجة المركزية وفي نفس الوقت تزيد من تعقيد الشفرة (يجب كتابتها بشكل مختلف) ، وكذلك للعمل مع شبكة أو قرص ، إلخ. مطلوبة برامج تشغيل غير متزامنة خاصة.
من أجل تقدير فوائد التحميل المسبق تمامًا ، بالنسبة للتجربة ، قمت بتنزيلها مع جميع الملفات التي يحتاجها البرنامج النصي للاختبار أثناء العمل ، وقمت بتحميله بمظهر يشبه عبء الإنتاج العادي باستخدام
wrk2 - وهو تناظرية أكثر تقدماً من Apache Benchmark ، ولكن بنفس البساطة .
لتجربة التحميل المسبق ، يجب أولاً الترقية إلى PHP 7.4 (لدينا الآن PHP 7.2). قمت بقياس أداء PHP 7.2 ، PHP 7.4 دون التحميل المسبق و PHP 7.4 مع التحميل المسبق. والنتيجة هي مثل هذه الصورة:

وبالتالي ، فإن الانتقال من PHP 7.2 إلى PHP 7.4 يمنح + 10 ٪ إلى الأداء في نقطة النهاية ، ويمنح التحميل المسبق 10 ٪ أخرى من أعلاه.
في حالة التحميل المسبق ، ستعتمد النتائج إلى حد كبير على عدد الملفات المتصلة وتعقيد المنطق القابل للتنفيذ: إذا كان هناك العديد من الملفات متصلة وكان المنطق بسيطًا ، فإن التحميل المسبق سيعطي أكثر مما إذا كان هناك عدد قليل من الملفات وكان المنطق معقدًا.
الفروق الدقيقة في التحميل المسبق
ما يزيد من الإنتاجية عادة ما يكون الجانب السلبي. التحميل المسبق لديه الكثير من الفروق الدقيقة ، والتي سأقدمها أدناه. يجب أخذها جميعًا في الاعتبار ، لكن واحدًا (الأول) فقط يمكن أن يكون أساسيًا.
التغيير - إعادة التشغيل
نظرًا لأن جميع ملفات التحميل المسبق يتم تصنيفها فقط عند بدء التشغيل ، ويتم تمييزها على أنها غير قابلة للتغيير ولن يتم إعادة تجميعها في المستقبل ، فإن الطريقة الوحيدة لتطبيق التغييرات على هذه الملفات هي إعادة التشغيل (إعادة التحميل أو إعادة التشغيل) PHP-FPM / Apache / ، إلخ.
في حالة إعادة التحميل ، يحاول PHP إعادة التشغيل بأكبر قدر ممكن من الدقة: لن تتم مقاطعة طلبات المستخدم ، ولكن مع ذلك ، بينما تكون مرحلة التحميل المسبق قيد التقدم ، ستنتظر جميع الطلبات الجديدة حتى تكتمل. إذا لم يكن هناك الكثير من التعليمات البرمجية في التحميل المسبق ، فقد لا يتسبب ذلك في حدوث مشكلات ، ولكن إذا حاولت تنزيل التطبيق بالكامل ، فسيكون ذلك محفوفًا بزيادة ملحوظة في وقت الاستجابة أثناء إعادة التشغيل.
أيضًا ، ميزة إعادة التشغيل (بغض النظر عما إذا كانت إعادة التحميل أو إعادة التشغيل) لها ميزة مهمة - كنتيجة لهذا الإجراء ، يتم مسح OPcache. وهذا يعني أن جميع الطلبات بعد ذلك ستعمل مع ذاكرة التخزين المؤقت للشفرة الباردة ، والتي يمكن أن تزيد من وقت الاستجابة أكثر.
شخصيات غير محددة
لتحميل التحميل المسبق لفصل دراسي ، يجب تحديد كل شيء يعتمد عليه حتى هذه المرحلة. بالنسبة للفئة أدناه ، يعني ذلك أن جميع الفئات الأخرى (B و C و D و E و F و G) والمتغير
$someGlobalVar
someGlobalVar وثابت SOME_CONST يجب أن تكون متاحة قبل تجميع هذه الفئة. نظرًا لأن البرنامج النصي للتحميل هو مجرد كود PHP منتظم ، يمكننا تحديد أداة التحميل التلقائي. في هذه الحالة ، سيتم تلقائيًا تحميل كل ما يتعلق بالفئات الأخرى. لكن هذا لا يعمل مع المتغيرات والثوابت: يجب أن نتأكد من تعريفنا في وقت الإعلان عن هذه الفئة.
class A extends \B implements \C { use \D; const SOME_CONST = \E::E1; private static $someVar = \F::F1; private $anotherVar = \G::G1; private $varLink = $someGlobalVar; private $constLink = SOME_CONST; }
لحسن الحظ ، يحتوي التحميل المسبق على أدوات كافية لفهم ما إذا كنت قد خرجت عن الطريق أم لا. أولاً ، هذه رسائل تحذير تحتوي على معلومات حول ما فشل تحميله ولماذا:
PHP Warning: Can't preload class MyTestClass with unresolved initializer for constant RAND in /local/preload-internal.php on line 6 PHP Warning: Can't preload unlinked class MyTestClass: Unknown parent AnotherClass in /local/preload-internal.php on line 5
ثانيًا ، يضيف التحميل المسبق قسمًا منفصلًا إلى نتيجة دالة opcache_get_status () ، والذي يوضح ما تم تحميله بنجاح في مرحلة التحميل المسبق:

حقل الصف / التحسين المستمر
كما كتبت أعلاه ، التحميل المسبق يحل قيم الحقول / ثوابت الفصل ويحفظها. يتيح لك هذا تحسين الكود: أثناء معالجة الطلب ، تكون البيانات جاهزة ولا تحتاج إلى اشتقاقها من بيانات أخرى. ولكن هذا يمكن أن يؤدي إلى نتائج غير واضحة ، والتي يوضحها المثال التالي:
const.php: <?php define('MYTESTCONST', mt_rand(1, 1000));
preload.php: <?php include 'const.php'; class MyTestClass { const RAND = MYTESTCONST; }
script.php: <?php include 'const.php'; echo MYTESTCONST, ', ', MyTestClass::RAND;
والنتيجة هي موقف مضاد للحدس: يبدو أن الثوابت يجب أن تكون متساوية ، حيث تم تعيين واحد منهم بقيمة الآخر ، ولكن في الواقع هذا ليس كذلك. ويرجع ذلك إلى حقيقة أن الثوابت العمومية ، على عكس الثوابت / الحقول ، يتم مسحها بالقوة بعد انتهاء مرحلة التحميل المسبق ، بينما يتم حل ثوابت / حقول الفصل وحفظها. وهذا يؤدي إلى حقيقة أنه أثناء تنفيذ الطلب ، يتعين علينا تحديد الثابت العام مرة أخرى ، ونتيجة لذلك يمكن أن تحصل على قيمة مختلفة.
لا يمكن إعادة تعريف someFunc ()
في حالة الفئات ، يكون الموقف بسيطًا: عادةً لا نربطهم بشكل صريح ، لكننا نستخدم أداة تحميل تلقائي. هذا يعني أنه إذا تم تعريف فئة ما في مرحلة التحميل المسبق ، فلن يتم تنفيذ برنامج التحميل التلقائي ببساطة أثناء الطلب ولن نحاول توصيل هذه الفئة مرة أخرى.
يختلف الوضع مع الوظائف: يجب أن نربطها بوضوح. يمكن أن يؤدي ذلك إلى موقف حيث في البرنامج النصي للتحميل المسبق ، سوف نقوم بتوصيل جميع الملفات الضرورية بالوظائف ، وخلال الطلب سنحاول القيام بذلك مرة أخرى (مثال نموذجي هو أداة تحميل booter للملحن: سيحاول دائمًا توصيل جميع الملفات بالوظائف). في هذه الحالة ، حصلنا على خطأ: تم تعريف الوظيفة بالفعل ولا يمكن إعادة تعريفها.
يمكن حل هذه المشكلة بطرق مختلفة. في حالة الملحن ، يمكنك ، على سبيل المثال ، توصيل كل شيء في مرحلة التحميل المسبق ، وعدم توصيل أي شيء متعلق بالملحن أثناء الطلبات. الحل الآخر هو عدم توصيل الملفات بالوظائف مباشرة ، ولكن للقيام بذلك من خلال ملف وكيل مع التحقق من وجود function_exists () ، على سبيل المثال ،
يفعل Guzzle HTTP.

لم يتم إصدار PHP 7.4 رسميًا بعد (حتى الآن)
ستصبح هذه الفروق الدقيقة غير ذات صلة بعد مرور بعض الوقت ، ولكن حتى الآن لم يتم إصدار إصدار PHP 7.4 رسميًا بعد ،
وكتب فريق PHP في ملاحظات الإصدار بشكل صريح: "الرجاء عدم استخدام هذا الإصدار في الإنتاج ، إنه إصدار اختبار مبكر". خلال تجاربنا مع التحميل المسبق ، صادفنا العديد من الأخطاء ، وقمنا بإصلاحها بأنفسنا وحتى
أرسلنا شيئًا ما إلى المنبع. لتجنب المفاجآت ، من الأفضل انتظار الإصدار الرسمي.
رود رنر
RoadRunner هي عبارة عن برنامج خفي مكتوب في Go ، والذي ، من ناحية ، ينشئ عمال PHP ويرصدهم (يبدأ / ينتهي / يعيد التشغيل حسب الضرورة) ، ومن ناحية أخرى ، يقبل الطلبات ويمررها إلى هؤلاء العمال. في هذا المعنى ، لا يختلف عملها عن عمل PHP-FPM (حيث توجد أيضًا عملية رئيسية تراقب العمال). ولكن لا تزال هناك اختلافات. المفتاح هو أن RoadRunner لا تقوم بإعادة تعيين حالة البرنامج النصي بعد الانتهاء من الاستعلام.
وبالتالي ، إذا استذكرنا قائمتنا المتعلقة بالموارد التي يتم إنفاقها في حالة PHP "الكلاسيكية" ، فإن RoadRunner تسمح لك بالتعامل مع جميع النقاط (التحميل المسبق ، كما نتذكر ، هو الأول فقط):
- اتصال الملف (تشمل ، تتطلب ، وما إلى ذلك) ؛
- التهيئة (الإطار ، المكتبات ، حاوية DI ، إلخ) ؛
- طلب بيانات من وحدة التخزين الخارجية (بدلاً من التخزين في الذاكرة).
مثال Hello World RoadRunner يبدو كالتالي:
$relay = new Spiral\Goridge\StreamRelay(STDIN, STDOUT); $psr7 = new Spiral\RoadRunner\PSR7Client(new Spiral\RoadRunner\Worker($relay)); while ($req = $psr7->acceptRequest()) { $resp = new \Zend\Diactoros\Response(); $resp->getBody()->write("hello world"); $psr7->respond($resp); }
سنحاول استخدام نقطة النهاية الحالية ، التي اختبرناها مع التحميل المسبق ، على RoadRunner دون تعديلات ، وقم بتحميلها وقياس الأداء. لا توجد تعديلات - لأنه بخلاف ذلك لن يكون المعيار صادقًا تمامًا.
دعنا نحاول تكييف مثال Hello World لهذا.
أولاً ، كما كتبت أعلاه ، لا نريد أن يسقط العامل في حالة حدوث خطأ. للقيام بذلك ، نحن بحاجة إلى التفاف كل شيء في محاولة عالمية..القبض. ثانياً ، نظرًا لأن البرنامج النصي الخاص بنا لا يعرف شيئًا عن Zend Diactoros ، للحصول على الإجابة ، سنحتاج إلى تحويل نتائجها. لهذا نستخدم وظائف ob_. ثالثًا ، البرنامج النصي لدينا لا يعرف شيئًا عن طبيعة طلب PSR-7. الحل هو ملء بيئة PHP القياسية من هذه الكيانات. ورابعا ، سيناريونا يتوقع أن يموت الطلب وسيتم مسح الحالة بأكملها. لذلك ، مع RoadRunner سنحتاج إلى القيام بهذا التنظيف بأنفسنا.
وبالتالي ، تتحول نسخة Hello World الأولية إلى شيء مثل هذا:
while ($req = $psr7->acceptRequest()) { try { $uri = $req->getUri(); $_COOKIE = $req->getCookieParams(); $_POST = $req->getParsedBody(); $_SERVER = [ 'REQUEST_METHOD' => $req->getMethod(), 'HTTP_HOST' => $uri->getHost(), 'DOCUMENT_URI' => $uri->getPath(), 'SERVER_NAME' => $uri->getHost(), 'QUERY_STRING' => $uri->getQuery(),
معايير RoadRunner
حسنا ، لقد حان الوقت لإطلاق المعايير.

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

في نتائج perf ، نرى phar_compile_file. هذا لأننا نقوم بتضمين بعض الملفات أثناء تنفيذ البرنامج النصي ، وبما أن OPcache غير ممكن (يقوم RoadRunner بتشغيل البرامج النصية مثل CLI ، حيث يتم إيقاف تشغيل OPcache افتراضيًا) ، يتم تجميع هذه الملفات مرة أخرى مع كل طلب.
تحرير تكوين RoadRunner - تمكين OPcache:


هذه النتائج تشبه بالفعل ما توقعنا رؤيته: بدأت RoadRunner في إظهار أداء أكثر من التحميل المسبق. ولكن ربما سنكون قادرين على الحصول على المزيد!
لا يبدو أن هناك شيئًا غير مألوف بالنسبة إلى perf - فلنلقِ نظرة على كود PHP. أسهل طريقة لملف التعريف هو استخدام
phpspy : إنه لا يتطلب أي تعديل لرمز PHP - تحتاج فقط إلى تشغيله في وحدة التحكم. دعونا نفعل هذا وبناء الرسم البياني لهب:

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

الجزء الرئيسي منه يأتي إلى استدعاء fread () ، بالكاد يمكن القيام بأي شيء مع هذا. لكننا نرى بعض الفروع الأخرى في
\ Spiral \ RoadRunner \ PSR7Client :: acceptRequest () ، باستثناء
freak نفسه. يمكنك فهم معناها من خلال النظر في شفرة المصدر:
public function acceptRequest() { $rawRequest = $this->httpClient->acceptRequest(); if ($rawRequest === null) { return null; } $_SERVER = $this->configureServer($rawRequest['ctx']); $request = $this->requestFactory->createServerRequest( $rawRequest['ctx']['method'], $rawRequest['ctx']['uri'], $_SERVER ); parse_str($rawRequest['ctx']['rawQuery'], $query); $request = $request ->withProtocolVersion(static::fetchProtocolVersion($rawRequest['ctx']['protocol'])) ->withCookieParams($rawRequest['ctx']['cookies']) ->withQueryParams($query) ->withUploadedFiles($this->wrapUploads($rawRequest['ctx']['uploads']));
يصبح من الواضح أن RoadRunner تحاول إنشاء كائن طلب متوافق مع PSR-7 باستخدام صفيف متسلسل. إذا كان إطارك يعمل مع كائنات استعلام PSR-7 مباشرة (على سبيل المثال ، Symfony
لا يعمل ) ، فسيكون هذا مبررًا تمامًا. في حالات أخرى ، يصبح PSR-7 رابطًا إضافيًا قبل تحويل الطلب إلى ما يمكن أن يعمل به التطبيق الخاص بك. دعنا نزيل هذا الرابط الوسيط وننظر إلى النتائج مرة أخرى:

كان البرنامج النصي للاختبار سهلاً للغاية ، لذا تمكنت من استبعاد حصة كبيرة من الأداء - + 17٪ مقارنةً بـ PHP الخالص (أذكر أن التحميل المسبق يمنح + 10٪ على نفس البرنامج النصي).
الفروق الدقيقة في RoadRunner
بشكل عام ، يعد استخدام RoadRunner تغييرًا أكثر خطورة من مجرد تضمين التحميل المسبق ، وبالتالي فإن الفروق الدقيقة هنا أكثر أهمية.
-, RoadRunner, , PHP- , , , : , , .
-, RoadRunner , «» — . / RoadRunner ; , , , , - .
-, endpoint', , , RoadRunner. .
استنتاج
, «» PHP, , preload RoadRunner.
PHP «» (PHP-FPM, Apache mod_php ) . - , . , , preload JIT.
, , , RoadRunner, .
, (: ):
- PHP 7.2 — 845 RPS;
- PHP 7.4 — 931 RPS;
- RoadRunner — 987 RPS;
- PHP 7.4 + preload — 1030 RPS;
- RoadRunner — 1089 RPS.
Badoo PHP 7.4 , ( ).
RoadRunner , , , , .
شكرا لاهتمامكم!