Pada Desember 2015 , PHP 7.0 dirilis. Perusahaan yang beralih ke "tujuh" mencatat bahwa produktivitas telah meningkat, dan beban di server telah menurun. Yang pertama pindah ke tujuh adalah Vebia dan Etsy, dan kami memiliki Badoo, Avito dan OLX. Untuk Badoo, beralih ke tujuh menghemat $ 1 juta dalam penghematan server. Berkat PHP 7 di OLX, beban server rata-rata berkurang 3 kali lipat, peningkatan efisiensi dan penghematan sumber daya.
Dmitry Stogov dari Zend Technologies
berbicara di
HighLoad ++ , yang meningkatkan produktivitas. Dalam decoding: tentang struktur internal PHP, tentang ide-ide di jantung versi 7.0, tentang perubahan struktur data dasar dan algoritma yang menentukan keberhasilan.
Penafian: Per Maret 2019, 80% situs berjalan dengan PHP, dan 70% di antaranya berjalan di PHP 5, meskipun versi ini belum didukung sejak 1 Januari 2019 . Laporan Dmitry tahun 2016 tentang prinsip-prinsip yang menyebabkan lompatan ganda dalam produktivitas antara PHP 5 dan 7 juga relevan pada bulan Maret 2019. Tentu saja untuk separuh situs.Tentang pembicara: Dmitry Stogov mulai memprogram kembali di tahun 80-an: "Electronics B3-34", Basic, assembler. Pada tahun 2002 Dmitry berkenalan dengan PHP dan segera mulai bekerja untuk memperbaikinya: ia mengembangkan Turck MMCache untuk PHP, memimpin proyek PHPNG dan memainkan peran penting dalam mengerjakan JIT untuk PHP. 14 tahun terakhir dari Principal Engineer di Zend Technologies.
Zend Technologies sedang mengembangkan solusi PHP dan komersial berdasarkan itu. Pada tahun 1999, ia didirikan oleh programmer Israel Andy Gutmans dan Zeev Suraski, yang dua tahun lalu menciptakan PHP 3. Orang-orang ini berada di garis depan pengembangan PHP dan sangat menentukan tampilan bahasa saat ini dan keberhasilan teknologi.
Zend Technologies sedang mengembangkan inti PHP dan aplikasi untuknya, dan selama bekerja saya harus menulis ekstensi, masuk ke semua subsistem dan bahkan terlibat dalam proyek komersial, kadang-kadang sama sekali tidak terhubung dengan PHP. Tapi topik yang paling menarik bagi saya adalah
kinerja .
Saya mulai mencari cara untuk mempercepat PHP bahkan sebelum bergabung dengan Zend, mengerjakan proyek saya sendiri yang bersaing dengan perusahaan. Selama mengerjakan proyek, saya benar-benar memahami bahasa dan menyadari bahwa bekerja dengan proyek utama, Anda hanya dapat mempengaruhi aspek-aspek tertentu dari eksekusi skrip, dan semua yang paling menarik dan efektif hanya dapat dibuat
di kernel . Pemahaman dan kebetulan ini membawa saya ke Zend.
Penyimpangan kecil ke dalam sejarah PHP
PHP bukan hanya dan
bukan hanya bahasa pemrograman . PHP adalah singkatan dari Personal Home Page - alat untuk membuat halaman web pribadi dan situs web dinamis. Bahasa hanyalah salah satu bagian utamanya. PHP adalah pustaka fungsi yang sangat besar, banyak ekstensi untuk bekerja dengan pustaka pihak ketiga lainnya, misalnya, untuk akses ke database atau parser XML, serta satu set modul untuk berkomunikasi dengan berbagai server web.
Programmer Denmark
Rasmus Lerdorf memperkenalkan PHP
pada Juni 1995 . Pada saat itu, itu hanya
kumpulan skrip CGI yang ditulis dalam Perl . Pada bulan April 96, Rasmus memperkenalkan PHP / FI, dan pada bulan Juni PHP / FI 2.0 dirilis. Selanjutnya, versi ini secara substansial dikerjakan ulang oleh Andy Gutmans dan Zeev Surasky, dan pada 98 dirilis PHP 3.0. Pada tahun 2000, bahasa tersebut menjadi sesuatu yang biasa kita lihat hari ini baik dari segi bahasa dan arsitektur internal - PHP 4, berdasarkan Zend Engine.
Sejak versi 4, PHP telah berevolusi. Titik baliknya adalah rilis PHP 5 pada tahun 2004, ketika
model objek sepenuhnya diperbarui . Dialah yang membuka era kerangka PHP dan mengangkat pertanyaan kinerja ke tingkat yang baru. Mengantisipasi ini, segera setelah rilis 5.0, kami di Zend berpikir tentang mempercepat PHP dan mulai bekerja untuk meningkatkan produktivitas.
Versi 7.1, yang dirilis pada November 2016 pada tes sintetis,
25 kali lebih cepat daripada versi 2002 . Menurut grafik perubahan kinerja di cabang yang berbeda, terobosan utama terlihat di 5.1 dan 7.0.

Dalam versi 5.1, kami baru saja mulai mengerjakan kinerja, dan semua yang kami ambil - ternyata, tetapi setelah 5.3 kami menabrak tembok, semua upaya untuk meningkatkan penerjemah tidak membuahkan hasil.
Namun demikian, kami menemukan tempat untuk menggali, dan mendapatkan lebih dari yang diharapkan - akselerasi 2,5 kali lipat dibandingkan dengan versi sebelumnya pada tes. Tetapi hal yang paling menarik adalah kita mendapatkan akselerasi 2,5 kali lipat yang sama pada aplikasi nyata yang tidak berubah. Ini adalah fenomena, karena kami mengembangkan faktor 2 sebelumnya sepanjang kehidupan lima dalam 10 tahun.

Lompatan besar pada 5.1 pada tes sintetis tidak terlihat pada aplikasi nyata. Alasannya adalah bahwa dengan penggunaan yang berbeda, kinerja PHP bersandar pada rem yang terkait dengan berbagai subsistem.
Sejarah PHP 7 dimulai dengan stagnasi tiga tahun yang dimulai pada 2012 dan berakhir pada 2015 dengan dirilisnya versi ketujuh. Kemudian kami menyadari bahwa kami tidak dapat lagi meningkatkan produktivitas dengan sedikit peningkatan juru bahasa kami dan beralih ke sisi JIT.
Berkeliaran di sekitar JIT
Hampir dua tahun kami habiskan untuk prototipe JIT untuk PHP-5.5. Pada awalnya, kami menghasilkan kode yang sangat sederhana - urutan panggilan untuk penangan standar, sesuatu seperti kode Fort yang dijahit. Kemudian mereka menulis
Runtime Assembler mereka sendiri, sebaris kode terpisah untuk pemecahan masalah, tetapi menyadari bahwa
optimasi tingkat rendah seperti
itu tidak memberikan efek
praktis bahkan pada tes.
Kemudian kami berpikir tentang menurunkan tipe variabel menggunakan metode analisis statis. Setelah menyadari kesimpulannya, kami segera menerima
akselerasi 2 kali lipat dalam tes. Karena terdorong, mereka mencoba menulis pengalokasi daftar global, tetapi gagal. Kami menggunakan representasi level yang cukup tinggi, dan hampir tidak mungkin menggunakannya untuk alokasi register.
Untuk menghindari masalah tingkat rendah, kami memutuskan untuk mencoba LLVM, dan setahun kemudian kami mendapat
akselerasi 10x untuk bench.php , tetapi tidak ada pada aplikasi nyata. Selain itu, mengkompilasi aplikasi nyata sekarang membutuhkan beberapa menit, misalnya,
permintaan pertama
ke Wordpress membutuhkan waktu 2 menit dan tidak memberikan akselerasi. Tentu saja, ini sama sekali tidak cocok untuk latihan nyata.
Kode yang baik dimungkinkan dengan prediksi tipe yang tepat, yang bekerja buruk di aplikasi nyata, dan menggunakan struktur data PHP membuat kode yang dihasilkan tidak efisien.
Apa yang melambat?
Kami memikirkan kembali alasan kegagalan tersebut dan memutuskan sekali lagi untuk melihat mengapa PHP lambat. Gambar menunjukkan hasil profiling beberapa permintaan ke halaman beranda Wordpress.

Kurang dari 30% dihabiskan untuk menafsirkan bytecode, 20% adalah overhead dari manajer memori, 13% bekerja dengan tabel hash, dan 5% bekerja dengan ekspresi reguler.
Bekerja di JIT, kami hanya menyingkirkan 30% pertama, dan semua yang lainnya terbebani. Hampir di mana-mana, kami dipaksa untuk menggunakan struktur data PHP standar, yang mencakup overhead: alokasi memori, penghitungan referensi, dll. Pemahaman ini mengarah pada kesimpulan bahwa perlu untuk mengganti struktur data utama dalam PHP. Dengan
penggantian yayasan ini , proyek
PHPNG dimulai.Phpng Generasi baru
Proyek ini dikembangkan setelah upaya yang gagal untuk membuat JIT untuk PHP. Tujuan utamanya adalah
untuk mencapai tingkat produktivitas baru dan meletakkan dasar bagi peningkatan di masa depan .
Kami berjanji pada diri sendiri untuk beberapa waktu untuk tidak lagi menggunakan tes sintetis untuk mengukur kinerja - ini biasanya adalah program komputasi kecil yang menggunakan jumlah data terbatas yang sepenuhnya cocok dengan cache prosesor. Aplikasi nyata, sebaliknya, tunduk pada rem yang terkait dengan memori subsistem, dan satu pembacaan dari memori dapat menelan biaya 100 instruksi komputasi.
Proyek PHPNG adalah refactoring struktur data PHP utama untuk mengoptimalkan akses memori . Tidak ada inovasi, 100% PHP 5 kompatibel.
Cara mengubah struktur ini jelas. Tetapi volume perubahan dependen sangat besar, karena
inti dari PHP itu sendiri
adalah 150.000 baris , dan hampir setiap sepertiga perlu diubah. Tambahkan seratus ekstensi lagi yang termasuk dalam distribusi dasar, selusin modul untuk server web yang berbeda, dan Anda akan menyadari kemegahan proyek.
Kami bahkan tidak yakin bahwa kami akan menyelesaikan proyek. Karena itu, mereka meluncurkan proyek secara rahasia dan membukanya hanya ketika hasil optimis pertama muncul. Butuh dua minggu untuk
mengkompilasi kernel . Dua minggu kemudian, bench.php diterima. Kami menghabiskan satu setengah bulan untuk memastikan pekerjaan Wordpress. Sebulan kemudian, kami membuka proyek - itu Mei 2014. Saat itu, kami memiliki
akselerasi 30% di Wordpress . Itu sudah tampak seperti acara akbar.
PHPNG segera membangkitkan gelombang ketertarikan, dan pada Agustus 2014 ini
diadopsi sebagai dasar untuk masa depan PHP 7 . Itu sudah merupakan proyek lain, dengan serangkaian tujuan yang berbeda, di mana produktivitas hanyalah salah satunya.
PHP 7.0
Versi nomor 7 sendiri diragukan. Versi sebelumnya adalah yang kelima. Dan yang keenam dikembangkan beberapa tahun yang lalu dan sepenuhnya dikhususkan untuk dukungan
Unicode asli, tetapi keputusan yang gagal dibuat pada tahap awal pengembangan menyebabkan kompleksitas yang berlebihan dari kode kernel dan setiap ekstensi. Pada akhirnya, diputuskan untuk membekukan proyek.
Pada saat ini, banyak materi yang dikhususkan untuk PHP 6 telah terakumulasi: pidato di konferensi, buku yang diterbitkan. Agar tidak membingungkan siapa pun, kami menyebut proyek PHP 7, melewatkan PHP 6. Versi ini jauh lebih beruntung - PHP 7 dirilis pada Desember 2015, hampir sesuai rencana.
Selain kinerja, beberapa inovasi yang lama dicari muncul di PHP 7:
- Kemampuan untuk menentukan jenis parameter skalar dan mengembalikan nilai.
- Pengecualian alih-alih kesalahan - sekarang kita dapat menangkap dan memprosesnya.
Zero-cost assert()
, kelas anonim, inkonsistensi pembersihan, operator dan fungsi baru (<=>, ??) muncul.
Inovasi itu baik, tetapi kembali ke perubahan internal. Mari kita bicara tentang jalur yang diikuti PHP 7 dan di mana jalur ini dapat membawa kita.
zval
Ini adalah struktur data PHP dasar. Ini digunakan untuk
mewakili nilai apa pun dalam PHP . Karena bahasa kita diketik secara dinamis dan jenis variabel dapat berubah selama eksekusi program, kita perlu menyimpan bidang tipe (tipe zend_uchar), yang dapat mengambil nilai IS_NULL, IS_BOOL, IS_LONG, IS_DOUBLE, IS_ARRAY, IS_OBJECT, dll., Dan sebenarnya nilai yang diwakili oleh gabungan (nilai), tempat bilangan bulat, bilangan real, string, array, atau objek dapat disimpan.
zval di PHP 5
Memori untuk setiap struktur tersebut dialokasikan secara terpisah di Heap. Selain jenis dan nilai, counter referensi ke struktur juga disimpan di dalamnya. Jadi strukturnya membutuhkan 24 byte, tidak termasuk overhead dari manajer memori dan pointer ke sana.
Gambar di kanan atas menunjukkan struktur data yang dibuat dalam memori PHP 5 untuk skrip sederhana.

Pada stack, memori dialokasikan untuk 4 variabel yang diwakili oleh pointer. Nilai-nilai itu sendiri (zval) ada di heap. Dalam kasus kami, ini hanya dua zval, yang masing-masing direferensikan oleh dua variabel, dan karenanya penghitung referensi mereka diatur ke 2.
Untuk mengakses jenis atau nilai skalar, Anda memerlukan setidaknya dua bacaan: pertama baca nilai pointer, dan kemudian nilai struktur. Jika Anda perlu membaca bukan nilai skalar, tetapi, misalnya, bagian dari string atau array, maka Anda akan memerlukan setidaknya satu bacaan lagi.
zval di PHP 7
Di mana kami menggunakan pointer sebelumnya, di tujuh kami mulai menanamkan zval. Kami telah beralih dari penghitungan referensi untuk jenis skalar. Jenis dan nilai bidang tetap tanpa perubahan signifikan, tetapi beberapa bendera lagi dan tempat yang dipesan ditambahkan, yang akan saya bicarakan nanti.

Di sebelah kiri terlihat seperti apa di PHP 5, dan di sebelah kanan, di PHP 7.

Sekarang zval sendiri ada di tumpukan. Untuk membaca jenis dan nilai skalar, cukup satu instruksi mesin saja. Semua nilai dikelompokkan dalam satu area memori, yang berarti bahwa ketika bekerja dengan variabel lokal, kita praktis tidak akan mengalami kerugian karena kehilangan cache prosesor. Tetapi kekuatan sebenarnya dari kinerja baru termasuk ketika menyalin diperlukan.
Salin Rekam
Di baris teratas skrip, tugas lain ditambahkan.

Dalam PHP5, kami mengalokasikan memori dari heap untuk zval baru, menginisialisasi intnya (2), mengubah nilai pointer ke variabel b, dan menurunkan penghitung referensi dari nilai yang disebutkan b sebelumnya.
Di PHP 7, kami cukup
menginisialisasi variabel b langsung di tempat dengan beberapa instruksi , sedangkan di PHP 5 diperlukan ratusan instruksi. Jadi zval terlihat sekarang di memori.

Ini adalah dua kata 64-bit. Kata pertama
artinya: integer, real atau pointer. Pada kata kedua,
tipe (dikatakan bagaimana mengartikan makna), bendera, dan tempat yang dipesan yang masih akan ditambahkan saat menyelaraskan. Tapi itu tidak hilang, tetapi digunakan oleh berbagai subsistem untuk menyimpan nilai yang terkait secara tidak langsung.
Bendera adalah seperangkat bit di mana setiap bit menunjukkan apakah zval mendukung protokol. Misalnya, jika
IS_TYPE_REFCOUNTED
, maka ketika bekerja dengan zval ini, mesin harus menjaga nilai penghitung referensi. Ketika menetapkan, meningkat, ketika meninggalkan ruang lingkup, kurangi, jika penghitung referensi mencapai nol, hancurkan struktur dependen.
Dari jenis tersebut, dibandingkan dengan PHP 5, beberapa yang baru muncul.
IS_UNDEF
- penanda variabel tidak diinisialisasi.IS_BOOL
tunggal digantikan oleh IS_FALSE
dan IS_FALSE
terpisah.- Menambahkan tipe terpisah untuk tautan dan beberapa jenis sihir lainnya.
Jenis dari
IS_UNDEF
ke
IS_DOUBLE
adalah skalar, dan tidak memerlukan memori tambahan. Untuk menyalinnya, cukup menyalin mesin 64-bit kata pertama dengan nilai dan setengah detik dengan jenis dan bendera.
Dihitung ulang
Dengan tipe lain lebih sulit. Mereka semua diwakili oleh struktur bawahan, dan zval hanya menyimpan referensi ke struktur ini. Untuk setiap jenis, struktur ini berbeda, tetapi dalam hal OOP, mereka semua memiliki leluhur abstrak yang sama atau struktur zend_refcounted. Ini menentukan format kata
64-bit pertama, tempat penghitungan referensi dan informasi lain untuk pengumpul sampah disimpan.

Kata ini dapat dianggap hanya sebagai informasi untuk pengumpul sampah, dan struktur untuk tipe tertentu menambahkan bidang mereka setelah kata pertama ini.
Garis
Dalam tujuh untuk string, kami menyimpan nilai yang dihitung dari fungsi hash, panjangnya dan karakter itu sendiri. Ukuran struktur seperti itu adalah variabel dan tergantung pada panjang tali. Fungsi hash dihitung untuk string sekali, bila perlu. Di PHP 5, itu dihitung ulang di setiap kebutuhan.

Sekarang string telah menjadi referensi yang dapat dihitung, dan jika dalam PHP 5 kita menyalin karakter itu sendiri, sekarang cukup untuk meningkatkan jumlah referensi untuk struktur ini.
Seperti dalam PHP 5, kami masih memiliki konsep
string yang tidak dapat diubah atau diinternir . Mereka biasanya ada dalam satu contoh, hidup sampai akhir permintaan, dan dapat berperilaku seperti nilai skalar. Kita tidak perlu mengurus konter referensi kepada mereka, dan untuk menyalinnya cukup menyalin hanya zval itu sendiri dengan bantuan empat instruksi mesin.
Array
Array diwakili oleh tabel hash bawaan dan tidak jauh berbeda dari PHP 5. Tabel hash itu sendiri telah berubah, tetapi lebih pada itu secara terpisah.

Array sekarang merupakan
struktur adaptif yang sedikit mengubah struktur internal dan perilakunya tergantung pada data yang disimpan. Jika kita menyimpan hanya elemen dengan kunci numerik dekat, maka kita mendapatkan akses ke elemen secara langsung dengan indeks dengan kecepatan yang sebanding dengan kecepatan array di C. Tetapi jika Anda menambahkan elemen dengan kunci string ke array yang sama, itu berubah menjadi hash nyata dengan resolusi tabrakan.
Ini adalah bagaimana tabel hash terlihat seperti di PHP 5.

Ini adalah implementasi tabel hash klasik dengan resolusi tabrakan menggunakan daftar linear (ditunjukkan di sudut kanan atas). Setiap item diwakili oleh ember. Semua Bucket ditautkan oleh daftar yang ditautkan ganda untuk menyelesaikan tabrakan, dan ditautkan oleh daftar lain yang ditautkan dua kali untuk berurutan. Nilai untuk setiap zval dialokasikan secara terpisah - di Bucket kami hanya menyimpan tautan ke sana. Juga, kunci string dapat dialokasikan secara terpisah.
Jadi, untuk setiap tabel hash, Anda perlu mengalokasikan banyak blok kecil memori, dan untuk menemukan sesuatu nanti, Anda harus menjalankan sepanjang pointer. Setiap transisi tersebut dapat menyebabkan cahce miss dan penundaan ~ 10-100 siklus prosesor.
Inilah yang terjadi di PHP 7.

Struktur logis tetap tidak berubah, hanya yang fisik yang berubah. Sekarang, di bawah tabel hash, memori dialokasikan dengan satu operasi.
Dalam gambar, di bagian bawah penunjuk dasar, ada elemen, dan di bagian atas adalah array hash yang ditangani oleh fungsi hash. Untuk array datar atau penuh, ketika kami menyimpan hanya elemen dengan indeks numerik, bagian atas tidak dialokasikan sama sekali, dan kami menangani Bucket langsung dengan nomor.
Untuk memotong elemen, kami mengurutkannya secara berurutan dari atas ke bawah atau dari bawah ke atas, yang dilakukan oleh prosesor modern dengan sempurna. Nilai-nilai dibangun ke dalam Bucket, tetapi ruang yang disediakan di dalamnya hanya digunakan untuk menyelesaikan tabrakan. Ini menyimpan indeks Bucket lain dengan nilai fungsi hash yang sama atau akhir penanda daftar.
Memori untuk nilai string kunci dialokasikan secara terpisah, tetapi masih zend_string yang sama. Saat menempelkan ke dalam array, cukup menambah counter referensi string, meskipun sebelumnya kita harus menyalin karakter secara langsung, dan saat mencari, kita sekarang tidak dapat membandingkan karakter, tetapi pointer ke string sendiri.
Array yang Tidak Berubah
Sebelumnya, kami memiliki string yang tidak dapat diubah, tetapi sekarang array yang tidak berubah juga telah muncul. Seperti string, mereka tidak menggunakan jumlah referensi dan tidak dihancurkan sampai akhir permintaan. Ini adalah skrip sederhana yang membuat array dari sejuta elemen, dan setiap elemen adalah array yang sama dengan satu elemen "halo".

Dalam PHP 5, pada setiap iterasi loop, array kosong baru dibuat, "halo" ditulis untuk itu, dan semua ini ditambahkan ke array yang dihasilkan. Dalam PHP 7, pada tahap kompilasi, kami
membuat hanya satu array yang
tidak berubah yang berperilaku seperti skalar, dan menambahkannya ke yang dihasilkan. Dalam contoh yang disajikan, ini memungkinkan kita untuk mencapai pengurangan konsumsi memori lebih dari 10 kali lipat dan akselerasi hampir 10 kali lipat.
Array konstan jutaan elemen dalam aplikasi nyata, tentu saja, tidak sering ditemukan, tetapi yang kecil cukup umum. Pada masing-masing dari mereka Anda akan mendapatkan yang kecil, tetapi menang.
Benda-benda
Tautan ke semua objek di PHP 5 terletak di repositori terpisah, dan di zval hanya ada handle - ID objek unik.

Untuk sampai ke objek, kami membuat setidaknya 3 pembacaan. Selain itu, memori untuk nilai setiap properti objek dialokasikan secara terpisah, dan kami membutuhkan setidaknya 2 bacaan lagi untuk membacanya.
Di PHP 7, kami dapat beralih ke pengalamatan langsung.

Alamat
zend_object
dapat diakses dengan instruksi mesin tunggal. Dan Properti built-in dan untuk membacanya Anda hanya perlu satu bacaan tambahan. Mereka juga dikelompokkan bersama, yang
meningkatkan lokalitas data dan membantu prosesor modern tidak tersandung.
Selain properti yang telah ditentukan, tautan ke kelas objek ini juga disimpan di sini, beberapa penangan - analog dengan tabel metode virtual, dan tabel hash untuk properti yang belum ditentukan. Di PHP, Anda bisa menambahkan properti ke objek apa pun yang awalnya tidak ditentukan, dan jika beberapa instruksi mesin cukup untuk mengakses properti yang telah ditentukan, maka untuk properti non-standar Anda harus menggunakan tabel hash, yang akan membutuhkan banyak instruksi mesin. Tentu saja, ini jauh lebih mahal.
Referensi
Akhirnya, kami harus memperkenalkan
tipe terpisah untuk mewakili tautan PHP.

Ini adalah tipe yang sepenuhnya transparan. Itu tidak terlihat oleh skrip PHP. Script melihat zval lain yang dibangun ke dalam struktur zend_reference. Dapat dipahami bahwa kita merujuk ke satu struktur seperti itu dari setidaknya dua tempat, dan penghitung referensi dari struktur ini selalu lebih besar dari 1. Begitu penghitung turun ke 1, tautan berubah menjadi nilai skalar biasa. Zval yang tertanam dalam tautan disalin ke zval terakhir yang merujuknya, dan strukturnya sendiri dihapus.
Tampaknya bekerja dengan referensi sekarang jauh lebih rumit daripada dengan jenis lain (dan ini benar), tetapi pada kenyataannya dalam PHP 5 kami harus melakukan pekerjaan dengan kompleksitas yang sebanding ketika mengakses nilai apa pun (bahkan bilangan bulat utama). Sekarang kami menerapkan protokol yang lebih kompleks hanya untuk satu jenis dan dengan demikian mempercepat pekerjaan dengan yang lainnya, terutama dengan nilai skalar.
IS_FALSE dan IS_TRUE
Saya sudah mengatakan bahwa tipe tunggal IS_BOOL dibagi menjadi IS_FALSE dan IS_TRUE terpisah. Gagasan ini dimata-matai dalam implementasi LuaJIT, dan dibuat untuk mempercepat salah satu operasi yang paling umum - transisi bersyarat.

Jika dalam PHP 5 ia harus membaca jenisnya, memeriksa boolean, membaca nilainya, mencari tahu apakah itu benar atau salah dan melakukan transisi berdasarkan ini, sekarang cukup untuk cukup memeriksa jenisnya dan membandingkannya dengan benar:
- jika itu benar, maka kita pergi bersama satu cabang;
- jika itu kurang dari benar, pergi ke cabang lain;
- jika lebih dari benar, buka apa yang disebut jalur lambat (jalur lambat) dan di sana kita periksa jenis asalnya dan apa yang harus dilakukan dengannya: jika itu bilangan bulat, maka kita harus membandingkan nilainya dengan 0, jika mengambang - lagi dengan 0 ( tapi nyata), dll.
Konvensi panggilan
Perubahan dalam Konvensi Panggilan atau konvensi panggilan fungsi merupakan optimasi penting yang memengaruhi tidak hanya struktur data, tetapi juga algoritma yang mendasarinya. Pada gambar di sebelah kiri adalah skrip kecil yang terdiri dari fungsi foo () dan panggilannya. Di bawah ini adalah bytecode di mana skrip ini dikompilasi oleh PHP 5.

Pertama, saya akan memberi tahu Anda cara kerjanya di PHP 5.
Konvensi Panggilan di PHP 5
Pernyataan
SEND_VAL
pertama adalah mengirim nilai "3" ke fungsi foo. Untuk melakukan ini, dia terpaksa mengalokasikan zval baru di heap, salin nilai (3) di sana dan tulis nilai pointer ke struktur ini ke stack.

Begitu pula dengan instruksi kedua. Selanjutnya
DO_FCALL
menginisialisasi
CALL FRAME
, memesan tempat untuk variabel lokal dan sementara, dan mentransfer kontrol ke fungsi yang dipanggil.

RECV
pertama memeriksa argumen pertama dan menginisialisasi slot pada stack dengan variabel lokal yang sesuai ($ a). Di sini kami melakukannya tanpa menyalin dan hanya meningkatkan penghitung referensi dari parameter yang sesuai (zval dengan nilai 3). Demikian pula,
RECV
kedua membuat koneksi antara variabel $ b dan parameter 5.

Fungsi tubuh selanjutnya. 3 + 5 penambahan terjadi - ternyata 8. Ini adalah variabel sementara dan nilainya disimpan langsung di tumpukan.

KEMBALI dan kita kembali dari fungsi.

Saat kembali, kami merilis semua variabel dan argumen yang di luar cakupan. Untuk melakukan ini, kita melalui semua zval yang direferensikan oleh slot dari frame yang dibebaskan, dan untuk masing-masing kita mengurangi jumlah referensi. Jika mencapai 0, maka hancurkan struktur yang sesuai.
Seperti yang Anda lihat, bahkan operasi sederhana seperti mengirim konstanta ke suatu fungsi membutuhkan mengalokasikan memori baru, menyalin dan meningkatkan penghitung referensi, dan kemudian juga mengurangi dan menghapus dua kali lipat.
Konvensi Panggilan di PHP 7
Di PHP 7, masalah ini telah diperbaiki - sekarang di stack kami menyimpan bukan zval pointer, tetapi zval itu sendiri.

Kami juga memperkenalkan instruksi baru,
INIT_FCALL
, yang sekarang bertanggung jawab untuk menginisialisasi dan mengalokasikan memori di bawah
CALL FRAME
, dan menyediakan ruang untuk argumen dan variabel sementara.

SEND_VAL 3
sekarang hanya menyalin argumen ke slot pertama setelah
CALL FRAME
. Selanjutnya
SEND_VAL 5
ke slot kedua.

Maka yang paling menarik. Tampaknya
DO_FCALL
harus memberikan kontrol ke instruksi pertama dari fungsi yang dipanggil. Tetapi argumen sudah mengenai slot yang dicadangkan untuk parameter variabel $ a dan $ b, dan instruksi
RECV
tidak melakukan apa-apa. Karena itu, Anda dapat melewati mereka. Kami mengirim dua parameter, jadi kami lewati dua instruksi. Jika mereka mengirim tiga, mereka akan melewatkan tiga.

Jadi kita langsung ke fungsi tubuh, membuat penambahan dan kembali.

Ketika kembali, kami menghapus semua variabel lokal, tetapi sekarang hanya untuk dua slot, dan karena kami memiliki skalar di sana, kami kembali tidak perlu melakukan apa pun.

Cerita saya sedikit disederhanakan, tidak memperhitungkan fungsi akun dengan sejumlah variabel argumen dan kebutuhan untuk pengecekan tipe dan beberapa poin lainnya.
Konvensi Panggilan yang baru telah sedikit merusak kompatibilitas . PHP memiliki fungsi seperti
func_get_arg
dan
func_get_args
. Jika sebelumnya mereka mengembalikan nilai asli dari parameter yang dikirim, sekarang mereka mengembalikan nilai saat ini dari variabel lokal yang sesuai, karena kami tidak menyimpan nilai aslinya. Seperti halnya C. debuggers

Selain itu, fungsi tidak lagi dapat memiliki beberapa parameter dengan nama yang sama. Tidak ada gunanya dalam hal ini sebelumnya, tetapi saya bertemu dengan kode PHP
foo($_, $_)
. Seperti apa bentuknya? (Saya mengenali Prolog)
Manajer memori baru
Setelah selesai dengan optimalisasi struktur data dan algoritma dasar, kami sekali lagi menarik perhatian ke semua subsistem pengereman. Manajer memori di PHP 5 memakan waktu
hampir 20% dari waktu prosesor di Wordpress.
Setelah kami menyingkirkan banyak alokasi, biaya overhead-nya menjadi lebih sedikit, tetapi masih signifikan - dan bukan karena ia melakukan pekerjaan yang signifikan, tetapi karena ia tersandung pada cache. Ini terjadi karena fakta bahwa kami menggunakan algoritma malloc klasik Doug Lea, yang melibatkan menemukan lokasi memori bebas yang cocok dengan melakukan perjalanan melalui tautan dan pohon, dan semua perjalanan ini pasti menyebabkan kesalahan cache.
Saat ini, ada algoritma manajemen memori baru yang mempertimbangkan fitur prosesor modern. Misalnya:
jemalloc dan
ptmalloc dari Google . Pada awalnya, kami mencoba menggunakannya tidak berubah, tetapi tidak mendapatkan kemenangan, karena kurangnya fungsionalitas khusus PHP membuatnya lebih mahal untuk benar-benar membebaskan memori pada akhir permintaan. Akibatnya, kami meninggalkan dlmalloc dan menulis sesuatu dari kami sendiri, menggabungkan ide-ide dari manajer memori lama dan jemalloc.
Kami telah
mengurangi overhead Memory Manager hingga 5% , mengurangi overhead memori untuk informasi layanan dan meningkatkan penggunaan cache CPU. Blok memori yang sesuai sekarang dicari oleh bitmap, memori untuk blok kecil dialokasikan dari halaman individual dan di-cache saat dirilis, fungsi-fungsi khusus untuk ukuran blok yang sering digunakan ditambahkan.
Banyak perbaikan kecil
Saya hanya berbicara tentang perbaikan yang paling penting, tetapi ada jauh lebih kecil. Saya dapat menyebutkan beberapa dari mereka.
- API cepat untuk mem-parsing parameter fungsi internal dan API baru untuk iterasi di atas HashTable.
- Instruksi VM baru: penggabungan string, spesialisasi, instruksi super.
- Beberapa fungsi internal telah diubah menjadi instruksi VM: strlen, is_int.
- Menggunakan register CPU untuk register VM: IP dan FP.
- Optimalisasi duplikasi dan penghapusan array.
- Menggunakan jumlah tautan alih-alih menyalin di mana pun Anda bisa.
- PCRE JIT.
- Optimalisasi fungsi internal dan serialisasi ().
- Mengurangi ukuran kode dan memproses data.
Beberapa sangat sederhana, misalnya, hanya membutuhkan tiga baris kode untuk mengaktifkan JIT dalam ekspresi Perl reguler, dan ini segera membawa akselerasi yang terlihat (2-3%) ke hampir semua aplikasi. Optimalisasi lainnya menyentuh beberapa aspek sempit dari fungsi PHP tertentu, dan tidak terlalu menarik, meskipun kontribusi total dari semua perbaikan kecil ini cukup signifikan.
Kamu datang ke apa
Ini adalah kontribusi berbagai subsistem pada WordPress / PHP 7.0.

Kontribusi mesin virtual meningkat hingga 50%. Memory Manager 5% β Memory Manager, . 130 . , 10 . , Memory Manager , .

:
- 2 .
- MM 17 .
- - 4 .
- WordPress 3,5 .
2,5- , . ? , , CPU time, β , . PHP , .
PHP 7
WordPress 3.6 β . - , PHP 7 mysql, , .

, PHPNG. 2/3 . , .
, WordPress, , β 1,5 2- .
PHP 7 HHVM
HHVM.

β . . Facebook . HHVM . , , , , .

PHP 7 β . Vebia, Etsy Badoo. Highload- , .
PHP 7.0 Etsy Badoo -. Badoo
.

, 2 , β 7 .
PHP 7.0.
, PHP 7.1, .
PHP Russia PHP 8 . PHP, , , β 1 . , , β , , , .