Mereka mengganti tracelet TranslatorX64 dengan Representasi Menengah HipHop yang baru (hhir), dan lapisan tipuan yang baru di mana terdapat logika untuk menghasilkan hhir, yang sebenarnya disebut dengan nama yang sama, hhir.
Dari level tinggi, ia menggunakan 6 instruksi untuk melakukan apa yang diperlukan 9 instruksi sebelumnya, seperti disebutkan di sini: "Ini dimulai dengan typechecks yang sama tetapi isi terjemahannya adalah 6 instruksi, jauh lebih baik daripada 9 dari TranslatorX64"
Itu sebagian besar artefak tentang bagaimana sistem dirancang dan merupakan sesuatu yang kami rencanakan untuk akhirnya dibersihkan. Semua kode yang tersisa di TranslatorX64 adalah mesin yang diperlukan untuk memancarkan kode dan menghubungkan terjemahan secara bersamaan; kode yang mengerti cara menerjemahkan bytecodes hilang dari TranslatorX64.
Ketika hhir menggantikan TranslatorX64, itu menghasilkan kode yang kira-kira 5% lebih cepat dan tampak secara signifikan lebih baik pada inspeksi manual. Kami menindaklanjuti debut produksinya dengan mini-lockdown lainnya dan mendapat tambahan keuntungan kinerja 10% di atas itu. Untuk melihat beberapa peningkatan ini dalam tindakan, mari kita lihat fungsi addPositive dan bagian dari terjemahannya.
function addPositive($arr) {
$n = count($arr);
$sum = 0;
for ($i = 0; $i < $n; $i++) {
$elem = $arr[$i];
if ($elem > 0) {
$sum = $sum + $elem;
}
}
return $sum;
}
Fungsi ini terlihat seperti banyak kode PHP: ia berputar di atas array dan melakukan sesuatu dengan setiap elemen. Mari kita fokus pada baris 5 dan 6 untuk saat ini, bersama dengan bytecode mereka:
$elem = $arr[$i];
if ($elem > 0) {
// line 5
85: CGetM <L:0 EL:3>
98: SetL 4
100: PopC
// line 6
101: Int 0
110: CGetL2 4
112: Gt
113: JmpZ 13 (126)
Dua baris ini memuat elemen dari array, menyimpannya dalam variabel lokal, lalu membandingkan nilai lokal itu dengan 0 dan melompat secara kondisional di suatu tempat berdasarkan hasilnya. Jika Anda tertarik lebih detail tentang apa yang terjadi dalam bytecode, Anda dapat membaca bytecode.specification. JIT, baik sekarang dan kembali di hari-hari TranslatorX64, memecah kode ini menjadi dua jejak: satu dengan hanya CGetM, kemudian yang lain dengan sisa instruksi (penjelasan lengkap mengapa ini terjadi tidak relevan di sini, tapi itu sebagian besar karena kita tidak tahu pada waktu kompilasi apa jenis elemen array akan). Terjemahan CGetM bermuara pada panggilan ke fungsi pembantu C ++ dan tidak terlalu menarik, jadi kita akan melihat tracelet kedua. Komitmen ini adalah pensiun resmi TranslatorX64,
cmpl $0xa, 0xc(%rbx)
jnz 0x276004b2
cmpl $0xc, -0x44(%rbp)
jnle 0x276004b2
101: SetL 4
103: PopC
movq (%rbx), %rax
movq -0x50(%rbp), %r13
104: Int 0
xor %ecx, %ecx
113: CGetL2 4
mov %rax, %rdx
movl $0xa, -0x44(%rbp)
movq %rax, -0x50(%rbp)
add $0x10, %rbx
cmp %rcx, %rdx
115: Gt
116: JmpZ 13 (129)
jle 0x7608200
Empat baris pertama adalah typechecks yang memverifikasi bahwa nilai dalam $ elem dan nilai di atas tumpukan adalah jenis yang kita harapkan. Jika salah satu dari mereka gagal, kami akan melompat ke kode yang memicu retranslation tracelet, menggunakan tipe baru untuk menghasilkan potongan kode mesin yang berbeda. Daging terjemahan mengikuti, dan kode memiliki banyak ruang untuk perbaikan. Ada dead load pada baris 8, register yang mudah dihindari untuk register move pada baris 12, dan kesempatan untuk propagasi konstan antara baris 10 dan 16. Ini semua adalah konsekuensi dari pendekatan bytecode-at-a-time yang digunakan oleh TranslatorX64. Tidak ada kompiler terhormat yang akan memancarkan kode seperti ini, tetapi optimasi sederhana yang diperlukan untuk menghindarinya tidak cocok dengan model TranslatorX64.
Sekarang mari kita lihat tracelet yang sama yang diterjemahkan menggunakan hhir, pada revisi hhvm yang sama:
cmpl $0xa, 0xc(%rbx)
jnz 0x276004bf
cmpl $0xc, -0x44(%rbp)
jnle 0x276004bf
101: SetL 4
movq (%rbx), %rcx
movl $0xa, -0x44(%rbp)
movq %rcx, -0x50(%rbp)
115: Gt
116: JmpZ 13 (129)
add $0x10, %rbx
cmp $0x0, %rcx
jle 0x76081c0
Ini dimulai dengan typechecks yang sama tetapi isi terjemahannya adalah 6 instruksi, jauh lebih baik daripada 9 dari TranslatorX64. Perhatikan bahwa tidak ada beban mati atau register untuk mendaftar gerakan, dan kode 0 langsung dari kode byt Int 0 disebarkan ke cmp pada baris 12. Inilah hhir yang dihasilkan antara tracelet dan terjemahan itu:
(00) DefLabel
(02) t1:FramePtr = DefFP
(03) t2:StkPtr = DefSP<6> t1:FramePtr
(05) t3:StkPtr = GuardStk<Int,0> t2:StkPtr
(06) GuardLoc<Uncounted,4> t1:FramePtr
(11) t4:Int = LdStack<Int,0> t3:StkPtr
(13) StLoc<4> t1:FramePtr, t4:Int
(27) t10:StkPtr = SpillStack t3:StkPtr, 1
(35) SyncABIRegs t1:FramePtr, t10:StkPtr
(36) ReqBindJmpLte<129,121> t4:Int, 0
Instruksi bytecode telah dipecah menjadi operasi yang lebih kecil, lebih sederhana. Banyak operasi yang tersembunyi dalam perilaku bytecode tertentu secara eksplisit diwakili dalam hhir, seperti LdStack on line 6 yang merupakan bagian dari SetL. Dengan menggunakan temporari tanpa nama (t1, t2, dll ...) alih-alih register fisik untuk mewakili aliran nilai, kita dapat dengan mudah melacak definisi dan penggunaan dari setiap nilai. Ini membuatnya sepele untuk melihat apakah tujuan suatu beban benar-benar digunakan, atau jika salah satu input ke instruksi benar-benar merupakan nilai konstan dari 3 bytecode yang lalu. Untuk penjelasan yang jauh lebih menyeluruh tentang apa dan bagaimana cara kerjanya, lihat ir.spesifikasi.
Contoh ini menunjukkan hanya beberapa peningkatan yang dilakukan oleh TranslatorX64. Memperoleh hhir yang digunakan untuk produksi dan pensiun TranslatorX64 pada Mei 2013 adalah tonggak pencapaian yang bagus, tetapi itu baru permulaan. Sejak itu, kami telah menerapkan lebih banyak optimasi yang hampir tidak mungkin dilakukan di TranslatorX64, menjadikan hhvm hampir dua kali lebih efisien dalam prosesnya. Ini juga sangat penting dalam upaya kami untuk menjalankan hhvm pada prosesor ARM dengan mengisolasi dan mengurangi jumlah kode khusus arsitektur yang perlu kami implementasikan kembali. Tonton pos mendatang yang ditujukan untuk port ARM kami untuk detail lebih lanjut! "