v8 ผลกระทบของประสิทธิภาพ JavaScript ของ const, let และ var?


90

โดยไม่คำนึงถึงความแตกต่างของฟังก์ชันการใช้คำหลักใหม่ "let" และ "const" มีผลกระทบโดยทั่วไปหรือเฉพาะเจาะจงใด ๆ ต่อประสิทธิภาพเมื่อเทียบกับ "var"

หลังจากรันโปรแกรม:

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

.. ผลของฉันมีดังต่อไปนี้:

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}

อย่างไรก็ตามการอภิปรายตามที่ระบุไว้ในที่นี้ดูเหมือนจะบ่งบอกถึงศักยภาพที่แท้จริงสำหรับความแตกต่างของประสิทธิภาพภายใต้สถานการณ์บางอย่าง: https://esdiscuss.org/topic/performance-concern-with-let-const


ฉันคิดว่าขึ้นอยู่กับการใช้งานเช่นที่letใช้ในขอบเขตบล็อกควรมีประสิทธิภาพมากกว่าvarซึ่งไม่มีขอบเขตการบล็อก แต่มีเพียงขอบเขตฟังก์ชันเท่านั้น
adeneo

ถ้าฉันอาจถามว่าทำไมถึงเป็น @adeneo?
sean2078

1
@ sean2078 - หากคุณต้องการประกาศตัวแปรที่อาศัยอยู่ในขอบเขตบล็อกเท่านั้นให้letทำเช่นนั้นจากนั้นจะเก็บขยะในขณะvarที่ซึ่งเป็นฟังก์ชันที่กำหนดขอบเขตจะไม่จำเป็นต้องทำงานในลักษณะเดียวกัน อีกครั้งฉันคิดว่ามันเฉพาะเจาะจงสำหรับการใช้งานซึ่งทั้งสองอย่างletและconst สามารถมีประสิทธิภาพมากขึ้น แต่ก็ไม่ได้เป็นเช่นนั้นเสมอไป
adeneo

1
ฉันสับสนว่าโค้ดที่ยกมามีไว้เพื่อแสดงความแตกต่างระหว่างvarและlet: มันไม่เคยใช้letเลย
TJ Crowder

1
ปัจจุบันไม่ได้มีเพียงแค่ const เทียบกับ var เท่านั้นที่มาจากgist.github.com/srikumarks/1431640 (ให้เครดิต srikumarks) แต่มีการร้องขอให้ดึงรหัสเป็นคำถาม
sean2078

คำตอบ:


123

TL; ดร

ตามทฤษฎีแล้วเวอร์ชันที่ไม่ได้เพิ่มประสิทธิภาพของลูปนี้:

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

อาจช้ากว่ารุ่นที่ไม่ได้เพิ่มประสิทธิภาพของลูปเดียวกันด้วยvar:

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

เพราะแตกต่างกัน iตัวแปรถูกสร้างขึ้นสำหรับแต่ละซ้ำวงด้วยletในขณะที่มีเพียงหนึ่งด้วยivar

การโต้แย้งว่าเป็นความจริงที่varถูกยกขึ้นดังนั้นจึงมีการประกาศนอกลูปในขณะที่letมีการประกาศเฉพาะในลูปซึ่งอาจให้ประโยชน์ในการเพิ่มประสิทธิภาพ

ในทางปฏิบัติที่นี่ในปี 2018 เอ็นจิ้น JavaScript สมัยใหม่จะทำการไตร่ตรองอย่างเพียงพอเพื่อให้ทราบว่าเมื่อใดที่สามารถปรับความแตกต่างนั้นให้เหมาะสมได้ (ก่อนหน้านั้นโอกาสที่ลูปของคุณทำงานได้มากพอที่letจะล้างค่าโสหุ้ยที่เกี่ยวข้องเพิ่มเติมออกไป แต่ตอนนี้คุณไม่ต้องกังวลแล้ว)

ระวังเกณฑ์มาตรฐานสังเคราะห์เนื่องจากง่ายต่อการผิดพลาดและเรียกใช้เครื่องมือเพิ่มประสิทธิภาพของ JavaScript ในรูปแบบที่โค้ดจริงไม่มี (ทั้งทางดีและทางไม่ดี) อย่างไรก็ตามหากคุณต้องการเกณฑ์มาตรฐานสังเคราะห์นี่คือหนึ่ง:

กล่าวว่าไม่มีความแตกต่างอย่างมีนัยสำคัญในการทดสอบสังเคราะห์บน V8 / Chrome หรือ SpiderMonkey / Firefox (การทดสอบซ้ำ ๆ ในเบราว์เซอร์ทั้งสองจะมีการชนะครั้งเดียวหรืออีกครั้งหนึ่งชนะและในทั้งสองกรณีก็อยู่ในขอบเขตของข้อผิดพลาด) แต่อีกครั้งเป็นเกณฑ์มาตรฐานสังเคราะห์ไม่ใช่รหัสของคุณ กังวลเกี่ยวกับประสิทธิภาพของโค้ดของคุณเมื่อใดและหากโค้ดของคุณมีปัญหาด้านประสิทธิภาพ

ในletแง่ของสไตล์ฉันชอบเพื่อประโยชน์ในการกำหนดขอบเขตและประโยชน์ของการปิดในลูปหากฉันใช้ตัวแปรลูปในการปิด

รายละเอียด

ความแตกต่างที่สำคัญระหว่างvarและletในforลูปคือความแตกต่างiถูกสร้างขึ้นสำหรับการวนซ้ำแต่ละครั้ง เป็นการแก้ไขปัญหา "การปิดในลูป" แบบคลาสสิก:

การสร้าง EnvironmentRecord ใหม่สำหรับแต่ละ loop body ( ลิงก์ข้อมูลจำเพาะ ) เป็นการทำงานและการทำงานต้องใช้เวลาซึ่งในทางทฤษฎีletเวอร์ชันจึงช้ากว่าvarเวอร์ชัน

แต่ความแตกต่างจะมีความสำคัญก็ต่อเมื่อคุณสร้างฟังก์ชัน (การปิด) ภายในลูปที่ใช้iดังที่ฉันทำในตัวอย่างข้อมูลโค้ดที่รันได้ด้านบน มิฉะนั้นจะไม่สามารถสังเกตเห็นความแตกต่างและสามารถปรับให้เหมาะสมได้

ที่นี่ในปี 2018 ดูเหมือนว่า V8 (และ SpiderMonkey ใน Firefox) กำลังทำการไตร่ตรองอย่างเพียงพอว่าไม่มีต้นทุนด้านประสิทธิภาพในการวนซ้ำที่ไม่ได้ใช้ประโยชน์จากletความหมายตัวแปรต่อการวนซ้ำ ดูการทดสอบนี้


ในบางกรณีconstอาจให้โอกาสสำหรับการเพิ่มประสิทธิภาพที่varจะไม่เกิดขึ้นโดยเฉพาะอย่างยิ่งสำหรับตัวแปรส่วนกลาง

ปัญหาเกี่ยวกับตัวแปรส่วนกลางก็คือมันเป็นทั่วโลก รหัสใด ๆที่สามารถเข้าถึงได้ ดังนั้นหากคุณประกาศตัวแปรโดยvarที่คุณไม่เคยตั้งใจที่จะเปลี่ยนแปลง (และไม่เคยเปลี่ยนแปลงโค้ดของคุณ) เอ็นจิ้นจะไม่คิดว่าจะไม่มีการเปลี่ยนแปลงเนื่องจากโค้ดโหลดในภายหลังหรือใกล้เคียงกัน

ด้วยconstแม้ว่าคุณอย่างชัดเจนบอกเครื่องยนต์ว่าค่าที่ไม่สามารถchange¹ ดังนั้นจึงเป็นอิสระที่จะทำการเพิ่มประสิทธิภาพตามที่ต้องการรวมถึงการเปล่งตัวอักษรแทนการอ้างอิงตัวแปรไปยังโค้ดโดยใช้มันโดยรู้ว่าไม่สามารถเปลี่ยนแปลงค่าได้

¹โปรดจำไว้ว่าสำหรับวัตถุค่าจะอ้างอิงถึงวัตถุไม่ใช่ตัววัตถุ ด้วยเหตุconst o = {}นี้คุณสามารถเปลี่ยนสถานะของวัตถุ ( o.answer = 42) ได้ แต่คุณไม่สามารถoชี้ไปที่วัตถุใหม่ได้ (เพราะจะต้องมีการเปลี่ยนแปลงการอ้างอิงวัตถุที่มีอยู่)


เมื่อใช้งานletหรือconstในvarสถานการณ์อื่น ๆก็ไม่น่าจะมีประสิทธิภาพที่แตกต่างกัน ฟังก์ชันนี้ควรมีประสิทธิภาพเหมือนกันทุกประการไม่ว่าคุณจะใช้varหรือletตัวอย่างเช่น:

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

แน่นอนว่าทุกอย่างไม่น่าจะมีความสำคัญและมีบางสิ่งที่ต้องกังวลก็ต่อเมื่อมีปัญหาจริงที่ต้องแก้ไขเท่านั้น


ขอบคุณสำหรับคำตอบ - ฉันเห็นด้วยดังนั้นสำหรับตัวฉันเองได้กำหนดมาตรฐานในการใช้ var สำหรับการดำเนินการวนซ้ำตามที่ระบุไว้ในตัวอย่างแรกของคุณสำหรับลูปและ let / const สำหรับการประกาศอื่น ๆ ทั้งหมดโดยสมมติว่าความแตกต่างของประสิทธิภาพนั้นไม่มีอยู่จริงเนื่องจากการทดสอบประสิทธิภาพจะดูเหมือน เพื่อระบุตอนนี้ ในภายหลังอาจมีการเพิ่มการปรับให้เหมาะสมกับ const นั่นคือเว้นแต่จะมีคนอื่นแสดงความแตกต่างที่มองเห็นได้ผ่านตัวอย่างโค้ด
sean2078

@ sean2078: ฉันใช้letในตัวอย่างลูปด้วย ความแตกต่างของประสิทธิภาพนั้นไม่คุ้มค่าที่จะต้องกังวลในกรณี 99.999%
TJ Crowder

2
ตั้งแต่กลางปี ​​2018 เวอร์ชันที่ใช้ let และ var จะมีความเร็วเท่ากันใน Chrome ดังนั้นตอนนี้จึงไม่มีความแตกต่างอีกต่อไป
สูงสุด

1
@ DanM: ข่าวดีการเพิ่มประสิทธิภาพดูเหมือนจะเกิดขึ้นอย่างน้อยก็ใน V8 และ SpiderMonkey :-)
TJ Crowder

1
ขอบคุณ. พอใช้.
ไฮเปอร์

20

"LET" จะดีกว่าในการประกาศลูป

ด้วยการทดสอบอย่างง่าย (5 ครั้ง) ในเนวิเกเตอร์ดังนี้:

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

เวลาเฉลี่ยในการดำเนินการมากกว่า 2.5 มิลลิวินาที

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

เวลาเฉลี่ยในการดำเนินการมากกว่า 1.5 มิลลิวินาที

ฉันพบว่าเวลาลูปกับ let นั้นดีกว่า


7
ใช้สิ่งนี้ใน Firefox 65.0 ฉันได้รับความเร็วเฉลี่ยvar=138.8msและlet=4ms. นั่นไม่ใช่การพิมพ์ผิดletเร็วกว่า 30 เท่าในตอนนี้
Katamari

7
ฉันเพิ่งทดลองสิ่งนี้ในโหนด v12.5 ผมพบว่าความเร็วเฉลี่ยอยู่และvar=2.6ms let=1.0msดังนั้นให้ใน Node เร็วขึ้นกว่าสองเท่า
Kane Hooper

2
เพียงเพื่อให้จุดปกติที่การทดสอบประสิทธิภาพเป็นเรื่องยากต่อหน้าผู้เพิ่มประสิทธิภาพ: ฉันคิดว่าลูปปล่อยให้เหมาะสมทั้งหมด - ปล่อยให้มีอยู่ในบล็อกเท่านั้นและลูปไม่มีผลข้างเคียงและ V8 ฉลาดพอที่จะรู้ว่าทำได้ เพียงแค่ลบบล็อกแล้ววนซ้ำ การประกาศ var ถูกยกขึ้นดังนั้นจึงไม่สามารถทราบได้ ลูปของคุณตามที่ฉันได้รับ 1ms / 0.4ms แต่ถ้าสำหรับทั้งสองฉันมีตัวแปร j (var หรือ let) นอกลูปซึ่งเพิ่มขึ้นด้วยฉันก็จะได้ 1ms / 1.5ms เช่น var loop ไม่มีการเปลี่ยนแปลงปล่อยให้ลูปใช้เวลานานขึ้น
Euan Smith

@KaneHooper - หากคุณมีความแตกต่างห้าเท่าใน Firefox มันจะต้องเป็นตัววนซ้ำที่ว่างเปล่า ลูปที่แท้จริงไม่มีร่างกายว่างเปล่า
TJ Crowder

2
ระวังเกณฑ์มาตรฐานสังเคราะห์และโดยเฉพาะอย่างยิ่งกับลูปที่มีตัวว่างเปล่า หากคุณทำอะไรบางอย่างในลูปจริงเกณฑ์มาตรฐานสังเคราะห์นี้ (ซึ่งระวังอีกครั้ง! :-)) แสดงว่าไม่มีความแตกต่างอย่างมีนัยสำคัญ ฉันได้เพิ่มคำตอบหนึ่งไว้ในคำตอบของฉันดังนั้นจึงอยู่ในสถานที่ (ไม่เหมือนกับการทดสอบ jsPerf ที่ยังคงหายไปกับฉัน :-)) การวิ่งซ้ำ ๆ แสดงว่าหนึ่งชนะหรืออีกครั้งหนึ่งที่ชนะ ไม่มีข้อสรุปแน่นอน
TJ Crowder

10

คำตอบของTJ Crowderนั้นยอดเยี่ยมมาก

นี่คือส่วนเพิ่มเติมของ: "เมื่อใดที่ฉันจะได้รับประโยชน์สูงสุดจากการแก้ไขการประกาศ var ที่มีอยู่เป็น const"

ฉันพบว่าการเพิ่มประสิทธิภาพส่วนใหญ่เกี่ยวข้องกับฟังก์ชัน "ส่งออก"

ดังนั้นหากไฟล์ A, B, R และ Z เรียกใช้ฟังก์ชัน "ยูทิลิตี้" ในไฟล์ U ที่ใช้กันทั่วไปผ่านแอพของคุณจากนั้นเปลี่ยนฟังก์ชันยูทิลิตี้นั้นเป็น "const" และการอ้างอิงไฟล์พาเรนต์ไปยัง const สามารถทำได้ ประสิทธิภาพที่ดีขึ้น สำหรับฉันแล้วดูเหมือนว่ามันไม่ได้เร็วขึ้นอย่างวัดได้ แต่การใช้หน่วยความจำโดยรวมลดลงประมาณ 1-3% สำหรับแอป Frankenstein-ed ที่เป็นเสาหินโดยรวมของฉัน ซึ่งหากคุณใช้จ่ายเงินเป็นจำนวนมากบนคลาวด์หรือเซิร์ฟเวอร์ baremetal ของคุณอาจเป็นเหตุผลที่ดีที่จะใช้เวลา 30 นาทีในการรวบรวมและอัปเดตการประกาศ var บางส่วนเป็น const

ฉันตระหนักดีว่าถ้าคุณอ่านวิธี const, var และปล่อยให้ทำงานภายใต้การครอบคลุมคุณอาจสรุปข้างต้นแล้ว ... แต่ในกรณีที่คุณ "ชำเลืองมอง" มากกว่า: D

จากสิ่งที่ฉันจำได้ของการเปรียบเทียบบนโหนด v8.12.0 เมื่อฉันทำการอัปเดตแอปของฉันเปลี่ยนจากการใช้ RAM ประมาณ 240MB เป็น ~ 233MB RAM


3

คำตอบของ TJ Crowder นั้นดีมาก แต่:

  1. 'let' ถูกสร้างขึ้นเพื่อให้โค้ดอ่านง่ายขึ้นไม่ให้มีประสิทธิภาพมากขึ้น
  2. ตามทฤษฎีจะช้ากว่า var
  3. โดยการฝึกฝนคอมไพเลอร์ไม่สามารถแก้ปัญหาได้อย่างสมบูรณ์ (การวิเคราะห์แบบคงที่) โปรแกรมที่ยังไม่เสร็จสมบูรณ์ดังนั้นในบางครั้งมันจะพลาดการปรับให้เหมาะสม
  4. ไม่ว่าในกรณีใดการใช้ 'let' จะต้องใช้ CPU มากขึ้นสำหรับการวิปัสสนาต้องเริ่มบัลลังก์เมื่อ google v8 เริ่มแยกวิเคราะห์
  5. หากการวิปัสสนาล้มเหลว "ปล่อย" จะผลักดันเครื่องเก็บขยะ V8 อย่างหนักก็จะต้องมีการทำซ้ำมากขึ้นเพื่อให้ว่าง / ใช้ซ้ำ มันจะกินแรมมากขึ้นด้วย บัลลังก์ต้องคำนึงถึงประเด็นเหล่านี้
  6. Google Closure จะเปลี่ยนรูปแบบ ...

ผลกระทบของประสิทธิภาพการทำงานระหว่าง var และ let สามารถเห็นได้ในโปรแกรมที่สมบูรณ์ในชีวิตจริงไม่ใช่ในลูปพื้นฐานเดียว

อย่างไรก็ตามในการใช้ let ในที่ที่คุณไม่ต้องทำทำให้โค้ดของคุณอ่านได้น้อยลง


ไม่สนใจ - ทฤษฎีอะไรletช้ากว่าvar? โดยเฉพาะอย่างยิ่งเมื่อได้รับฉันทามติในความคิดเห็นเกี่ยวกับคำตอบข้างต้นแสดงว่าเร็วกว่า?
JamesTheAwesomeDude
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.