Apakah ukuran array javascript tetap?

Properti panjang larik JavaScript mengembalikan bilangan bulat 32-bit yang tidak ditandatangani yang menentukan jumlah elemen dalam larik

Sintaksis

Sintaksnya adalah sebagai berikut −

array.length

Nilai Pengembalian

Mengembalikan panjang array

Contoh

Coba contoh berikut

   
      JavaScript Array length Property
   
   
      
            
   

_

Keluaran

arr.length is : 3 

javascript_arrays_object. htm

Di bagian sebelumnya dari seri ini, kami membahas Map and Set, koleksi standar yang diperkenalkan di ES6. Kali ini kita akan fokus pada array JavaScript

Array, yang pada dasarnya adalah objek seperti daftar, adalah salah satu fitur inti dari bahasa ini dan setiap pengembang JavaScript memiliki pengalaman yang solid dalam bekerja dengannya. Posting blog ini tidak mencoba memberi Anda pemahaman tentang API publik tetapi sebaliknya bertujuan untuk membahas secara singkat berbagai aspek implementasi internal array JS V8 yang tampaknya layak bagi saya. tata letak memori, batasan ukuran, dan detail implementasi menarik lainnya

Agar lebih sederhana, bagian selanjutnya dari posting blog mengasumsikan bahwa V8 berjalan pada sistem 64-bit

TL;DR penggemar mungkin ingin melompat ke bagian terakhir dari posting blog di mana Anda dapat menemukan ringkasan

Penafian. Apa yang tertulis di bawah ini adalah detail implementasi khusus untuk V8 8. 9 dibundel dengan versi dev terbaru dari Node. js (komit 49342fe lebih tepatnya). Seperti biasa, Anda seharusnya tidak mengharapkan perilaku apa pun di luar spesifikasi, karena detail implementasi dapat berubah di versi V8 apa pun

Sekali Waktu dalam REPL

Anda mungkin bertanya pada diri sendiri. apa yang mungkin lebih sederhana dari array JavaScript? . e. sepotong memori yang bersebelahan. Semua operasi harus berupa manipulasi langsung dengan data yang disimpan dalam larik yang mendasarinya. Tapi seperti yang akan kita lihat nanti, kenyataannya sedikit lebih rumit dari itu

Untuk membuatnya lebih praktis, kita akan mengamati transformasi internal dari sebuah array di sebuah Node. js REPL. Lebih sedikit kata, lebih banyak kode, jadi mari kita jalankan

$ node --allow-natives-syntaxWelcome to Node.js v16.0.0-pre.Type “.help” for more information.>

Kami menggunakan bendera --allow-natives-syntax untuk dapat menggunakan fungsi %DebugPrint() V8. Fungsi ini mencetak informasi debug internal untuk objek atau nilai primitif yang diberikan

Sekarang mari buat array kosong dan cetak informasi debugnya

> const arr = [];
undefined
> %DebugPrint(arr);
DebugPrint: 0x3db6370d4e51: [JSArray]
- map: 0x3de594a433f9 [FastProperties]
- prototype: 0x3a5538d05849
- elements: 0x357222481309 [PACKED_SMI_ELEMENTS]
- length: 0
- properties: 0x357222481309
- All own properties (excluding elements): {
0x357222484909: [String] in ReadOnlySpace: #length: 0x0f4cc91c1189 (const accessor descriptor), location: descriptor
}
...
[]
_

Output aslinya cukup panjang, jadi saya memangkasnya. Yang kami minati adalah bagian - elements: .. [PACKED_SMI_ELEMENTS] dari output. Ini memberitahu kita bahwa array kita menggunakan a untuk menyimpan data (V8 menggunakan istilah "backing store" untuk ini), seperti yang kita harapkan. Ukuran array itu adalah nol

Cetakan debug juga memberi tahu kita bahwa larik JS kita memiliki jenis elemen PACKED_SMI_ELEMENTS. Jenis elemen adalah metadata yang dilacak oleh V8 untuk mengoptimalkan operasi larik. Ini menjelaskan jenis elemen yang disimpan dalam array. Jika Anda tidak terbiasa dengan konsepnya, Anda harus membaca posting blog hebat ini dari tim V8

PACKED_SMI_ELEMENTS adalah jenis elemen paling spesifik yang berarti bahwa semua item dalam array adalah Smis, bilangan bulat kecil dari rentang -2³¹ hingga 2³¹-1. Berdasarkan metadata ini, V8 dapat menghindari pemeriksaan dan konversi nilai yang tidak perlu saat menangani array. Aspek penting lainnya bagi kami adalah sebagai berikut. Ketika array JS dimodifikasi, jenis elemennya dapat bertransisi dari jenis yang lebih spesifik ke jenis yang kurang spesifik, tetapi tidak sebaliknya. Misalnya, jika jenis elemen array berubah dari PACKED_SMI_ELEMENTS menjadi sesuatu yang lain karena penyisipan, tidak ada jalan kembali ke jenis asli (lebih spesifik) untuk instance array khusus ini

Untuk melihat bagaimana larik internal tumbuh, kita akan menambahkan elemen pertamanya, bilangan bulat kecil

> arr.push(42);
> %DebugPrint(arr);
DebugPrint: 0xe61bd5eb321: [JSArray] in OldSpace
...
- elements: 0x0e61bd5e7501 [PACKED_SMI_ELEMENTS]
- length: 1
...
- elements: 0x0e61bd5e7501 {
0: 42
1-16: 0x357222481669
}
...
[ 42 ]

Di sini kita melihat bahwa array internal yang digunakan sebagai backing store telah berubah menjadi [PACKED_SMI_ELEMENTS]. Array baru memiliki jenis elemen yang sama, tetapi alamat berbeda, dan ukuran array internal sama dengan 17. Pada sistem 64-bit kami, ini berarti dibutuhkan 17 * 8=136 byte memori (demi kesederhanaan, kami mengabaikan header objek). Ini juga berarti bahwa larik internal yang dialokasikan lebih besar dari yang kami minta. Ini memungkinkan V8 mencapai konstanta untuk

> const arr = [];
undefined
> %DebugPrint(arr);
DebugPrint: 0x3db6370d4e51: [JSArray]
- map: 0x3de594a433f9 [FastProperties]
- prototype: 0x3a5538d05849
- elements: 0x357222481309 [PACKED_SMI_ELEMENTS]
- length: 0
- properties: 0x357222481309
- All own properties (excluding elements): {
0x357222484909: [String] in ReadOnlySpace: #length: 0x0f4cc91c1189 (const accessor descriptor), location: descriptor
}
...
[]
0 dan operasi serupa yang menumbuhkan larik. Berikut ini digunakan untuk menentukan ukuran baru dalam situasi ketika array internal tidak cukup

new_capacity = (old_capacity + 50%) + 16

Di sini,

> const arr = [];
undefined
> %DebugPrint(arr);
DebugPrint: 0x3db6370d4e51: [JSArray]
- map: 0x3de594a433f9 [FastProperties]
- prototype: 0x3a5538d05849
- elements: 0x357222481309 [PACKED_SMI_ELEMENTS]
- length: 0
- properties: 0x357222481309
- All own properties (excluding elements): {
0x357222484909: [String] in ReadOnlySpace: #length: 0x0f4cc91c1189 (const accessor descriptor), location: descriptor
}
...
[]
_1 singkatan dari ukuran array internal lama ditambah jumlah item yang dimasukkan, maka dalam kasus kami sama dengan 1 dan
> const arr = [];
undefined
> %DebugPrint(arr);
DebugPrint: 0x3db6370d4e51: [JSArray]
- map: 0x3de594a433f9 [FastProperties]
- prototype: 0x3a5538d05849
- elements: 0x357222481309 [PACKED_SMI_ELEMENTS]
- length: 0
- properties: 0x357222481309
- All own properties (excluding elements): {
0x357222484909: [String] in ReadOnlySpace: #length: 0x0f4cc91c1189 (const accessor descriptor), location: descriptor
}
...
[]
2 dihitung sebagai 1 + 16 = 17

Ada satu lagi detail yang menarik pada keluaran di atas. Yaitu, teks

> const arr = [];
undefined
> %DebugPrint(arr);
DebugPrint: 0x3db6370d4e51: [JSArray]
- map: 0x3de594a433f9 [FastProperties]
- prototype: 0x3a5538d05849
- elements: 0x357222481309 [PACKED_SMI_ELEMENTS]
- length: 0
- properties: 0x357222481309
- All own properties (excluding elements): {
0x357222484909: [String] in ReadOnlySpace: #length: 0x0f4cc91c1189 (const accessor descriptor), location: descriptor
}
...
[]
3 dalam isi array memberitahu kita bahwa bagian yang tidak terpakai dari array internal diisi dengan "lubang". Lubang adalah nilai khusus yang digunakan oleh V8 untuk menandai item array yang tidak ditetapkan atau dihapus (dan bukan hanya itu). Ini adalah detail implementasi yang tidak pernah "bocor" ke dalam kode JS. Dalam contoh kita, V8 menggunakan lubang untuk menginisialisasi pecahan array yang tidak terpakai

Anda mungkin bertanya-tanya apakah array internal menyusut. Tampaknya menyusut pada operasi yang mengurangi panjang array seperti

> const arr = [];
undefined
> %DebugPrint(arr);
DebugPrint: 0x3db6370d4e51: [JSArray]
- map: 0x3de594a433f9 [FastProperties]
- prototype: 0x3a5538d05849
- elements: 0x357222481309 [PACKED_SMI_ELEMENTS]
- length: 0
- properties: 0x357222481309
- All own properties (excluding elements): {
0x357222484909: [String] in ReadOnlySpace: #length: 0x0f4cc91c1189 (const accessor descriptor), location: descriptor
}
...
[]
4 atau
> const arr = [];
undefined
> %DebugPrint(arr);
DebugPrint: 0x3db6370d4e51: [JSArray]
- map: 0x3de594a433f9 [FastProperties]
- prototype: 0x3a5538d05849
- elements: 0x357222481309 [PACKED_SMI_ELEMENTS]
- length: 0
- properties: 0x357222481309
- All own properties (excluding elements): {
0x357222484909: [String] in ReadOnlySpace: #length: 0x0f4cc91c1189 (const accessor descriptor), location: descriptor
}
...
[]
5. Ini terjadi jika lebih dari (dengan beberapa bantalan untuk larik kecil) tidak akan digunakan sebagai hasil operasi

Kembali ke sesi REPL kami, jenis PACKED_SMI_ELEMENTS dalam array kami tidak mengasumsikan lubang, tetapi jika kami mengubahnya dengan cara tertentu, jenis tersebut akan bertransisi menjadi yang kurang spesifik. Ayo lakukan

> arr[2] = 0;
> %DebugPrint(arr);
...
- elements: 0x0e61bd5e7501 [HOLEY_SMI_ELEMENTS]
- length: 3
...
- elements: 0x0e61bd5e7501 {
0: 42
1: 0x357222481669
2: 0
3-16: 0x357222481669
}

Di sini kita menugaskan item kedua dari larik, melewatkan item pertama yang berisi lubang. Akibatnya, jenis elemen array ditransisikan ke HOLEY_SMI_ELEMENTS. Jenis ini mengasumsikan bahwa array hanya berisi nilai Smis atau holey. Dalam hal kinerja, jenis elemen ini sedikit lebih lambat daripada yang dikemas karena V8 harus melakukan pemeriksaan nilai untuk melewati lubang saat mengulang array atau memodifikasinya

Kami tidak akan bereksperimen lebih jauh dengan jenis elemen lain yang didukung oleh array. Ini dibiarkan sebagai latihan untuk pembaca yang ingin tahu. Namun demikian, masuk akal untuk menyebutkan bahwa V8 mengoptimalkan array angka floating-point 64-bit. Jenis PACKED_DOUBLE_ELEMENTS dan HOLEY_DOUBLE_ELEMENTS menyimpan nomor di larik pendukung, menghindari penunjuk tumpukan untuk setiap nomor

Apa yang kami minati sebagai langkah selanjutnya adalah mengetahui apakah backing store yang digunakan untuk item array dapat berbeda dari array berukuran tetap. Mari kita lakukan satu percobaan lagi di sesi REPL kita

> arr[32 << 20] = 0;
> %DebugPrint(arr);
...
- elements: 0x10f6026db0d9 [DICTIONARY_ELEMENTS]
- length: 33554433
...
- elements: 0x10f6026db0d9 {
- max_number_key: 33554432
2: 0 (data, dict_index: 0, attrs: [WEC])
0: 42 (data, dict_index: 0, attrs: [WEC])
33554432: 0 (data, dict_index: 0, attrs: [WEC])
}
...

Apa yang baru saja terjadi? . Jika Anda tertarik dengan detail tambahan, tabel hash digunakan dengan penyelidikan kuadrat

Jenis elemen juga dialihkan ke DICTIONARY_ELEMENTS yang berarti jalur "lambat" untuk array JS. Dengan jenis ini, V8 bertujuan untuk mengurangi jejak memori untuk array jarang dengan banyak lubang, karena tabel hash hanya menyimpan elemen array non-lubang. Di sisi lain, operasi tabel hash lebih lambat daripada array karena kita perlu membayar biaya perhitungan kode hash, pencarian entri, dan pengulangan. Beberapa saat kemudian kami akan melakukan microbenchmarking untuk memahami biayanya

Jenis kamus digunakan untuk larik yang lebih besar dari ambang batas tertentu, jadi itulah mengapa larik kami dialihkan ke jenis ini setelah kami menekan. Dalam hal memori, ini berarti array JS yang dipanggang dengan array tidak dapat tumbuh melebihi ~268MB

Sedangkan untuk larik berbasis kamus, ukuran maksimumnya dibatasi oleh dan tidak boleh melebihi nilai maksimum bilangan bulat tak bertanda 32-bit (2³² — 1)

Besar. Sekarang, ketika kita memiliki pemahaman yang lebih baik tentang bagaimana V8 menangani array JS, mari lakukan pembandingan

Beberapa Tolok Ukur Konyol

Sebelum kita melangkah lebih jauh, saya perlu memperingatkan Anda bahwa tolok ukur mikro berikut ini benar-benar non-ilmiah, tolok ukur yang tidak adil, jadi ambillah dengan sebutir garam. Tolok ukur dilakukan pada mesin dev saya dengan CPU i5–8400H, Ubuntu 20. 04, dan Node. js v15. 11. 0

Pertama, mari kita coba memahami perbedaan antara jenis elemen yang berbeda dalam hal iterasi array. Pada tolok ukur pertama, kami mengulangi array angka dan cukup menghitung jumlah total elemennya. Hasilnya divisualisasikan di bawah ini

Hasil benchmark iterasi array

Di sini hasil untuk jenis kamus hampir tidak terlihat karena dua* urutan besarnya lebih kecil dari yang untuk jenis yang dikemas (catatan. sebenarnya bedanya disini satu urutan besarnya yang masih lumayan banyak). Untuk jenis berlubang, hanya 23% lebih lambat dari yang dikemas

Sekarang mari lakukan beberapa pengukuran untuk operasi mutasi dasar, seperti

> const arr = [];
undefined
> %DebugPrint(arr);
DebugPrint: 0x3db6370d4e51: [JSArray]
- map: 0x3de594a433f9 [FastProperties]
- prototype: 0x3a5538d05849
- elements: 0x357222481309 [PACKED_SMI_ELEMENTS]
- length: 0
- properties: 0x357222481309
- All own properties (excluding elements): {
0x357222484909: [String] in ReadOnlySpace: #length: 0x0f4cc91c1189 (const accessor descriptor), location: descriptor
}
...
[]
0dan
> const arr = [];
undefined
> %DebugPrint(arr);
DebugPrint: 0x3db6370d4e51: [JSArray]
- map: 0x3de594a433f9 [FastProperties]
- prototype: 0x3a5538d05849
- elements: 0x357222481309 [PACKED_SMI_ELEMENTS]
- length: 0
- properties: 0x357222481309
- All own properties (excluding elements): {
0x357222484909: [String] in ReadOnlySpace: #length: 0x0f4cc91c1189 (const accessor descriptor), location: descriptor
}
...
[]
4. Di tolok ukur kedua, kami mendorong 1K elemen ke dalam array, lalu memunculkan semuanya di setiap iterasi. Hasilnya di bawah ini

Dorong/munculkan hasil tolok ukur

Kali ini hasil jenis kamus bahkan tidak terlihat (dan, ya, saya buruk dalam visualisasi data) karena ~200 versus ~238K operasi per detik untuk jenis berbasis array

Menariknya, jika kita menonaktifkan JIT di V8 dengan flag

> const arr = [];
undefined
> %DebugPrint(arr);
DebugPrint: 0x3db6370d4e51: [JSArray]
- map: 0x3de594a433f9 [FastProperties]
- prototype: 0x3a5538d05849
- elements: 0x357222481309 [PACKED_SMI_ELEMENTS]
- length: 0
- properties: 0x357222481309
- All own properties (excluding elements): {
0x357222484909: [String] in ReadOnlySpace: #length: 0x0f4cc91c1189 (const accessor descriptor), location: descriptor
}
...
[]
9, hasilnya menjadi ~200 versus ~16K operasi per detik. Ini dengan jelas menunjukkan betapa bagusnya kompiler JIT V8 dalam mengoptimalkan loop untuk jenis berbasis array

Meskipun angka absolut tidak penting, hasil di atas mengilustrasikan bahwa aplikasi JS Anda harus menghindari berurusan dengan array berbasis kamus, kecuali jika Anda benar-benar harus

Saatnya untuk menyelesaikan dan membuat daftar temuan kita hari ini

Ringkasan

  • Setiap larik JS dikaitkan dengan jenis elemen, metadata yang dilacak oleh V8 untuk mengoptimalkan operasi larik. Jenis ini menjelaskan jenis elemen yang disimpan dalam array
  • Elemen array yang cukup kecil disimpan dalam array ukuran tetap internal. V8 mengalokasikan beberapa ruang ekstra dalam larik internal untuk mencapai waktu amortisasi konstan untuk
    > const arr = [];
    undefined
    > %DebugPrint(arr);
    DebugPrint: 0x3db6370d4e51: [JSArray]
    - map: 0x3de594a433f9 [FastProperties]
    - prototype: 0x3a5538d05849
    - elements: 0x357222481309 [PACKED_SMI_ELEMENTS]
    - length: 0
    - properties: 0x357222481309
    - All own properties (excluding elements): {
    0x357222484909: [String] in ReadOnlySpace: #length: 0x0f4cc91c1189 (const accessor descriptor), location: descriptor
    }
    ...
    []
    0 dan operasi serupa yang menumbuhkan larik. Ketika panjang array berkurang, array internal mungkin juga menyusut
  • Setelah array JS menjadi besar (ini juga termasuk array berlubang), V8 mulai menggunakan tabel hash untuk menyimpan elemen array. Array sekarang dikaitkan dengan jenis elemen kamus "lambat".
  • Untuk hot loop, jenis "lambat" mungkin lebih lambat beberapa urutan daripada jenis berbasis array
  • Kompiler JIT V8 bagus dalam mengoptimalkan loop untuk jenis berbasis array
  • Secara umum, saat menulis kode yang memanipulasi array besar di hot path, Anda harus membiarkan V8 menggunakan jenis elemen yang paling spesifik untuk array Anda

Terima kasih telah membaca postingan ini. Beri tahu saya jika Anda memiliki ide untuk posting selanjutnya dalam seri V8 Deep Dives. Umpan balik tentang ketidakkonsistenan atau asumsi yang salah juga sangat diharapkan

Apakah ukuran array tetap?

Larik adalah struktur/wadah/objek data yang menyimpan kumpulan elemen bertipe sama yang berukuran tetap secara berurutan. Ukuran/panjang array ditentukan pada saat pembuatan.

Apakah array JavaScript berukuran dinamis?

Larik JavaScript bersifat Dinamis yang artinya, panjang larik dapat diubah pada saat dijalankan (bila perlu). Panjang array dapat ditentukan setelah mendeklarasikan array seperti yang ditunjukkan pada contoh berikut.

Bisakah ukuran array berubah?

Anda tidak dapat mengubah ukuran larik setelah dibuat . Namun, Anda dapat mengubah jumlah elemen dalam ArrayList kapan pun Anda mau.

Bagaimana cara mengatur ukuran array tetap dalam JavaScript?

var my_arr = Array(7). isi(''); .