الفرح: ما الذي يحدث؟
الحزن: نحن مجردة! هناك أربع مراحل. هذا هو الأول. تفتيت غير موضوعي!
بينغ بونغ: حسنا ، لا داعي للذعر. المهم أن نبقى جميعًا معًا. [فجأة تسقط ذراعه المجردة]
الفرح: أوه! [يبدأ الحزن والفرح في الانهيار أيضًا]
الحزن: نحن في المرحلة الثانية. نحن نتفكك! [كما ينخفض بينغ بونغ]
بنج بونغ: لا أستطيع أن أشعر بساقي! [يختار ساق واحدة] أوه ، هناك.
© Cartoon Inside Outالجميع يحب أن يكتب رمز جميل. إلى التجريد ، lambdas ، SOLID ، DRY ، DI ، إلخ. إلخ في هذه المقالة ، أريد أن أستكشف كم يكلف من حيث الأداء ولماذا.
للقيام بذلك ، قم بمهمة بسيطة منفصلة عن الواقع وستجلب جمالها تدريجياً ، وتقيس الأداء وتبحث تحت الغطاء.
إخلاء المسئولية: لا ينبغي بأي حال من الأحوال تفسير هذه المقالة على أنها دعوة لكتابة كود غير صحيح. من الأفضل أن تضبط مسبقًا لتقول بعد قراءة "رائع! الآن أنا أعرف كيف هو في الداخل. لكن ، بالطبع ، لن أستخدمها ". :)
الهدف:
- دان ملف نصي.
- نحن تقسيمها إلى خطوط.
- تقليم المساحات اليسار واليمين
- تجاهل جميع الخطوط الفارغة.
- نستبدل جميع المساحات غير المفردة بمسافات مفردة ("ABC" -> "ABC").
- الأسطر التي تحتوي على أكثر من 10 كلمات ، وفقًا للكلمات ، يتم قلبها للخلف ("An Bn Cn" -> "Cn Bn An").
- نحن نحسب عدد المرات التي يحدث فيها كل صف.
- طباعة جميع الخطوط التي تحدث أكثر من N مرات.
كملف إدخال ، من خلال التقليد نأخذ php-src / Zend / zend_vm_execute.h مقابل حوالي 70 ألف سطر.
كوقت تشغيل ، خذ PHP 7.3.6.
دعونا نلقي نظرة على رموز التشغيل المترجمة هنا
https://3v4l.org .
سيتم إجراء القياسات على النحو التالي:
النهج الأول ، ساذجة
لنكتب رمزًا بسيطًا ضروريًا:
$array = explode("\n", file_get_contents('/Users/rjhdby/CLionProjects/php-src/Zend/zend_vm_execute.h')); $cache = []; foreach ($array as $row) { if (empty($row)) continue; $words = preg_split("/\s+/", trim($row)); if (count($words) > 10) { $words = array_reverse($words); } $row = implode(" ", $words); if (isset($cache[$row])) { $cache[$row]++; } else { $cache[$row] = 1; } } foreach ($cache as $key => $value) { if ($value > 1000) { echo "$key : $value" . PHP_EOL; } }
وقت التشغيل ~ 0.148 ثانية.
كل شيء بسيط وليس هناك ما يمكن الحديث عنه.
النهج الثاني ، الإجرائي
نحن refactor كودنا ونخرج الإجراءات الأولية في الوظيفة.
سنحاول الالتزام بمبدأ المسؤولية الفردية.
Footcloth تحت المفسد. function getContentFromFile(string $fileName): array { return explode("\n", file_get_contents($fileName)); } function reverseWordsIfNeeded(array &$input) { if (count($input) > 10) { $input = array_reverse($input); } } function prepareString(string $input): string { $words = preg_split("/\s+/", trim($input)); reverseWordsIfNeeded($words); return implode(" ", $words); } function printIfSuitable(array $input, int $threshold) { foreach ($input as $key => $value) { if ($value > $threshold) { echo "$key : $value" . PHP_EOL; } } } function addToCache(array &$cache, string $line) { if (isset($cache[$line])) { $cache[$line]++; } else { $cache[$line] = 1; } } function processContent(array $input): array { $cache = []; foreach ($input as $row) { if (empty($row)) continue; addToCache($cache, prepareString($row)); } return $cache; } printIfSuitable( processContent( getContentFromFile('/Users/rjhdby/CLionProjects/php-src/Zend/zend_vm_execute.h') ), 1000 );
وقت التشغيل ~ 0.275 ثانية ... وتف!؟ الفرق هو تقريبا 2 مرات!
دعونا نرى ما هي وظيفة PHP من وجهة نظر الجهاز الظاهري.
كود:
$a = 1; $b = 2; $c = $a + $b;
يجمع إلى:
line
لنضع الإضافة في وظيفة:
function sum($a, $b){ return $a + $b; } $a = 1; $b = 1; $c = sum($a, $b);
يتم تجميع هذه الشفرة في مجموعتين من الشفرات: واحدة لمساحة الجذر ، والثانية للوظيفة.
الجذر:
line
وظيفة:
line
أي حتى إذا كنت تقوم فقط بالعد بواسطة رموز opcodes ، فإن كل استدعاء دالة يضيف 3 + 2N من رموز الشفرة ، حيث N هو عدد الوسائط التي تم تمريرها.
وإذا قمت بحفر أعمق قليلاً ، فإننا هنا نغير سياق التنفيذ.
يعطي التقدير التقريبي للشفرة المعاد تشكيلها مثل هذه الأرقام (تذكر حوالي 70،000 تكرار).
عدد شفرة التشغيل "الإضافية" المنفذة: حوالي 17،000،000.
عدد من مفاتيح السياق: ~ 280،000.
النهج الثالث ، الكلاسيكية
خاصة دون الفلسفه ، ونحن التفاف كل هذه الوظائف مع الفصل.
ورقة السرير تحت المفسد class ProcessFile { private $content; private $cache = []; function __construct(string $fileName) { $this->content = explode("\n", file_get_contents($fileName)); } private function reverseWordsIfNeeded(array &$input) { if (count($input) > 10) { $input = array_reverse($input); } } private function prepareString(string $input): string { $words = preg_split("/\s+/", trim($input)); $this->reverseWordsIfNeeded($words); return implode(" ", $words); } function printIfSuitable(int $threshold) { foreach ($this->cache as $key => $value) { if ($value > $threshold) { echo "$key : $value" . PHP_EOL; } } } private function addToCache(string $line) { if (isset($this->cache[$line])) { $this->cache[$line]++; } else { $this->cache[$line] = 1; } } function processContent() { foreach ($this->content as $row) { if (empty($row)) continue; $this->addToCache( $this->prepareString($row)); } } } $processFile = new ProcessFile('/Users/rjhdby/CLionProjects/php-src/Zend/zend_vm_execute.h'); $processFile->processContent(); $processFile->printIfSuitable(1000);
يؤدي الوقت: 0.297. لقد ساءت. ليس كثيرا ، ولكن ملحوظ. هل إنشاء كائن (10 مرات في حالتنا) مكلف للغاية؟ Nuuu ... ليس هذا فقط.
دعونا نرى كيف يعمل الجهاز الظاهري مع الفصل.
class Adder{ private $a; private $b; function __construct($a, $b) { $this->a = $a; $this->b = $b; } function sum(){ return $this->a + $this->b; } } $a = 1; $b = 1; $adder = new Adder($a, $b); $c = $adder->sum();
سيكون هناك ثلاث مجموعات من رموز التشغيل ، وهو أمر منطقي: الجذر وطريقتان.
الجذر:
line
المصمم:
line
طريقة
المجموع :
line
يتم بالفعل تحويل الكلمة الأساسية
الجديدة إلى استدعاء دالة (الأسطر 3-6).
يقوم بإنشاء مثيل للفئة ويستدعي المنشئ باستخدام المعلمات التي تم تمريره عليه.
في كود الطرق ، سنهتم بالعمل مع حقول الفصل. يرجى ملاحظة أنه إذا قمت بتعيين شفرة تشغيل
ASSIGN واحدة بسيطة مع متغيرات عادية للواجب ، فسيكون كل شيء مختلفًا بالنسبة لحقول الفصل.
احالة - 2 شفرة التشغيل
7 2 ASSIGN_OBJ 'a' 3 OP_DATA !0
قراءة - 1 شفرة التشغيل
1 FETCH_OBJ_R ~1 'b'
هنا يجب أن تعرف أن
ASSIGN_OBJ و
FETCH_OBJ_R أكثر تعقيدًا وبالتالي ،
فهي أكثر
كثافة من حيث الموارد من
ASSIGN البسيطة ، والتي ، تقريبًا ، تقوم بنسخ
zval من قطعة ذاكرة إلى أخرى.
من الواضح أن هذه المقارنة بعيدة كل البعد عن أن تكون صحيحة ، لكنها ما زالت تعطي فكرة. أبعد قليلا سوف أقوم بإجراء القياسات.
الآن دعنا نرى كم هو مكلف إنشاء كائن. دعونا نقيس على مليون تكرار:
class ValueObject{ private $a; function __construct($a) { $this->a = $a; } } $start = microtime(true); for($i = 0; $i < 1000000; $i++){
الاحالة المتغيرة: 0.092.
كائن مثيل: 0.889.
شيء مثل هذا. ليست حرة تماما ، وخاصة إذا كان عدة مرات.
حسنًا ، حتى لا ننهض مرتين ، دعونا نقيس الفرق بين العمل مع الخصائص والمتغيرات المحلية. للقيام بذلك ، قم بتغيير الكود الخاص بنا بهذه الطريقة:
class ValueObject{ private $b; function try($a) {
التبادل من خلال الاحالة: 0.830.
الصرف من خلال الممتلكات: 0.862.
فقط قليلا ، ولكن لفترة أطول. بنفس ترتيب الفرق الذي حصلت عليه بعد التفاف الوظائف في الفصل.
استنتاجات عادية
- في المرة القادمة التي تريد فيها إنشاء مثيل لمليون كائن ، فكر فيما إذا كنت بحاجة إليه حقًا. ربما مجرد مجموعة ، هاه؟
- كتابة رمز السباغيتي من أجل توفير ميلي ثانية واحدة - حسنا ، هذا. العادم رخيص ، ويمكن للزملاء التغلب عليهم لاحقًا.
- ولكن من أجل توفير 500 مللي ثانية ، ربما يكون الأمر منطقيًا في بعض الأحيان. الشيء الرئيسي هو عدم الذهاب إلى أبعد من ذلك وتذكر أنه من المرجح أن يتم حفظ 500 ملي ثانية فقط بواسطة قسم صغير من الكود الساخن للغاية ، وليس لتحويل المشروع بأكمله إلى فراغ من الحزن.
PS حول lambdas في المرة القادمة. انها مثيرة للاهتمام هناك. :)