سمعت مؤخرًا مرة أخرى أن العاملين في PHP ما زالوا يتحدثون عن علامات الاقتباس المفردة مقابل علامات الاقتباس المزدوجة وأن استخدام علامات الاقتباس المفردة هو مجرد تحسين صغير ولكن إذا اعتدت على استخدام علامات الاقتباس المفردة طوال الوقت، فستوفر مجموعة كبيرة من وحدة المعالجة المركزية دورات!
"لقد قيل كل شيء بالفعل، ولكن ليس من قبل الجميع بعد" - كارل فالنتين
بهذه الروح أكتب مقالًا عن نفس الموضوع الذي كتبه نيكيتا بوبوف منذ 12 عامًا (إذا كنت تقرأ مقالته، يمكنك التوقف عن القراءة هنا).
يقوم PHP بإجراء استيفاء السلسلة، حيث يبحث عن استخدام المتغيرات في السلسلة ويستبدلها بقيمة المتغير المستخدم:
$juice = "apple"; echo "They drank some $juice juice."; // will output: They drank some apple juice.
تقتصر هذه الميزة على السلاسل الموجودة بين علامات الاقتباس المزدوجة والمستند. سيؤدي استخدام علامات الاقتباس المفردة (أو nowdoc) إلى نتيجة مختلفة:
$juice = "apple"; echo 'They drank some $juice juice.'; // will output: They drank some $juice juice.
انظر إلى ذلك: لن يقوم PHP بالبحث عن المتغيرات في تلك السلسلة المفردة المقتبسة. لذلك يمكننا البدء باستخدام علامات الاقتباس المفردة في كل مكان. لذلك بدأ الناس باقتراح تغييرات مثل هذه ..
- $juice = "apple"; $juice = 'apple';
.. لأنه سيكون أسرع وسيوفر مجموعة من دورات وحدة المعالجة المركزية مع كل تنفيذ لهذا الكود لأن PHP لا تبحث عن متغيرات في سلاسل مفردة مقتبسة (وهي غير موجودة في المثال على أي حال) و الجميع سعداء، القضية مغلقة.
من الواضح أن هناك اختلافًا في استخدام علامات الاقتباس المفردة مقابل علامات الاقتباس المزدوجة، ولكن لكي نفهم ما يحدث، نحتاج إلى التعمق أكثر.
على الرغم من أن PHP هي لغة مفسرة، إلا أنها تستخدم خطوة ترجمة حيث تلعب أجزاء معينة معًا للحصول على شيء يمكن للجهاز الظاهري تنفيذه بالفعل، وهو أكواد التشغيل. إذًا كيف يمكننا الانتقال من كود مصدر PHP إلى أكواد التشغيل؟
يقوم المعجم بمسح ملف الكود المصدري ويقسمه إلى رموز مميزة. يمكن العثور على مثال بسيط لما يعنيه هذا في وثائق الدالة token_get_all() . كود مصدر PHP يتكون فقط من
T_OPEN_TAG (يمكننا رؤية هذا على أرض الواقع واللعب به في هذا المقتطف 3v4l.org.
المحلل اللغوي
يأخذ المحلل اللغوي هذه الرموز المميزة ويقوم بإنشاء شجرة بناء جملة مجردة منها. يبدو تمثيل AST للمثال أعلاه بهذا الشكل عند تمثيله كـ JSON:
{ "data": [ { "nodeType": "Stmt_Echo", "attributes": { "startLine": 1, "startTokenPos": 1, "startFilePos": 6, "endLine": 1, "endTokenPos": 4, "endFilePos": 13 }, "exprs": [ { "nodeType": "Scalar_String", "attributes": { "startLine": 1, "startTokenPos": 3, "startFilePos": 11, "endLine": 1, "endTokenPos": 3, "endFilePos": 12, "kind": 2, "rawValue": "\"\"" }, "value": "" } ] } ] }في حال كنت تريد اللعب بهذا أيضًا ومعرفة كيف يبدو AST للرموز الأخرى، فقد وجدت https://phpast.com/ بواسطة Ryan Chandler وhttps://php-ast-viewer.com/ والذي يعرض لك كلاهما AST لجزء معين من كود PHP.
المترجم
يأخذ المترجم AST ويقوم بإنشاء أكواد التشغيل. أكواد التشغيل هي الأشياء التي ينفذها الجهاز الظاهري، وهي أيضًا ما سيتم تخزينه في OPcache إذا كان لديك هذا الإعداد وتمكينه (وهو ما أوصي به بشدة).
لعرض أكواد التشغيل لدينا خيارات متعددة (ربما أكثر، لكنني أعرف هذه الخيارات الثلاثة):
- استخدم ملحق تفريغ منطق فولكان. يتم خبزه أيضًا في 3v4l.org
- استخدم phpdbg -p script.php لتفريغ أكواد التشغيل
- أو استخدم إعداد opcache.opt_debug_level INI لـ OPcache لجعله يطبع أكواد التشغيل
- قيمة 0x10000 تنتج أكواد التشغيل قبل التحسين
- قيمة 0x20000 تنتج أكواد التشغيل بعد التحسين
$ echo ' foo.php $ php -dopcache.opt_debug_level=0x10000 foo.php $_main: ... 0000 ECHO string("") 0001 RETURN int(1)فرضية
بالعودة إلى الفكرة الأولية المتمثلة في حفظ دورات وحدة المعالجة المركزية عند استخدام علامات الاقتباس المفردة مقابل علامات الاقتباس المزدوجة، أعتقد أننا جميعًا نتفق على أن هذا لن يكون صحيحًا إلا إذا قامت PHP بتقييم هذه السلاسل في وقت التشغيل لكل طلب على حدة.
ماذا يحدث في وقت التشغيل؟
لذلك دعونا نرى أي أكواد التشغيل التي تنشئها PHP للإصدارين المختلفين.
علامات الاقتباس المزدوجة:
0000 ECHO string("apple") 0001 RETURN int(1)مقابل. اقتباسات مفردة:
0000 ECHO string("apple") 0001 RETURN int(1)مهلا، انتظر، حدث شيء غريب. هذا يبدو متطابقا! أين ذهب التحسين الجزئي الخاص بي؟
حسنًا ربما، فقط ربما يقوم تنفيذ معالج كود التشغيل ECHO بتحليل السلسلة المحددة، على الرغم من عدم وجود علامة أو أي شيء آخر يخبره بذلك ... حسنًا؟
دعونا نجرب طريقة مختلفة ونرى ما يفعله المعجم في هاتين الحالتين:
علامات الاقتباس المزدوجة:
T_OPEN_TAG (مقابل. اقتباسات مفردة:
Line 1: T_OPEN_TAG (لا تزال الرموز المميزة تميز بين علامات الاقتباس المزدوجة والمفردة، ولكن التحقق من AST سيعطينا نتيجة متطابقة في كلتا الحالتين - والفرق الوحيد هو القيمة الأولية في سمات العقدة Scalar_String، التي لا تزال تحتوي على علامات الاقتباس المفردة/المزدوجة، ولكن تستخدم القيمة علامات الاقتباس المزدوجة في كلتا الحالتين.
فرضية جديدة
هل يمكن أن يتم استيفاء السلسلة بالفعل في وقت الترجمة؟
دعونا نتحقق من خلال مثال أكثر "تعقيدًا" قليلاً:
الرموز المميزة لهذا الملف هي:
T_OPEN_TAG (انظر إلى الرمزين الأخيرين! يتم التعامل مع استيفاء السلسلة في المعجم، وبالتالي فهو أمر يتعلق بوقت الترجمة ولا علاقة له بوقت التشغيل.
للاكتمال، دعونا نلقي نظرة على أكواد التشغيل التي تم إنشاؤها بواسطة هذا (بعد التحسين، باستخدام 0x20000):
0000 ASSIGN CV0($juice) string("apple") 0001 T2 = FAST_CONCAT string("juice: ") CV0($juice) 0002 ECHO T2 0003 RETURN int(1)هذا كود تشغيل مختلف عما كان لدينا في
نصل إلى هذه النقطة: هل يجب أن أقوم بالربط أو الاستيفاء؟
دعونا نلقي نظرة على هذه الإصدارات الثلاثة المختلفة:
يخصص كود التشغيل الأول السلسلة "apple" للمتغير $juice:
0000 ASSIGN CV0($juice) string("apple")
الإصدار الأول (استيفاء السلسلة) يستخدم الحبل باعتباره بنية البيانات الأساسية، والذي تم تحسينه للقيام بنسخ أقل عدد ممكن من السلسلة.
0001 T2 = ROPE_INIT 4 string("juice: ") 0002 T2 = ROPE_ADD 1 T2 CV0($juice) 0003 T2 = ROPE_ADD 2 T2 string(" ") 0004 T1 = ROPE_END 3 T2 CV0($juice) 0005 ECHO T1
الإصدار الثاني هو الأكثر فعالية في الذاكرة لأنه لا يقوم بإنشاء تمثيل سلسلة وسيطة. بدلاً من ذلك، يقوم بإجراء مكالمات متعددة إلى ECHO وهو اتصال محظور من منظور الإدخال/الإخراج، لذا اعتمادًا على حالة الاستخدام الخاصة بك، قد يكون هذا جانبًا سلبيًا.
0006 ECHO string("juice: ") 0007 ECHO CV0($juice) 0008 ECHO string(" ") 0009 ECHO CV0($juice)
يستخدم الإصدار الثالث CONCAT/FAST_CONCAT لإنشاء تمثيل سلسلة وسيطة وبالتالي قد يستخدم ذاكرة أكبر من إصدار الحبل.
0010 T1 = CONCAT string("juice: ") CV0($juice) 0011 T2 = FAST_CONCAT T1 string(" ") 0012 T1 = CONCAT T2 CV0($juice) 0013 ECHO T1
إذن ... ما هو الشيء الصحيح الذي يجب فعله هنا ولماذا يتم استيفاء السلسلة؟
يستخدم استيفاء السلسلة إما FAST_CONCAT في حالة الصدى "juice: $juice"؛ أو رموز تشغيل ROPE_* المحسنة للغاية في حالة echo "juice: $juice $juice";، ولكن الأهم من ذلك أنها توصل القصد بوضوح ولم يكن أي من هذا بمثابة عنق الزجاجة في أي من تطبيقات PHP التي عملت معها حتى الآن، لذلك لا شيء من هذا يهم في الواقع.
استيفاء السلسلة هو أمر يتطلب وقت الترجمة. من المؤكد أنه بدون OPcache، سيتعين على المعجم التحقق من المتغيرات المستخدمة في السلاسل المزدوجة المقتبسة في كل طلب، حتى لو لم يكن هناك أي منها، مما يؤدي إلى تقليص دورات وحدة المعالجة المركزية، ولكن بصراحة: المشكلة ليست السلاسل المزدوجة المقتبسة، ولكن عدم استخدام OPcache!
ومع ذلك، هناك تحذير واحد: PHP ما يصل إلى 4 (وأعتقد أنه حتى بما في ذلك 5.0 وربما حتى 5.1، لا أعرف) قام باستيفاء السلسلة في وقت التشغيل، لذا باستخدام هذه الإصدارات ... حسنًا، أعتقد إذا لا يزال أي شخص يستخدم PHP 5، وينطبق نفس ما ورد أعلاه: المشكلة ليست في السلاسل المقتبسة المزدوجة، ولكن في استخدام إصدار PHP قديم.
التحديث إلى أحدث إصدار PHP، وتمكين OPcache والعيش في سعادة دائمة!
تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.
Copyright© 2022 湘ICP备2022001581号-3