Materi, bagian pertama dari terjemahan yang kami terbitkan hari ini, akan membahas bagaimana mesin JavaScript V8 memilih cara terbaik untuk mewakili berbagai nilai JS dalam memori, dan bagaimana hal ini memengaruhi mekanisme internal V8 terkait bekerja dengan apa yang disebut
formulir benda (Bentuk). Semua ini akan membantu kami memilah esensi
masalah kinerja Bereaksi baru-baru ini.

Tipe data JavaScript
Setiap nilai JavaScript hanya dapat memiliki satu dari delapan tipe data yang ada:
Number
,
String
,
Symbol
,
BigInt
,
Boolean
,
Undefined
,
Null
dan
Object
.
Tipe data JavaScriptJenis nilai dapat ditentukan menggunakan operator
typeof
, tetapi ada satu pengecualian penting:
typeof 42;
Seperti yang Anda lihat, perintah
typeof null
mengembalikan
'object'
, bukan
'null'
, meskipun fakta bahwa
null
memiliki tipe sendiri -
Null
. Untuk memahami alasan
typeof
perilaku ini, kami memperhitungkan fakta bahwa rangkaian semua tipe JavaScript dapat dibagi menjadi dua grup:
- Objek (mis., Ketik
Object
). - Nilai-nilai primitif (yaitu, nilai-nilai non-objektif).
Dalam terang pengetahuan ini, ternyata
null
berarti "tidak ada nilai objek", sedangkan
undefined
berarti "tidak ada nilai".
Nilai-nilai primitif, objek, null dan tidak terdefinisiMengikuti refleksi-refleksi ini dalam semangat Java, Brendan Eich mendesain JavaScript sehingga
typeof
operator akan mengembalikan
'object'
untuk nilai-nilai tipe-tipe yang terletak pada gambar sebelumnya di sebelah kanan. Semua nilai objek dan
null
sampai di sini. Itulah sebabnya ekspresi
typeof null === 'object'
benar, walaupun ada tipe terpisah
Null
dalam spesifikasi bahasa.
Ekspresi typeof v === 'objek' benarRepresentasi Nilai
Mesin JavaScript harus dapat mewakili nilai JavaScript apa pun dalam memori. Namun, penting untuk dicatat bahwa tipe nilai dalam JavaScript terpisah dari bagaimana mesin JS merepresentasikannya dalam memori.
Misalnya, nilai 42 dalam JavaScript adalah
number
jenis.
typeof 42;
Ada beberapa cara untuk merepresentasikan bilangan bulat seperti 42 di memori:
Menurut standar ECMAScript, angka adalah nilai floating point 64-bit, yang dikenal sebagai angka floating-point presisi ganda (Float64). Namun, ini tidak berarti bahwa mesin JavaScript selalu menyimpan angka dalam tampilan Float64. Itu akan sangat, sangat tidak efisien! Engine dapat menggunakan representasi internal lainnya dari angka - selama perilaku nilai tersebut sama persis dengan bagaimana perilaku angka Float64.
Sebagian besar angka dalam aplikasi JS nyata, ternyata, adalah
indeks array ECMAScript yang valid. Yaitu - bilangan bulat dalam rentang dari 0 hingga 2
32 -2.
array[0];
Mesin JavaScript dapat memilih format optimal untuk mewakili nilai-nilai tersebut dalam memori. Ini dilakukan untuk mengoptimalkan kode yang berfungsi dengan elemen array menggunakan indeks. Prosesor yang melakukan operasi akses memori memerlukan indeks array tersedia sebagai angka yang disimpan dalam tampilan dengan
tambahan dua . Jika sebaliknya kami mewakili indeks array dalam bentuk nilai Float64, ini akan berarti pemborosan sumber daya sistem, karena mesin kemudian perlu mengkonversi angka Float64 ke format dengan penambahan dua dan sebaliknya setiap kali seseorang mengakses elemen array.
Representasi angka 32-bit dengan penambahan hingga dua bermanfaat tidak hanya untuk mengoptimalkan kerja dengan array. Secara umum, dapat dicatat bahwa prosesor melakukan operasi integer jauh lebih cepat daripada operasi yang menggunakan nilai floating point. Itulah sebabnya dalam contoh berikut, siklus pertama tanpa masalah dua kali lebih cepat dibandingkan dengan siklus kedua.
for (let i = 0; i < 1000; ++i) {
Hal yang sama berlaku untuk perhitungan menggunakan operator matematika.
Misalnya, kinerja operator untuk mengambil sisa pembagian dari fragmen kode berikutnya tergantung pada angka apa yang terlibat dalam perhitungan.
const remainder = value % divisor;
Jika kedua operan diwakili oleh bilangan bulat, maka prosesor dapat menghitung hasilnya dengan sangat efisien. Ada optimasi tambahan dalam V8 untuk kasus di mana operan
divisor
diwakili oleh angka yang merupakan kekuatan dua. Untuk nilai yang direpresentasikan sebagai angka floating point, perhitungannya jauh lebih rumit dan membutuhkan waktu lebih lama.
Karena operasi integer biasanya dilakukan jauh lebih cepat daripada operasi pada nilai floating-point, mungkin terlihat bahwa engine dapat selalu menyimpan semua integer dan semua hasil operasi integer dalam format dengan tambahan dua. Sayangnya, pendekatan seperti itu akan melanggar spesifikasi naskah ECMAS. Seperti yang telah disebutkan, standar menyediakan representasi angka dalam format Float64, dan beberapa operasi dengan bilangan bulat dapat menyebabkan tampilan hasil dalam bentuk angka floating-point. Adalah penting bahwa dalam situasi seperti itu, mesin JS menghasilkan hasil yang benar.
Meskipun dalam contoh sebelumnya semua angka di sisi kiri ekspresi adalah bilangan bulat, semua angka di sisi kanan ekspresi adalah nilai floating point. Itulah sebabnya mengapa tidak ada operasi sebelumnya yang dapat dilakukan dengan benar menggunakan format 32-bit dengan tambahan hingga dua. Mesin JavaScript harus memberi perhatian khusus untuk memastikan bahwa ketika melakukan operasi integer Anda mendapatkan yang benar (meskipun mampu terlihat tidak biasa - seperti dalam contoh sebelumnya) hasil Float64.
Dalam kasus bilangan bulat kecil yang berada dalam kisaran representasi 31-bit bilangan bulat yang ditandatangani, V8 menggunakan representasi khusus yang disebut
Smi
. Segala sesuatu yang bukan nilai
Smi
direpresentasikan sebagai nilai
HeapObject
, yang merupakan alamat beberapa entitas dalam memori. Untuk nomor yang tidak termasuk dalam rentang
Smi
, kami memiliki jenis
HeapObject
- yang disebut
HeapNumber
.
-Infinity
Seperti yang Anda lihat dari contoh sebelumnya, beberapa nomor JS direpresentasikan sebagai
Smi
, dan beberapa sebagai
HeapNumber
. Mesin V8 dioptimalkan dalam hal memproses angka
Smi
. Faktanya adalah integer kecil sangat umum dalam program JS nyata. Ketika bekerja dengan nilai-nilai
Smi
, tidak perlu mengalokasikan memori untuk masing-masing entitas. Selain itu, penggunaannya memungkinkan Anda untuk melakukan operasi cepat dengan bilangan bulat.
Perbandingan Smi, HeapNumber dan MutableHeapNumber
Mari kita bicara tentang seperti apa struktur internal dari mekanisme ini. Misalkan kita memiliki objek berikut:
const o = { x: 42,
Nilai 42 dari properti objek
x
dikodekan sebagai
Smi
. Ini berarti dapat disimpan di dalam objek itu sendiri. Untuk menyimpan nilai 4.2, di sisi lain, Anda harus membuat entitas terpisah. Di objek, akan ada tautan ke entitas ini.
Penyimpanan berbagai nilaiMisalkan kita menjalankan kode JavaScript berikut:
ox += 10;
Dalam hal ini, nilai properti
x
dapat diperbarui di lokasi penyimpanannya. Faktanya adalah bahwa nilai baru
x
adalah 52, dan angka ini berada dalam kisaran
Smi
.
Nilai baru properti x disimpan di tempat nilai sebelumnya disimpan.Namun, nilai baru
y
, 5.2, tidak masuk ke dalam kisaran
Smi
, dan itu, di samping itu, berbeda dari nilai sebelumnya dari y - 4.2. Akibatnya, V8 harus mengalokasikan memori untuk entitas
HeapNumber
baru dan referensi itu dari objek yang sudah ada.
Entitas baru HeapNumber untuk menyimpan nilai y baruEntitas
HeapNumber
tidak dapat diubah. Ini memungkinkan Anda untuk mengimplementasikan beberapa optimasi. Misalkan kita ingin mengatur properti objek
x
nilai properti
y
:
ox = oy;
Saat melakukan operasi ini, kita bisa merujuk ke entitas
HeapNumber
sama, dan tidak mengalokasikan memori tambahan untuk menyimpan nilai yang sama.
Salah satu kelemahan imunitas entitas HeapNuber adalah bahwa pemutakhiran bidang yang sering dengan nilai di luar rentang
Smi
lambat. Ini ditunjukkan dalam contoh berikut:
Saat memproses baris pertama, sebuah instance dari
HeapNumber
dibuat, nilai awalnya adalah 0,1. Dalam inti siklus, nilai ini berubah menjadi 1.1, 2.1, 3.1, 4.1, dan akhirnya ke 5.1. Akibatnya, dalam proses mengeksekusi kode ini, 6 contoh
HeapNumber
, lima di antaranya akan dikenai operasi pengumpulan sampah setelah selesainya loop.
Entitas BanyakUntuk menghindari masalah ini, V8 memiliki optimasi, yang merupakan mekanisme untuk memperbarui bidang numerik yang nilainya tidak sesuai dengan rentang
Smi
di tempat yang sama di mana mereka sudah disimpan. Jika bidang numerik menyimpan nilai yang entitas
Smi
tidak cocok untuk penyimpanan, maka V8, dalam bentuk objek, menandai bidang ini sebagai
Double
dan mengalokasikan memori untuk entitas
MutableHeapNumber
, yang menyimpan nilai nyata yang diwakili dalam format Float64.
Menggunakan Entitas MutableHeapNumberAkibatnya, setelah nilai bidang berubah, V8 tidak perlu lagi mengalokasikan memori untuk entitas
HeapNumber
baru. Alih-alih, cukup tulis nilai baru ke entitas
MutableHeapNumber
ada.
Menulis nilai baru ke MutableHeapNumberNamun, pendekatan ini memiliki kekurangan. Yaitu, karena nilai
MutableHeapNumber
dapat berubah, penting untuk memastikan bahwa sistem bekerja sedemikian rupa sehingga nilai-nilai ini berperilaku seperti yang disediakan dalam spesifikasi bahasa.
Kerugian MutableHeapNumberMisalnya, jika Anda menetapkan nilai
ox
beberapa variabel lain
y
, maka Anda perlu memastikan bahwa nilai
y
tidak berubah dengan perubahan berikutnya pada
ox
. Itu akan menjadi pelanggaran terhadap spesifikasi JavaScript! Akibatnya, ketika mengakses
ox
, angka tersebut harus
HeapNumber
ulang ke nilai
HeapNumber
biasa sebelum diberikan
y
.
Dalam hal angka floating-point, V8 melakukan operasi pengemasan di atas menggunakan mekanisme internal. Tetapi dalam kasus bilangan bulat kecil, menggunakan
MutableHeapNumber
akan membuang-buang waktu karena
Smi
adalah cara yang lebih efisien untuk mewakili angka-angka tersebut.
const object = { x: 1 };
Untuk menghindari penggunaan sumber daya sistem yang tidak efisien, yang perlu kita lakukan untuk bekerja dengan bilangan bulat kecil adalah menandai bidang terkait dalam bentuk objek sebagai
Smi
. Akibatnya, nilai-nilai bidang ini, asalkan sesuai dengan rentang
Smi
, dapat diperbarui langsung di dalam objek.
Bekerja dengan bilangan bulat yang nilainya berada dalam kisaran SmiDilanjutkan ...
Pembaca yang budiman! Pernahkah Anda mengalami masalah kinerja JavaScript yang disebabkan oleh fitur mesin JS?
