Joy: Apa yang terjadi?
Kesedihan: Kami abstrak! Ada empat tahap. Ini yang pertama. Fragmentasi tidak objektif!
Bing Bong: Baiklah, jangan panik. Yang penting adalah kita semua tetap bersama. [Tiba-tiba lengan abstraknya jatuh]
Joy: Oh! [Kesedihan dan Sukacita mulai berantakan juga]
Kesedihan: Kami berada di tahap kedua. Kami sedang mendekonstruksi! [Saat Bing Bong hancur berkeping-keping]
Bing Bong: Saya tidak bisa merasakan kaki saya! [mengambil satu kaki] Oh, ini dia.
Β© Kartun Inside OutSemua orang suka menulis kode yang indah. Untuk abstraksi, lambdas, SOLID, KERING, DI, dll. dll. Pada artikel ini, saya ingin mengeksplorasi berapa biayanya dalam hal kinerja dan mengapa.
Untuk melakukan ini, kami mengambil tugas sederhana, bercerai dari kenyataan, dan kami secara bertahap akan membawa keindahan ke dalamnya, mengukur kinerja dan melihat di bawah tenda.
Penafian: Artikel ini sama sekali tidak dapat ditafsirkan sebagai panggilan untuk menulis kode yang buruk. Lebih baik jika Anda mendengarkan terlebih dahulu untuk mengatakan setelah membaca "Keren! Sekarang saya tahu bagaimana isinya. Tapi, tentu saja, saya tidak akan menggunakannya. " :)
Tantangan:
- Dan file teks.
- Kami memecahnya menjadi garis-garis.
- Potong spasi kiri dan kanan
- Buang semua baris kosong.
- Kami mengganti semua ruang non-tunggal dengan yang tunggal ("ABC" -> "ABC").
- Baris dengan lebih dari 10 kata, sesuai dengan kata, dibalik ke belakang (βAn Bn Cnβ -> βCn Bn Anβ).
- Kami menghitung berapa kali setiap baris muncul.
- Cetak semua garis yang muncul lebih dari N kali.
Sebagai file input, berdasarkan tradisi kami mengambil php-src / Zend / zend_vm_execute.h untuk ~ 70 ribu baris.
Sebagai runtime, ambil PHP 7.3.6.
Mari kita lihat opcode yang dikompilasi di sini
https://3v4l.org .
Pengukuran akan dilakukan sebagai berikut:
Pendekatan pertama, naif
Mari kita menulis kode imperatif sederhana:
$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; } }
Runtime ~ 0,148 dtk.
Semuanya sederhana dan tidak ada yang perlu dibicarakan.
Pendekatan kedua, prosedural
Kami memperbaiki kode kami dan mengambil tindakan elementer dalam fungsi.
Kami akan berusaha mematuhi prinsip tanggung jawab tunggal.
Alas kaki di bawah spoiler. 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 );
Runtime ~ 0,275s ... WTF!? Perbedaannya hampir 2 kali lipat!
Mari kita lihat apa fungsi PHP dari sudut pandang mesin virtual.
Kode:
$a = 1; $b = 2; $c = $a + $b;
Kompilasi ke:
line
Mari kita tambahkan tambahan ke dalam fungsi:
function sum($a, $b){ return $a + $b; } $a = 1; $b = 1; $c = sum($a, $b);
Kode tersebut dikompilasi menjadi dua set opcode: satu untuk root namespace, dan yang kedua untuk fungsi.
Rooting:
line
Fungsi:
line
Yaitu bahkan jika Anda hanya menghitung dengan opcodes, maka setiap panggilan fungsi menambah 3 + 2N opcodes, di mana N adalah jumlah argumen yang dilewati.
Dan jika Anda menggali sedikit lebih dalam, maka di sini kami juga mengganti konteks eksekusi.
Perkiraan kasar kode refactored kami memberikan angka demikian (ingat sekitar 70.000 iterasi).
Jumlah opcode yang dieksekusi "tambahan": ~ 17.000.000.
Jumlah sakelar konteks: ~ 280.000.
Pendekatan ketiga, klasik
Terutama tanpa berfilsafat, kami membungkus semua fungsi ini dengan kelas.
Sprei di bawah spoiler 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);
Waktu pimpin: 0.297. Itu menjadi lebih buruk. Tidak banyak, tetapi nyata. Apakah pembuatan objek (10 kali dalam kasus kami) begitu mahal? Nuuu ... Bukan hanya itu.
Mari kita lihat bagaimana mesin virtual bekerja dengan kelas.
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();
Akan ada tiga set opcode, yang logis: root dan dua metode.
Rooting:
line
Konstruktor:
line
Metode
penjumlahan :
line
Kata kunci
baru sebenarnya dikonversi ke panggilan fungsi (baris 3-6).
Ini menciptakan turunan dari kelas dan memanggil konstruktor dengan parameter yang diteruskan di atasnya.
Dalam kode metode, kami akan tertarik bekerja dengan bidang kelas. Harap dicatat bahwa jika Anda menetapkan satu
kode ASSIGN sederhana dengan variabel biasa untuk tugas, maka untuk bidang kelas semuanya agak berbeda.
Penugasan - 2 opcodes
7 2 ASSIGN_OBJ 'a' 3 OP_DATA !0
Baca - 1 opcode
1 FETCH_OBJ_R ~1 'b'
Di sini Anda harus tahu bahwa
ASSIGN_OBJ dan
FETCH_OBJ_R jauh lebih rumit dan, karenanya, lebih banyak sumber daya daripada
ASSIGN yang sederhana, yang, secara kasar, hanya menyalin
zval dari satu memori ke memori lainnya.
Jelas bahwa perbandingan semacam itu sangat jauh dari benar, tetapi masih memberi beberapa gagasan. Sedikit lebih jauh saya akan melakukan pengukuran.
Sekarang mari kita lihat betapa mahalnya untuk membuat instance objek. Mari kita ukur satu juta iterasi:
class ValueObject{ private $a; function __construct($a) { $this->a = $a; } } $start = microtime(true); for($i = 0; $i < 1000000; $i++){
Penugasan variabel: 0,092.
Obyek Instance: 0.889.
Sesuatu seperti ini. Tidak sepenuhnya gratis, apalagi jika berkali-kali.
Nah, agar tidak bangun dua kali, mari kita mengukur perbedaan antara bekerja dengan properti dan variabel lokal. Untuk melakukan ini, ubah kode kami dengan cara ini:
class ValueObject{ private $b; function try($a) {
Pertukaran melalui penugasan: 0.830.
Pertukaran melalui properti: 0,862.
Hanya sedikit, tetapi lebih lama. Perbedaan urutan yang sama yang Anda dapatkan setelah membungkus fungsi dalam suatu kelas.
Kesimpulan dangkal
- Lain kali Anda ingin membuat sejuta objek, pikirkan apakah Anda benar-benar membutuhkannya. Mungkin hanya sebuah array, ya?
- Menulis kode spaghetti demi menghemat satu milidetik - yah, itu. Knalpotnya murah, dan rekannya bisa mengalahkan mereka nanti.
- Tapi demi menghemat 500 milidetik, mungkin terkadang masuk akal. Hal utama adalah jangan melangkah terlalu jauh dan ingat bahwa 500 milidetik ini kemungkinan besar hanya akan diselamatkan oleh sebagian kecil kode yang sangat panas, dan tidak mengubah seluruh proyek menjadi hampa kesedihan.
PS Tentang lambdas waktu berikutnya. Sangat menarik di sana. :)