Saya memiliki sekitar satu juta baris dalam javascript dan saya perlu menyimpan objek untuk metadata untuk setiap baris. Diberikan dua jenis objek yang berbeda berikut:

{0: {'e', 0, 'v': 'This is a value'}

Dan:

{0: '0This is a value'}

Apa perbedaan memori antara sejuta objek tipe pertama dan sejuta objek tipe kedua? Itu adalah:

[obj1, obj1, obj1, ...] // array of 1M
[obj2, obj2, obj2, ...] // array of 1M
0
user12282936 30 Oktober 2019, 21:22

1 menjawab

Jawaban Terbaik

Pengembang V8 di sini. Jawabannya masih "tergantung", karena mesin untuk bahasa dinamis cenderung beradaptasi dengan apa yang Anda lakukan, jadi testcase kecil kemungkinan besar tidak mewakili perilaku aplikasi nyata. Satu aturan praktis tingkat tinggi yang akan selalu berlaku: satu string membutuhkan lebih sedikit memori daripada objek yang membungkus string itu. Kurang berapa? Bergantung.

Yang mengatakan, saya dapat memberikan jawaban spesifik untuk contoh spesifik Anda. Untuk kode berikut:

const kCount = 1000000;
let a = new Array(kCount);
for (let i = 0; i < kCount; i++) {
  // Version 1 (comment out the one or the other):
  a[i] = {0: {'e': 0, 'v': 'This is a value'}};
  // Version 2:
  a[i] = {0: '0This is a value'};
}
gc();

Berjalan dengan --expose-gc --trace-gc, saya melihat:

Versi 1: 244,5 MB

Versi 2: 206.4 MB

(Hampir saat ini V8, x64, d8 shell. Inilah yang disarankan oleh @paulsm4 agar Anda dapat melakukannya sendiri di DevTools.)

Pembagiannya adalah sebagai berikut:

  • array itu sendiri akan membutuhkan 8 byte per entri
  • objek yang dibuat dari literal objek memiliki header 3 pointer dan ruang yang dialokasikan sebelumnya untuk 4 properti bernama (tidak digunakan di sini), total 7 * 8 = 56 byte
  • penyimpanan dukungannya untuk properti yang diindeks mengalokasikan ruang untuk 17 entri meskipun hanya satu yang akan digunakan, ditambah header yang 19 pointer = 152 byte
  • di versi 1 kami memiliki objek dalam yang mendeteksi bahwa dua (dan hanya dua) properti bernama diperlukan, sehingga dipangkas menjadi ukuran 5 (3 header, 2 untuk "e" dan "v") pointer = 40 byte
  • di versi 2 tidak ada objek dalam, hanya penunjuk ke string
  • literal string dideduplikasi, dan 0 disimpan sebagai "Smi" langsung di pointer, jadi keduanya tidak membutuhkan ruang ekstra.

Menyimpulkan:

Versi 1: 8+56+152+40 = 256 byte per objek

Versi 2: 8+56+152 = 216 byte per objek

Namun, hal-hal akan berubah secara dramatis jika tidak semua string sama, jika objek memiliki lebih banyak atau lebih sedikit properti bernama atau terindeks, jika mereka berasal dari konstruktor daripada literal, jika mereka tumbuh atau menyusut seiring berjalannya waktu hidup mereka, dan banyak faktor lainnya. Terus terang, saya tidak berpikir wawasan yang sangat berguna dapat diperoleh dari angka-angka ini (khususnya, meskipun mereka mungkin tampak sangat tidak efisien, mereka tidak mungkin terjadi dalam praktik dengan cara ini - saya yakin Anda tidak benar-benar menyimpan begitu banyak nol, dan membungkus data aktual menjadi objek {0: ...} properti tunggal juga tidak terlihat realistis).

Ayo lihat! Jika saya menghapus semua informasi yang jelas-jelas berlebihan dari tes kecil, dan pada saat yang sama memaksa pembuatan string baru dan unik untuk setiap entri, saya akan dibiarkan dengan loop ini untuk mengisi array:

for (let i = 0; i < kCount; i++) {
  a[i] = i.toString();
}

Yang hanya mengkonsumsi ~31 MB total. Lebih suka objek aktual untuk metadata?

function Metadata(e, v) {
  this.e = e;
  this.v = v;
}
for (let i = 0; i < kCount; i++) {
  a[i] = new Metadata(i, i.toString());
}

Sekarang kita berada di ~69 MB. Seperti yang Anda lihat: perubahan dramatis ;-)

Jadi untuk menentukan kebutuhan memori aplikasi Anda yang sebenarnya dan lengkap, dan alternatif implementasi apa pun untuknya, Anda harus mengukurnya sendiri.

2
jmrk 30 Oktober 2019, 19:48