Enjin V8: Bagaimana ia berfungsi?

V8 ialah a Enjin javascript dibuat di pusat pembangunan Google di Jerman. Enjin adalah sumber terbuka dan ditulis dalam C + +. Ia digunakan pada kedua-dua bahagian klien (Google Chrome) dan pada bahagian pelayan (node.js), dalam ekosistem aplikasi JavaScript.
atlet yang berlari: V8 oleh js republic
V8 pertama kali dikonsepkan untuk meningkatkan prestasi masa jalan JavaScript dalam penyemak imbas. Untuk mendapatkan kelajuan, V8 menterjemah kod JavaScript ke dalam bahasa mesin berprestasi tinggi dan bukannya menggunakan penterjemah. Enjin menyusun kod Javascript ke dalam pemasangan pada masa jalan dengan melaksanakan a Penyusun JIT (Just-In-Time). seperti kebanyakan enjin Javascript moden (seperti SpiderMonkey atau Rhino (Mozilla)) lakukan. Perbezaan utama dengan V8 ialah ia tidak menghasilkan bytecode/kod perantaraan.
Tujuan artikel ini adalah untuk menunjukkan kepada anda dan membuat anda faham bagaimana enjin V8 berfungsi untuk menghasilkan kod yang dioptimumkan untuk bahagian klien dan bahagian pelayan aplikasi anda. Jika anda pernah bertanya kepada diri sendiri soalan "Perlukah saya mengambil berat tentang prestasi JavaScript", maka saya menjawab anda dengan petikan daripada Daniel Clifford (ketua teknologi dan pengurus pasukan V8):
Dalam bahasa Perancis:

“Ia bukan sahaja untuk menjadikan apl semasa anda lebih pantas, ia juga untuk menjadikan perkara yang tidak mungkin dilakukan sebelum ini”

Dalam bahasa Inggeris:

“Ia bukan sahaja tentang menjadikan aplikasi semasa anda berjalan lebih pantas, ia juga mengenai membolehkan perkara yang anda tidak pernah dapat lakukan pada masa lalu”.

Kelas tersembunyi

JavaScript adalah berasaskan prototaip: Tiada kelas dan objek dicipta menggunakan proses pengklonan. JS juga merupakan bahasa yang ditaip secara dinamik: jenis dan maklumatnya tidak jelas, dan sifat boleh ditambah malah dialih keluar daripada objek dengan cepat. Mencapai jenis dan sifat dengan cekap ialah cabaran besar pertama untuk V8. Daripada menggunakan struktur seperti kamus untuk memegang sifat objek, dan melakukan carian dinamik untuk mencari lokasi harta (seperti kebanyakan enjin JavaScript lakukan), V8 mencipta kelas tersembunyi, dalam masa jalan, supaya mempunyai perwakilan dalaman bagi sistem jenis dan menambah baik masa capaian harta benda.
Ambil sebagai contoh fungsi Point dan penciptaan dua objek Point:

Jika susun atur adalah sama, yang mana mereka, p et q adalah sebahagian daripada kelas tersembunyi yang sama yang dicipta oleh V8. Ini menunjukkan satu lagi kelebihan menggunakan kelas tersembunyi: Mereka membenarkan V8 mengumpulkan objek yang mempunyai sifat yang sama. Di sini; p et q guna sama kod yang dioptimumkan.
Sekarang anggap kita mahu menambah harta z kepada objek kita q, sejurus selepas pengisytiharannya (yang benar-benar normal dengan bahasa yang ditaip secara dinamik).
Bagaimanakah V8 mengendalikan senario ini? Pada hakikatnya, V8 mencipta kelas tersembunyi baharu setiap kali fungsi pembina mengisytiharkan harta dan ia menyemak perubahan dalam kelas tersembunyi. kenapa ? Kerana jika dua objek dicipta (p et q) dan jika ahli ditambah pada objek kedua (q) selepas penciptaannya, V8 perlu mengekalkan kelas tersembunyi yang terakhir dicipta (untuk objek pertama p) dan enjin perlu mencipta yang baharu dengan (untuk objek kedua q) ahli baru.
Contoh cara V8 mencipta kelas tersembunyi untuk fungsi pembina
Setiap kali kelas tersembunyi baharu dibuat, kelas sebelumnya dikemas kini dengan peralihan kelas yang menunjukkan kelas tersembunyi yang hendak digunakan dan bukannya kelas sebelumnya.

Pengoptimuman kod

Memandangkan V8 mencipta kelas tersembunyi baharu untuk setiap harta, ciptaan mereka harus diminimumkan. Untuk itu, kita mesti cuba untuk tidak menambah sifat selepas penciptaan objek dan sentiasa memulakan ahli objek dalam susunan yang sama (untuk mengelakkan pokok kelas tersembunyi yang berbeza).
Petua lain: operasi monomorfik ialah operasi yang berfungsi pada objek yang berkongsi kelas tersembunyi yang sama. V8 mencipta kelas tersembunyi apabila memanggil fungsi. Jika kita memanggil fungsi yang sama tetapi dengan parameter jenis yang berbeza, V8 perlu mencipta kelas tersembunyi yang lain: Lebih suka kod monomorfik daripada kod polimorfik.

Lebih banyak contoh cara V8 mengoptimumkan kod JavaScript

Nilai teg

Untuk mempunyai perwakilan nombor dan objek JavaScript yang cekap, V8 mewakilinya dengan nilai Sedikit 32. Ia menggunakan sedikit untuk mengetahui sama ada ia adalah objek (bendera=1) atau integer (bendera=0) yang dipanggil daripada Integer Kecil atau SMI kerana 31 bit tersebut. Jadi jika nilai berangka lebih besar daripada 31 bit, V8 akan membalut nombor itu, sama ada menjadikannya berganda atau mencipta objek baharu untuk membalutnya.
Pengoptimuman Kod: Gunakan nombor yang ditandatangani 31-bit sebanyak mungkin, untuk mengelakkan operasi pembungkusan yang mahal dalam objek JavaScript.

Lukisan

V8 menggunakan dua kaedah berbeza untuk memproses tatasusunan:

  • Elemen Cepat: Direka untuk papan di mana semua kekunci sangat padat. Mereka ada satu penimbal storan linear supaya akses adalah sangat cekap.

  • Item kamus: Direka untuk papan di mana unsur-unsur bertaburan. Mereka sebenarnya jadual hash, yang lebih mahal untuk diakses daripada "Elemen Pantas".

Pengoptimuman Kod: Pastikan V8 menggunakan kaedah "Elemen Pantas" untuk menangani tatasusunan, dengan kata lain, elakkan tatasusunan dengan elemen berselerak di mana kunci tidak dipesan secara berperingkat. Elakkan juga pra-peruntukan tatasusunan besar. Adalah lebih baik mereka berkembang semasa mereka pergi. Akhir sekali, jangan padamkan elemen dalam tatasusunan: semua kekunci kemudiannya akan tersebar.

Bagaimanakah V8 menyusun kod JavaScript?

V8 mempunyai dua penyusun!

  • Un Penyusun "Penuh". yang boleh menghasilkan kod yang baik untuk mana-mana JavaScript: kod yang bagus tetapi, ia bukan kod JIT yang bagus. Tujuan pengkompil ini adalah untuk menjana kod dengan cepat. Untuk mencapai matlamat ini, pengkompil tidak melakukan analisis jenis dan tidak tahu apa-apa tentang jenis. Sebaliknya, pengkompil menggunakan strategi cache sebaris atau “IC” untuk memperhalusi pengetahuan tentang jenis semasa program sedang berjalan. IC sangat cekap dan meningkatkan kelajuan (dengan pesanan x20).

  • Un pengoptimuman pengkompil yang menghasilkan kod hebat untuk kebanyakan bahasa JavaScript. Pengkompil ini datang kemudian dan menyusun semula fungsi yang digunakan beberapa kali (fungsi panas). Pengkompil ini mengambil jenis daripada cache sebaris dan memutuskan cara terbaik untuk mengoptimumkan kod. Walau bagaimanapun, beberapa bahagian bahasa masih belum disokong, contohnya cuba/tangkap blok. (Helah untuk cuba/tangkap blok adalah dengan menulis kod "tidak stabil" dalam fungsi dan memanggilnya dalam blok cuba).

Pengoptimuman Kod: V8 juga menyokong penyahoptimuman: Pengkompil pengoptimuman membuat andaian optimistik daripada cache sebaris tentang jenis yang berbeza, penyahoptimuman berlaku jika andaian ini tidak sah. Contohnya, jika kelas tersembunyi telah dibuat yang tidak dimaksudkan, V8 membuang kod yang dioptimumkan dan kembali kepada pengkompil "Penuh" untuk mendapatkan semula jenis daripada cache sebaris. Proses ini lambat dan harus dielakkan, yang boleh dilakukan dengan mengelakkan perubahan fungsi selepas ia dioptimumkan.

Sumber

  • Google I/O 2012 "Melanggar Had Kelajuan JavaScript dengan V8" dengan Daniel Clifford, ketua teknologi dan pengurus pasukan V8: video dan slaid.

  • V8: enjin JavaScript sumber terbuka: video daripada Lars Bak, jurutera teras V8.

  • Catatan blog Nikkei Electronics Asia: Mengapa Enjin Google V8 Baharu Begitu Pantas?

Sumber tambahan: monomorfik vs polimorfik
Sumber: Bagaimana enjin V8 berfungsi?, Thibault Laurens
Terjemahan oleh Yoan Ribeiro