v8 Các hàm ý về hiệu suất JavaScript của const, let và var?


88

Bất kể sự khác biệt về chức năng, việc sử dụng các từ khóa mới 'let' và 'const' có bất kỳ tác động tổng quát hoặc cụ thể nào đến hiệu suất so với 'var' không?

Sau khi chạy chương trình:

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)));

.. Kết quả của tôi như sau:

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}

Tuy nhiên, thảo luận như đã lưu ý ở đây dường như chỉ ra tiềm năng thực sự về sự khác biệt về hiệu suất trong các tình huống nhất định: https://esdiscuss.org/topic/performance-concern-with-let-const


Tôi nghĩ rằng điều đó phụ thuộc vào cách sử dụng, ví dụ như letđược sử dụng trong phạm vi khối sẽ hiệu quả hơn var, không có phạm vi khối mà chỉ có phạm vi chức năng.
adeneo

Nếu tôi có thể hỏi, tại sao lại là @adeneo?
sean2078

1
@ sean2078 - nếu bạn cần khai báo một biến chỉ tồn tại trong phạm vi khối, letsẽ làm điều đó và sau đó được thu gom rác, trong khi var, thuộc phạm vi hàm, không cần thiết hoạt động theo cách tương tự. Một lần nữa tôi nghĩ, nó rất cụ thể cho cách sử dụng, cả hai letconst thể hiệu quả hơn, nhưng không phải lúc nào cũng vậy.
adeneo

1
Tôi bối rối bởi cách mã được trích dẫn có nghĩa là để chứng minh bất kỳ sự khác biệt nào giữa varlet: Nó không bao giờ sử dụng let.
TJ Crowder

1
Hiện nay là không - chỉ const vs var .. Nguyên có nguồn gốc từ gist.github.com/srikumarks/1431640 (tín dụng để srikumarks) tuy nhiên yêu cầu được thực hiện cho mã kéo vào câu hỏi
sean2078

Câu trả lời:


116

TL; DR

Về lý thuyết , một phiên bản chưa được tối ưu hóa của vòng lặp này:

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

có thể chậm hơn phiên bản chưa được tối ưu hóa của cùng một vòng lặp với var:

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

bởi vì một biến khác nhau i được tạo cho mỗi lần lặp vòng lặp với let, trong khi chỉ có một biến ivới var.

Lập luận chống lại điều đó là thực tế varlà nó được nâng lên nên nó được khai báo bên ngoài vòng lặp trong khi letchỉ được khai báo trong vòng lặp, điều này có thể mang lại lợi thế tối ưu hóa.

Trên thực tế , vào năm 2018, các công cụ JavaScript hiện đại đã xem xét kỹ lưỡng vòng lặp để biết khi nào nó có thể tối ưu hóa sự khác biệt đó. (Ngay cả trước đó, tỷ lệ cược là vòng lặp của bạn đã làm đủ công việc mà letdù sao thì chi phí liên quan đến bổ sung đã bị loại bỏ. Nhưng bây giờ bạn thậm chí không phải lo lắng về điều đó.)

Hãy coi chừng các điểm chuẩn tổng hợp vì chúng cực kỳ dễ sai và kích hoạt các trình tối ưu hóa công cụ JavaScript theo những cách mà mã thực không làm được (cả cách tốt và cách xấu). Tuy nhiên, nếu bạn muốn có một điểm chuẩn tổng hợp, thì đây là:

Nó nói rằng không có sự khác biệt đáng kể trong thử nghiệm tổng hợp đó trên V8 / Chrome hoặc SpiderMonkey / Firefox. (Các thử nghiệm lặp đi lặp lại trong cả hai trình duyệt đều có một chiến thắng hoặc trình duyệt khác chiến thắng và trong cả hai trường hợp đều nằm trong khoảng sai số.) Nhưng một lần nữa, đó là điểm chuẩn tổng hợp, không phải mã của bạn. Lo lắng về hiệu suất của mã của bạn khi và nếu mã của bạn có vấn đề về hiệu suất.

Về vấn đề phong cách, tôi thích letlợi ích phạm vi và lợi ích của vòng lặp đóng trong vòng lặp nếu tôi sử dụng biến vòng lặp trong một bao đóng.

Chi tiết

Sự khác biệt quan trọng giữa varlettrong một forvòng lặp là sự khác biệt iđược tạo ra cho mỗi lần lặp; nó giải quyết vấn đề "đóng trong vòng lặp" cổ điển:

Việc tạo EnvironmentRecord mới cho mỗi nội dung vòng lặp ( liên kết đặc điểm ) là công việc và công việc cần thời gian, đó là lý do tại sao về lý thuyết, letphiên bản này chậm hơn varphiên bản.

Nhưng sự khác biệt chỉ quan trọng nếu bạn tạo một hàm (đóng) trong vòng lặp sử dụng i, như tôi đã làm trong ví dụ về đoạn mã runnable ở trên. Nếu không, sự phân biệt không thể được quan sát và có thể được tối ưu hóa đi.

Ở đây vào năm 2018, có vẻ như V8 (và SpiderMonkey trong Firefox) đang xem xét đầy đủ để không có chi phí hiệu suất trong một vòng lặp không sử dụng letngữ nghĩa biến mỗi lần lặp lại. Xem thử nghiệm này .


Trong một số trường hợp, nó constcó thể tạo cơ hội cho việc tối ưu hóa mà varkhông, đặc biệt là đối với các biến toàn cục.

Vấn đề với một biến toàn cục là nó, tốt, toàn cục; bất kỳnào ở bất cứ đâu có thể truy cập nó. Vì vậy, nếu bạn khai báo một biến varmà bạn không bao giờ có ý định thay đổi (và không bao giờ thay đổi mã của bạn), thì engine không thể cho rằng nó sẽ không bao giờ thay đổi do mã được tải sau hoặc tương tự.

Với constMặc dù vậy, anh nói một cách rõ ràng động cơ rằng giá trị không thể change¹. Vì vậy, có thể tự do thực hiện bất kỳ tối ưu hóa nào nó muốn, bao gồm cả việc tạo ra một ký tự thay vì một tham chiếu biến cho mã bằng cách sử dụng nó, biết rằng các giá trị không thể thay đổi.

¹ Hãy nhớ rằng với các đối tượng, giá trị là một tham chiếu đến đối tượng, không phải bản thân đối tượng. Vì vậy, với const o = {}, bạn có thể thay đổi trạng thái của đối tượng ( o.answer = 42), nhưng bạn không thể otrỏ đến một đối tượng mới (vì điều đó sẽ yêu cầu thay đổi tham chiếu đối tượng mà nó chứa).


Khi sử dụng lethoặc consttrong các vartình huống tương tự, chúng không có khả năng có hiệu suất khác nhau. Hàm này phải có cùng một hiệu suất cho dù bạn sử dụng varhay let, ví dụ:

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

Tất nhiên, tất cả đều không thành vấn đề và chỉ cần lo lắng khi có một vấn đề thực sự cần giải quyết.


Cảm ơn câu trả lời - Tôi đồng ý, vì vậy bản thân tôi đã chuẩn hóa việc sử dụng các hoạt động lặp var cho vòng lặp như đã lưu ý trong ví dụ về vòng lặp for đầu tiên của bạn và let / const cho tất cả các khai báo khác giả định rằng sự khác biệt về hiệu suất về cơ bản không tồn tại như kiểm tra hiệu suất. để chỉ ra cho bây giờ. Có lẽ sau này, các tối ưu hóa trên const sẽ được thêm vào. Đó là, trừ khi người khác có thể cho thấy sự khác biệt rõ ràng thông qua ví dụ mã.
sean2078

@ sean2078: Tôi cũng sử dụng lettrong ví dụ về vòng lặp. Sự khác biệt về hiệu suất không đáng lo ngại về nó trong trường hợp 99,999%.
TJ Crowder

2
Kể từ giữa năm 2018, các phiên bản với let và var có cùng tốc độ trong Chrome, vì vậy bây giờ không có sự khác biệt nữa.
Tối đa

1
@DanM.: Tin tốt là việc tối ưu hóa dường như đã bắt kịp, ít nhất là trong V8 và SpiderMonkey. :-)
TJ Crowder

1
Cảm ơn. Đủ công bằng.
hypers

18

"HÃY" TỐT HƠN TRONG KHAI BÁO LOOP

Với một bài kiểm tra đơn giản (5 lần) trong Navigator như sau:

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

Thời gian trung bình để thực thi là hơn 2,5 mili giây

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

Thời gian trung bình để thực thi là hơn 1,5 mili giây

Tôi thấy rằng thời gian lặp với let tốt hơn.


6
Chạy điều này trong Firefox 65.0, tôi nhận được tốc độ trung bình của var=138.8mslet=4ms. Đó không phải là một lỗi đánh máy, letlà hơn 30x lần nhanh hơn ngay bây giờ
Katamari

6
Tôi vừa thử điều này trong Node v12.5. Tôi thấy tốc độ trung bình là var=2.6mslet=1.0ms. Vì vậy, let in Node nhanh hơn một chút.
Kane Hooper

2
Chỉ để làm cho điểm thông thường rằng kiểm tra hiệu suất là khó khăn khi có sự hiện diện của những người tối ưu: Tôi nghĩ rằng vòng lặp let đang được tối ưu hóa hoàn toàn - hãy để nó chỉ tồn tại bên trong khối và vòng lặp không có tác dụng phụ và V8 đủ thông minh để biết nó có thể chỉ cần loại bỏ khối, sau đó là vòng lặp. khai báo var được lưu trữ nên nó không thể biết điều đó. Các vòng lặp của bạn như vậy, tôi nhận được 1ms / 0,4ms, tuy nhiên nếu đối với cả hai, tôi có một biến j (var hoặc let) bên ngoài vòng lặp cũng được tăng lên, thì tôi nhận được 1ms / 1,5ms. tức là vòng lặp var không thay đổi, để vòng lặp bây giờ mất nhiều thời gian hơn.
Euan Smith

@KaneHooper - Nếu bạn nhận được sự khác biệt gấp năm lần trong Firefox, thì nó sẽ phải là phần thân vòng lặp trống đã làm được điều đó. Các vòng lặp thực không có phần thân trống.
TJ Crowder

Hãy cẩn thận với các điểm chuẩn tổng hợp và đặc biệt là các điểm chuẩn có vòng lặp với phần thân trống. Nếu bạn thực sự làm điều gì đó trong vòng lặp, điểm chuẩn tổng hợp này (một lần nữa, hãy cẩn thận! :-)) cho thấy không có sự khác biệt đáng kể. Tôi cũng đã thêm một câu trả lời vào câu trả lời của mình để nó có tại chỗ (không giống như những bài kiểm tra jsPerf đó cứ biến mất với tôi. :-)). Các lần chạy lặp đi lặp lại cho thấy một chiến thắng hoặc chiến thắng khác. Chắc chắn không có gì kết luận.
TJ Crowder

8

Câu trả lời của TJ Crowder quá xuất sắc.

Đây là một bổ sung của: "Khi nào tôi sẽ nhận được nhiều tiền nhất khi chỉnh sửa các khai báo var hiện có thành const?"

Tôi nhận thấy rằng hiệu suất tăng nhiều nhất phải làm với các chức năng "đã xuất".

Vì vậy, nếu tệp A, B, R và Z đang gọi hàm "tiện ích" trong tệp U thường được sử dụng thông qua ứng dụng của bạn, thì việc chuyển hàm tiện ích đó sang "const" và tham chiếu tệp mẹ thành const có thể eak ra một số hiệu suất được cải thiện. Đối với tôi, dường như nó không nhanh hơn đáng kể, nhưng mức tiêu thụ bộ nhớ tổng thể đã giảm khoảng 1-3% đối với ứng dụng Frankenstein-ed hoàn toàn nguyên khối của tôi. Mà nếu bạn đang chi tiêu nhiều tiền mặt trên đám mây hoặc máy chủ baremetal của mình, có thể là một lý do chính đáng để dành 30 phút để xem qua và cập nhật một số khai báo var đó thành const.

Tôi nhận ra rằng nếu bạn đọc hiểu về cách const, var và let hoạt động dưới vỏ bọc, bạn có thể đã kết luận ở trên ... nhưng trong trường hợp bạn "liếc" qua nó: D.

Từ những gì tôi nhớ về điểm chuẩn trên nút v8.12.0 khi tôi thực hiện cập nhật, ứng dụng của tôi đã chuyển từ mức tiêu thụ không tải của ~ 240MB RAM thành ~ 233MB RAM.


2

Câu trả lời của TJ Crowder rất hay nhưng:

  1. 'let' được tạo ra để làm cho mã dễ đọc hơn, không mạnh hơn
  2. theo lý thuyết let sẽ chậm hơn var
  3. bởi thực tế, trình biên dịch không thể giải quyết hoàn toàn (phân tích tĩnh) một chương trình chưa hoàn thiện nên đôi khi nó sẽ bỏ lỡ phần tối ưu hóa
  4. trong mọi trường hợp sử dụng 'let' sẽ yêu cầu nhiều CPU hơn để xem xét nội dung, phải bắt đầu dự bị khi google v8 bắt đầu phân tích cú pháp
  5. nếu phần nội quan không thành công 'let' sẽ đẩy mạnh trình thu gom rác V8, nó sẽ yêu cầu nhiều lần lặp hơn để giải phóng / tái sử dụng. nó cũng sẽ tiêu tốn nhiều RAM hơn. băng ghế dự bị phải tính đến những điểm này
  6. Google Closure sẽ biến đổi let in var ...

Tác động của khoảng cách hiệu suất giữa var và let có thể được nhìn thấy trong chương trình hoàn chỉnh ngoài đời thực chứ không phải trên một vòng lặp cơ bản.

Dù sao, để sử dụng let ở nơi bạn không cần phải làm, làm cho mã của bạn khó đọc hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.