v8 implikasi kinerja JavaScript dari const, let, dan var?

90

Terlepas dari perbedaan fungsional, apakah menggunakan kata kunci baru 'let' dan 'const' memiliki dampak umum atau spesifik pada kinerja yang terkait dengan 'var'?

Setelah menjalankan program:

function timeit(f, N, S) {
    var start, timeTaken;
    var stats = {min: 1e50, max: 0, N: 0, sum: 0, sqsum: 0};
    var i;
    for (i = 0; i < S; ++i) {
        start = Date.now();
        f(N);
        timeTaken = Date.now() - start;

        stats.min = Math.min(timeTaken, stats.min);
        stats.max = Math.max(timeTaken, stats.max);
        stats.sum += timeTaken;
        stats.sqsum += timeTaken * timeTaken;
        stats.N++
    }

    var mean = stats.sum / stats.N;
    var sqmean = stats.sqsum / stats.N;

    return {min: stats.min, max: stats.max, mean: mean, spread: Math.sqrt(sqmean - mean * mean)};
}

var variable1 = 10;
var variable2 = 10;
var variable3 = 10;
var variable4 = 10;
var variable5 = 10;
var variable6 = 10;
var variable7 = 10;
var variable8 = 10;
var variable9 = 10;
var variable10 = 10;

function varAccess(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += variable1;
        sum += variable2;
        sum += variable3;
        sum += variable4;
        sum += variable5;
        sum += variable6;
        sum += variable7;
        sum += variable8;
        sum += variable9;
        sum += variable10;
    }
    return sum;
}

const constant1 = 10;
const constant2 = 10;
const constant3 = 10;
const constant4 = 10;
const constant5 = 10;
const constant6 = 10;
const constant7 = 10;
const constant8 = 10;
const constant9 = 10;
const constant10 = 10;

function constAccess(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += constant1;
        sum += constant2;
        sum += constant3;
        sum += constant4;
        sum += constant5;
        sum += constant6;
        sum += constant7;
        sum += constant8;
        sum += constant9;
        sum += constant10;
    }
    return sum;
}


function control(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
    }
    return sum;
}

console.log("ctl = " + JSON.stringify(timeit(control, 10000000, 50)));
console.log("con = " + JSON.stringify(timeit(constAccess, 10000000, 50)));
console.log("var = " + JSON.stringify(timeit(varAccess, 10000000, 50)));

.. Hasil saya adalah sebagai berikut:

ctl = {"min":101,"max":117,"mean":108.34,"spread":4.145407097016924}
con = {"min":107,"max":572,"mean":435.7,"spread":169.4998820058587}
var = {"min":103,"max":608,"mean":439.82,"spread":176.44417700791374}

Namun diskusi seperti yang disebutkan di sini tampaknya menunjukkan potensi nyata untuk perbedaan kinerja dalam skenario tertentu: https://esdiscuss.org/topic/performance-concern-with-let-const

sean2078
sumber
Saya pikir itu tergantung pada penggunaan, misalnya yang letdigunakan dalam lingkup blok harus lebih berkinerja daripada var, yang tidak memiliki lingkup blok, tetapi hanya lingkup fungsi.
adeneo
Jika saya boleh bertanya, mengapa @adeneo itu?
sean2078
1
@ sean2078 - jika Anda perlu mendeklarasikan variabel yang hanya hidup dalam lingkup blok, letakan melakukannya, dan kemudian sampah dikumpulkan, sementara var, yang merupakan cakupan fungsi, tidak akan bekerja dengan cara yang sama. Sekali lagi saya pikir, ini sangat spesifik untuk penggunaan, sehingga keduanya letdan const bisa lebih berkinerja, tetapi tidak selalu demikian.
adeneo
1
Saya bingung dengan bagaimana kode kutipan dimaksudkan untuk menunjukkan perbedaan antara vardan let: Tidak pernah digunakan letsama sekali.
TJ Crowder
1
Saat ini tidak - hanya const vs. var .. Awalnya bersumber dari gist.github.com/srikumarks/1431640 (kredit untuk srikumarks) namun permintaan dibuat untuk menarik kode yang dipertanyakan
sean2078

Jawaban:

123

TL; DR

Secara teori , versi loop ini yang tidak dioptimalkan:

for (let i = 0; i < 500; ++i) {
    doSomethingWith(i);
}

mungkin lebih lambat daripada versi yang tidak dioptimalkan dari loop yang sama dengan var:

for (var i = 0; i < 500; ++i) {
    doSomethingWith(i);
}

karena variabel yang berbeda i dibuat untuk setiap iterasi pengulangan dengan let, sedangkan hanya ada satu idenganvar .

Memperdebatkan hal itu adalah fakta bahwa varhoisted sehingga dinyatakan di luar loop sedangkan lethanya dideklarasikan dalam loop, yang mungkin menawarkan keuntungan pengoptimalan.

Dalam praktiknya , di tahun 2018 ini, mesin JavaScript modern melakukan introspeksi loop yang cukup untuk mengetahui kapan dapat mengoptimalkan perbedaan itu. (Bahkan sebelum itu, kemungkinan besar loop Anda telah melakukan pekerjaan yang cukup sehingga letoverhead terkait tambahan telah hilang. Tetapi sekarang Anda bahkan tidak perlu khawatir tentang itu.)

Berhati-hatilah dengan benchmark sintetis karena sangat mudah membuat kesalahan, dan memicu pengoptimalan mesin JavaScript dengan cara yang tidak dilakukan oleh kode asli (baik dan buruk). Namun, jika Anda menginginkan tolok ukur sintetis, berikut ini:

Dikatakan bahwa tidak ada perbedaan yang signifikan dalam pengujian sintetik pada V8 / Chrome atau SpiderMonkey / Firefox. (Pengujian berulang di kedua browser memiliki satu pemenang, atau yang lainnya menang, dan dalam kedua kasus berada dalam margin kesalahan.) Namun sekali lagi, ini adalah tolok ukur sintetis, bukan kode Anda. Khawatir tentang kinerja kode Anda ketika dan jika kode Anda memiliki masalah kinerja.

Sebagai masalah gaya, saya lebih suka letuntuk manfaat pelingkupan dan manfaat closure-in-loop jika saya menggunakan variabel loop dalam sebuah closure.

Detail

Perbedaan penting antara vardan letdalam sebuah forloop adalah perbedaan iyang dibuat untuk setiap iterasi; itu mengatasi masalah klasik "closures in loop":

Membuat EnvironmentRecord baru untuk setiap badan loop ( tautan spesifikasi ) adalah pekerjaan, dan pekerjaan membutuhkan waktu, itulah sebabnya secara teori letversinya lebih lambat daripadavar versinya.

Tetapi perbedaannya hanya penting jika Anda membuat fungsi (penutupan) di dalam loop yang menggunakan i, seperti yang saya lakukan dalam contoh cuplikan runnable di atas. Jika tidak, perbedaan tidak dapat diamati dan dapat dioptimalkan.

Di sini, di tahun 2018, sepertinya V8 (dan SpiderMonkey di Firefox) melakukan introspeksi yang cukup sehingga tidak ada biaya kinerja dalam loop yang tidak menggunakan letsemantik variabel per iterasi. Lihat tes ini .


Dalam beberapa kasus, constmungkin juga memberikan peluang untuk pengoptimalan ituvar tidak, terutama untuk variabel global.

Masalah dengan variabel global adalah global; kode apa pun di mana pun dapat mengaksesnya. Jadi jika Anda mendeklarasikan variabel varyang tidak pernah ingin Anda ubah (dan tidak pernah melakukan perubahan dalam kode Anda), mesin tidak dapat berasumsi bahwa variabel tersebut tidak akan pernah berubah sebagai hasil dari kode yang dimuat nanti atau serupa.

Dengan const, meskipun, Anda secara eksplisit mengatakan mesin yang nilai tidak dapat change¹. Jadi bebas melakukan pengoptimalan apa pun yang diinginkan, termasuk memancarkan literal alih-alih referensi variabel ke kode yang menggunakannya, mengetahui bahwa nilainya tidak dapat diubah.

¹ Ingatlah bahwa dengan objek, nilainya adalah referensi ke objek, bukan objek itu sendiri. Jadi dengan const o = {}, Anda bisa mengubah status object ( o.answer = 42), tetapi Anda tidak bisa omenunjuk ke objek baru (karena itu akan memerlukan perubahan referensi objek yang dikandungnya).


Saat menggunakan letatau constdalam varsituasi serupa lainnya , mereka cenderung tidak memiliki kinerja yang berbeda. Fungsi ini harus memiliki kinerja yang persis sama baik Anda menggunakan varatau let, misalnya:

function foo() {
    var i = 0;
    while (Math.random() < 0.5) {
        ++i;
    }
    return i;
}

Itu semua, tentu saja, tidak mungkin menjadi masalah dan sesuatu yang perlu dikhawatirkan hanya jika dan ketika ada masalah nyata untuk dipecahkan.

TJ Crowder
sumber
Terima kasih atas jawabannya - Saya setuju, jadi untuk saya sendiri telah menstandarkan penggunaan var untuk operasi perulangan seperti yang dicatat dalam contoh loop pertama Anda, dan let / const untuk semua deklarasi lainnya dengan asumsi bahwa perbedaan kinerja pada dasarnya tidak ada karena tes kinerja akan tampak untuk menunjukkan untuk saat ini. Mungkin nanti, pengoptimalan pada const akan ditambahkan. Artinya, kecuali orang lain dapat menunjukkan perbedaan yang terlihat melalui contoh kode.
sean2078
@ sean2078: Saya menggunakan letcontoh loop juga. Perbedaan kinerja tidak perlu dikhawatirkan dalam kasus 99,999%.
TJ Crowder
2
Mulai pertengahan 2018, versi dengan let dan var memiliki kecepatan yang sama di Chrome, jadi sekarang tidak ada perbedaan lagi.
Maksimal
1
@DanM .: Kabar baik, pengoptimalan tampaknya telah menyusul, setidaknya di V8 dan SpiderMonkey. :-)
TJ Crowder
1
Terima kasih. Cukup adil.
hipers
20

"LET" LEBIH BAIK DALAM DEKLARASI LOOP

Dengan tes sederhana (5 kali) di navigator seperti itu:

// WITH VAR
console.time("var-time")
for(var i = 0; i < 500000; i++){}
console.timeEnd("var-time")

Waktu rata-rata untuk mengeksekusi lebih dari 2,5 md

// WITH LET
console.time("let-time")
for(let i = 0; i < 500000; i++){}
console.timeEnd("let-time")

Waktu rata-rata untuk mengeksekusi lebih dari 1,5 md

Saya menemukan bahwa waktu loop dengan let lebih baik.

Amn
sumber
7
Menjalankan ini di Firefox 65.0, saya mendapatkan kecepatan rata-rata var=138.8msdan let=4ms. Itu bukan salah ketik, letsekarang lebih dari 30x kali lebih cepat
Katamari
7
Saya baru saja mengujinya di Node v12.5. Saya menemukan kecepatan rata-rata adalah var=2.6msdan let=1.0ms. Jadi biarkan Node sedikit lebih dari dua kali lebih cepat.
Kane Hooper
2
Hanya untuk membuat poin biasa bahwa pengujian kinerja sulit di hadapan pengoptimal: Saya pikir loop biarkan dioptimalkan sepenuhnya - biarkan hanya ada di dalam blok dan loop tidak memiliki efek samping dan V8 cukup pintar untuk mengetahuinya dapat hapus saja bloknya, lalu loop. deklarasi var diangkat sehingga tidak bisa mengetahui itu. Loop Anda karena saya mendapatkan 1ms / 0.4ms, namun jika untuk keduanya saya memiliki variabel j (var atau let) di luar loop yang juga bertambah, saya kemudian mendapatkan 1ms / 1.5ms. yaitu var loop tidak ada perubahan, biarkan loop sekarang membutuhkan waktu lebih lama.
Euan Smith
@KaneHooper - Jika Anda mendapat perbedaan lima kali lipat di Firefox, itu pasti badan loop kosong yang melakukannya. Loop nyata tidak memiliki badan kosong.
TJ Crowder
2
Waspadalah terhadap tolok ukur sintetis , dan khususnya yang memiliki loop dengan badan kosong. Jika Anda benar-benar melakukan sesuatu dalam perulangan, tolok ukur sintetis ini (yang, sekali lagi, waspadalah! :-)) menunjukkan bahwa tidak ada perbedaan yang signifikan. Saya juga menambahkan satu ke jawaban saya jadi itu di situs (tidak seperti tes jsPerf yang terus menghilang pada saya. :-)). Putaran berulang menunjukkan satu kemenangan, atau kemenangan lainnya. Pastinya tidak ada yang konklusif.
TJ Crowder
10

Jawaban TJ Crowder sangat bagus.

Berikut adalah tambahan dari: "Kapan saya akan mendapatkan hasil maksimal dari mengedit deklarasi var yang ada ke const?"

Saya telah menemukan bahwa peningkatan kinerja yang paling berkaitan dengan fungsi "diekspor".

Jadi, jika file A, B, R, dan Z memanggil fungsi "utilitas" di file U yang biasanya digunakan melalui aplikasi Anda, alihkan fungsi utilitas tersebut ke "const" dan referensi file induk ke konstanta dapat keluar beberapa peningkatan kinerja. Bagi saya, tampaknya itu tidak lebih cepat, tetapi konsumsi memori secara keseluruhan berkurang sekitar 1-3% untuk aplikasi saya yang sangat monolitik dengan Frankenstein. Yang mana jika Anda menghabiskan banyak uang di cloud atau server baremetal Anda, bisa menjadi alasan yang baik untuk menghabiskan 30 menit untuk menyisir dan memperbarui beberapa dari deklarasi var tersebut ke const.

Saya menyadari bahwa jika Anda membaca bagaimana const, var, dan membiarkan bekerja di bawah selimut, Anda mungkin sudah menyimpulkan hal di atas ... tetapi jika Anda "melirik" di atasnya: D.

Dari apa yang saya ingat tentang pembandingan pada node v8.12.0 ketika saya membuat pembaruan, aplikasi saya beralih dari konsumsi idle ~ 240MB RAM menjadi ~ 233MB RAM.

isaacdre
sumber
3

Jawaban TJ Crowder sangat bagus tetapi:

  1. 'mari' dibuat agar kode lebih mudah dibaca, tidak lebih kuat
  2. secara teori let akan lebih lambat dari var
  3. dengan latihan compiler tidak dapat menyelesaikan sepenuhnya (analisis statis) program yang belum selesai sehingga kadang-kadang akan melewatkan optimasi
  4. dalam kasus apa pun menggunakan 'let' akan membutuhkan lebih banyak CPU untuk introspeksi, bangku harus dimulai ketika google v8 mulai mengurai
  5. jika introspeksi gagal 'let' akan menekan pengumpul sampah V8, itu akan membutuhkan lebih banyak iterasi untuk membebaskan / digunakan kembali. itu juga akan mengkonsumsi lebih banyak RAM. bangku harus memperhitungkan poin-poin ini
  6. Google Closure akan mengubah let in var ...

Pengaruh celah kinerja antara var dan let dapat dilihat dalam program lengkap kehidupan nyata dan bukan pada satu loop dasar.

Bagaimanapun, untuk menggunakan let di mana Anda tidak perlu, membuat kode Anda kurang dapat dibaca.

Michael Valve
sumber
Karena minat- teori apa yang letlebih lambat dari var? Terutama mengingat konsensus dalam komentar pada jawaban di atas menunjukkan itu lebih cepat?
JamesTheAwesomeDude