การใช้ฟังก์ชันที่ไม่ระบุชื่อมีผลต่อประสิทธิภาพหรือไม่?


89

ฉันสงสัยว่ามีความแตกต่างด้านประสิทธิภาพระหว่างการใช้ฟังก์ชันที่มีชื่อและฟังก์ชันที่ไม่ระบุชื่อใน Javascript หรือไม่

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = function() {
        // do something
    };
}

เทียบกับ

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

ข้อแรกเป็นระเบียบกว่าเนื่องจากไม่ทำให้โค้ดของคุณยุ่งเหยิงด้วยฟังก์ชันที่ไม่ค่อยได้ใช้งาน แต่คุณจะประกาศฟังก์ชันนั้นซ้ำหลายครั้งหรือไม่?


ฉันรู้ว่ามันไม่ได้อยู่ในคำถาม แต่เกี่ยวกับความสะอาดของรหัส / ความชัดเจนฉันคิดว่า 'วิธีที่ถูกต้อง' อยู่ตรงกลาง "ความยุ่งเหยิง" ของฟังก์ชันระดับบนสุดที่ไม่ค่อยได้ใช้นั้นเป็นสิ่งที่น่ารำคาญ แต่ก็เป็นโค้ดที่ซ้อนกันมากซึ่งขึ้นอยู่กับฟังก์ชันที่ไม่ระบุตัวตนซึ่งได้รับการประกาศในแนวเดียวกับการเรียกใช้ (คิดว่าการเรียกกลับ node.js) ทั้งก่อนหน้าและหลังสามารถทำให้การตรวจสอบจุดบกพร่อง / การดำเนินการติดตามยาก
Zac B

การทดสอบประสิทธิภาพด้านล่างเรียกใช้ฟังก์ชันสำหรับการทำซ้ำหลายพันครั้ง แม้ว่าคุณจะเห็นความแตกต่างอย่างมาก แต่กรณีการใช้งานส่วนใหญ่จะไม่ทำเช่นนี้ซ้ำในคำสั่งนั้น ดังนั้นจึงเป็นการดีกว่าที่จะเลือกสิ่งที่เหมาะกับความต้องการของคุณและไม่สนใจประสิทธิภาพสำหรับกรณีนี้
ผู้ใช้

@nickf แน่นอนว่าเป็นคำถามที่เก่าเกินไป แต่ดูคำตอบที่อัปเดตใหม่
Chandan Pasunoori

คำตอบ:


89

ปัญหาด้านประสิทธิภาพที่นี่คือต้นทุนในการสร้างอ็อบเจ็กต์ฟังก์ชันใหม่ในการวนซ้ำแต่ละครั้งไม่ใช่ข้อเท็จจริงที่ว่าคุณใช้ฟังก์ชันที่ไม่ระบุชื่อ:

for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = function() {
        // do something    
    };
}

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

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

หากคุณต้องสร้างฟังก์ชันที่ไม่ระบุตัวตนก่อนเข้าสู่ลูปให้กำหนดเฉพาะการอ้างอิงไปยังองค์ประกอบอาร์เรย์ในขณะที่อยู่ในลูปคุณจะพบว่าไม่มีความแตกต่างของประสิทธิภาพหรือความหมายใด ๆ เมื่อเทียบกับเวอร์ชันของฟังก์ชันที่มีชื่อ:

var handler = function() {
    // do something    
};
for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = handler;
}

กล่าวโดยย่อคือไม่มีต้นทุนประสิทธิภาพที่สังเกตได้สำหรับการใช้ฟังก์ชันที่ไม่ระบุชื่อ

นอกจากนี้อาจดูเหมือนจากด้านบนว่าไม่มีความแตกต่างระหว่าง:

function myEventHandler() { /* ... */ }

และ:

var myEventHandler = function() { /* ... */ }

อดีตคือการประกาศฟังก์ชันในขณะที่แบบหลังเป็นการกำหนดตัวแปรให้กับฟังก์ชันที่ไม่ระบุตัวตน แม้ว่าอาจดูเหมือนว่าจะมีผลเหมือนกัน แต่ JavaScript ก็ปฏิบัติแตกต่างกันเล็กน้อย เพื่อให้เข้าใจถึงความแตกต่างขอแนะนำให้อ่าน“ ความไม่ชัดเจนของการประกาศฟังก์ชัน JavaScript "

เวลาดำเนินการจริงสำหรับแนวทางใด ๆ ส่วนใหญ่จะถูกกำหนดโดยการใช้งานคอมไพเลอร์และรันไทม์ของเบราว์เซอร์ หากต้องการเปรียบเทียบประสิทธิภาพของเบราว์เซอร์สมัยใหม่ทั้งหมดโปรดไปที่เว็บไซต์ JS Perf


คุณลืมวงเล็บหน้าเนื้อหาฟังก์ชัน ฉันเพิ่งทดสอบมันจำเป็น
Chinoto Vokro

ดูเหมือนว่าผลลัพธ์มาตรฐานจะขึ้นอยู่กับ js-engine มาก!
aleclofabbro

3
ไม่มีข้อบกพร่องในตัวอย่าง JS Perf: กรณีที่ 1 กำหนดฟังก์ชันเท่านั้นในขณะที่กรณีที่ 2 และ 3 ดูเหมือนจะเรียกใช้ฟังก์ชันโดยไม่ได้ตั้งใจ
bluenote10

ดังนั้นการใช้เหตุผลนี้หมายความว่าเมื่อพัฒนาnode.jsเว็บแอปพลิเคชันควรสร้างฟังก์ชันนอกขั้นตอนการร้องขอและส่งผ่านเป็นการเรียกกลับแทนที่จะสร้างการโทรกลับแบบไม่ระบุตัวตนหรือไม่
Xavier T Mukodi

23

นี่คือรหัสทดสอบของฉัน:

var dummyVar;
function test1() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = myFunc;
    }
}

function test2() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = function() {
            var x = 0;
            x++;
        };
    }
}

function myFunc() {
    var x = 0;
    x++;
}

document.onclick = function() {
    var start = new Date();
    test1();
    var mid = new Date();
    test2();
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "\n Test 2: " + (end - mid));
}

ผลลัพธ์:
การทดสอบ 1: 142ms ทดสอบ 2: 1983ms

ดูเหมือนว่า JS engine ไม่รู้จักว่าเป็นฟังก์ชันเดียวกันใน Test2 และคอมไพล์ทุกครั้ง


3
การทดสอบนี้ดำเนินการในเบราว์เซอร์ใด
andynil

5
ครั้งสำหรับฉันใน Chrome 23: (2ms / 17ms), IE9: (20ms / 83ms), FF 17: (2ms / 96ms)
Davy8

คำตอบของคุณสมควรมีน้ำหนักมากกว่านี้ เวลาของฉันใน Intel i5 4570S: Chrome 41 (1/9), IE11 (1/25), FF36 (1/14) เห็นได้ชัดว่าฟังก์ชันนิรนามในลูปทำงานได้แย่ลง
ThisClark

3
การทดสอบนี้ไม่มีประโยชน์อย่างที่ปรากฏ ในทั้งสองตัวอย่างไม่มีการเรียกใช้ฟังก์ชันภายในจริง ผลการทดสอบทั้งหมดนี้แสดงให้เห็นว่าการสร้างฟังก์ชัน 10,000,000 ครั้งนั้นเร็วกว่าการสร้างฟังก์ชันเพียงครั้งเดียว
Nucleon

2

ตามหลักการออกแบบทั่วไปคุณควรหลีกเลี่ยงการใช้โค้ดเดียวกันหลายครั้ง แต่คุณควรยกโค้ดทั่วไปออกมาเป็นฟังก์ชันและเรียกใช้ฟังก์ชันนั้น (ทั่วไปผ่านการทดสอบอย่างดีและปรับเปลี่ยนได้ง่าย) จากที่ต่างๆ

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

เป็นคุณสมบัติที่มีประโยชน์มากในบางกรณี แต่ไม่ควรใช้ในหลาย ๆ สถานการณ์


1

ฉันจะไม่คาดหวังความแตกต่างมากนัก แต่ถ้ามีก็อาจจะแตกต่างกันไปตามกลไกการเขียนสคริปต์หรือเบราว์เซอร์

หากคุณพบว่าโค้ดง่ายต่อการสั่นประสิทธิภาพก็ไม่ใช่ปัญหาเว้นแต่คุณจะเรียกใช้ฟังก์ชันนี้เป็นล้าน ๆ ครั้ง


1

ในกรณีที่เราสามารถสร้างผลกระทบด้านประสิทธิภาพได้คือในการดำเนินการประกาศฟังก์ชัน นี่คือเกณฑ์มาตรฐานของการประกาศฟังก์ชันภายในบริบทของฟังก์ชันอื่นหรือภายนอก:

http://jsperf.com/function-context-benchmark

ใน Chrome การดำเนินการจะเร็วกว่าถ้าเราประกาศฟังก์ชันภายนอก แต่ใน Firefox จะตรงกันข้าม

ในตัวอย่างอื่น ๆ เราจะเห็นว่าหากฟังก์ชันภายในไม่ใช่ฟังก์ชันบริสุทธิ์ก็จะขาดประสิทธิภาพใน Firefox: http://jsperf.com/function-context-benchmark-3


0

สิ่งที่จะทำให้ลูปของคุณเร็วขึ้นอย่างแน่นอนในเบราว์เซอร์ต่างๆโดยเฉพาะเบราว์เซอร์ IE กำลังวนลูปดังนี้:

for (var i = 0, iLength = imgs.length; i < iLength; i++)
{
   // do something
}

คุณใส่ 1,000 ตามอำเภอใจในเงื่อนไขการวนซ้ำ แต่คุณจะได้รับการลอยตัวของฉันถ้าคุณต้องการดูรายการทั้งหมดในอาร์เรย์


0

การอ้างอิงมักจะช้าลงเมื่อเทียบกับสิ่งที่อ้างถึง ลองคิดแบบนี้สมมติว่าคุณต้องการพิมพ์ผลลัพธ์ของการเพิ่ม 1 + 1 ซึ่งเข้าท่ากว่า:

alert(1 + 1);

หรือ

a = 1;
b = 1;
alert(a + b);

ฉันรู้ว่านั่นเป็นวิธีที่ง่ายมากในการดู แต่มันเป็นภาพประกอบใช่ไหม? ใช้การอ้างอิงเฉพาะในกรณีที่ต้องใช้หลายครั้งตัวอย่างเช่นข้อใดที่เหมาะสมกว่า:

$(a.button1).click(function(){alert('you clicked ' + this);});
$(a.button2).click(function(){alert('you clicked ' + this);});

หรือ

function buttonClickHandler(){alert('you clicked ' + this);}
$(a.button1).click(buttonClickHandler);
$(a.button2).click(buttonClickHandler);

อย่างที่สองคือการฝึกฝนที่ดีกว่าแม้ว่าจะมีเส้นมากกว่า หวังว่าทั้งหมดนี้จะเป็นประโยชน์ (และไวยากรณ์ของ jquery ไม่ได้ทำให้ใครผิดหวัง)


0

@nickf

(หวังว่าฉันจะมีตัวแทนในการแสดงความคิดเห็น แต่ฉันเพิ่งพบไซต์นี้)

ประเด็นของฉันคือมีความสับสนระหว่างฟังก์ชันที่มีชื่อ / ไม่ระบุชื่อและกรณีการใช้งานของการเรียกใช้งาน + การรวบรวมในการทำซ้ำ ดังที่ฉันแสดงให้เห็นความแตกต่างระหว่าง anon + named นั้นเล็กน้อยในตัวมันเอง - ฉันกำลังบอกว่ามันเป็นกรณีการใช้งานที่ผิดพลาด

ดูเหมือนจะชัดเจนสำหรับฉัน แต่ถ้าไม่ฉันคิดว่าคำแนะนำที่ดีที่สุดคือ "อย่าทำอะไรโง่ ๆ " (ซึ่งการสร้างการเปลี่ยนบล็อกคงที่ + การสร้างวัตถุของกรณีการใช้งานนี้เป็นหนึ่ง) และถ้าคุณไม่แน่ใจให้ทดสอบ!


0

ใช่! ฟังก์ชันที่ไม่ระบุชื่อนั้นเร็วกว่าฟังก์ชันปกติ บางทีถ้าความเร็วมีความสำคัญสูงสุด ... สำคัญกว่าการใช้โค้ดซ้ำให้พิจารณาใช้ฟังก์ชันที่ไม่ระบุชื่อ

มีบทความที่ดีมากเกี่ยวกับการเพิ่มประสิทธิภาพจาวาสคริปต์และฟังก์ชันนิรนามที่นี่:

http://dev.opera.com/articles/view/efficient-javascript/?page=2


0

วัตถุที่ไม่ระบุชื่อเร็วกว่าวัตถุที่ระบุชื่อ แต่การเรียกใช้ฟังก์ชันเพิ่มเติมนั้นมีราคาแพงกว่าและในระดับที่บดบังการประหยัดที่คุณอาจได้รับจากการใช้ฟังก์ชันที่ไม่ระบุตัวตน แต่ละฟังก์ชั่นที่เรียกว่าจะเพิ่มไปยัง call stack ซึ่งจะแนะนำค่าใช้จ่ายจำนวนเล็กน้อย แต่ไม่สำคัญ

แต่ถ้าคุณไม่ได้เขียนกิจวัตรการเข้ารหัส / ถอดรหัสหรือบางสิ่งบางอย่างที่มีความอ่อนไหวต่อประสิทธิภาพในทำนองเดียวกันดังที่คนอื่น ๆ หลายคนตั้งข้อสังเกตว่าควรปรับแต่งโค้ดที่หรูหราและอ่านง่ายผ่านโค้ดที่รวดเร็ว

สมมติว่าคุณกำลังเขียนโค้ดที่ออกแบบมาอย่างดีปัญหาเรื่องความเร็วควรเป็นความรับผิดชอบของผู้ที่เขียนล่าม / คอมไพเลอร์


0

@nickf

นั่นเป็นการทดสอบที่ค่อนข้างร้ายแรงคุณกำลังเปรียบเทียบการดำเนินการและการรวบรวมเวลาในการนั่นซึ่งเห็นได้ชัดว่าจะเป็นวิธีต้นทุน 1 (รวบรวม N ครั้งเครื่องยนต์ JS ขึ้นอยู่กับวิธีที่ 2 (คอมไพล์ครั้งเดียว) ฉันนึกภาพไม่ออกว่านักพัฒนา JS คนไหนที่จะเขียนโค้ดการคุมประพฤติในลักษณะนี้

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

ใช้กรอบการทดสอบที่คล้ายกันกับของคุณ:


function test(m)
{
    for (var i = 0; i < 1000000; ++i) 
    {
        m();
    }
}

function named() {var x = 0; x++;}

var test1 = named;

var test2 = function() {var x = 0; x++;}

document.onclick = function() {
    var start = new Date();
    test(test1);
    var mid = new Date();
    test(test2);
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "ms\n Test 2: " + (end - mid) + "ms");
}

0

ตามที่ระบุไว้ในความคิดเห็นของคำตอบ @nickf: คำตอบ

การสร้างฟังก์ชันเร็วกว่าการสร้างเป็นล้านเท่า

ก็ใช่ แต่ในขณะที่ JS perf ของเขาแสดงให้เห็นว่ามันไม่ได้ช้าลงด้วยตัวคูณหนึ่งล้านแสดงให้เห็นว่ามันเร็วขึ้นเมื่อเวลาผ่านไป

คำถามที่น่าสนใจสำหรับฉันคือ:

ไม่ซ้ำวิธีสร้าง Run +เปรียบเทียบในการสร้างครั้งเดียว + ซ้ำวิ่ง

หากฟังก์ชันทำการคำนวณที่ซับซ้อนเวลาในการสร้างอ็อบเจกต์ของฟังก์ชันมีโอกาสน้อยมาก แต่สิ่งที่เกี่ยวกับส่วนหัวของการสร้างในกรณีที่การวิ่งเร็ว? ตัวอย่างเช่น:

// Variant 1: create once
function adder(a, b) {
  return a + b;
}
for (var i = 0; i < 100000; ++i) {
  var x = adder(412, 123);
}

// Variant 2: repeated creation via function statement
for (var i = 0; i < 100000; ++i) {
  function adder(a, b) {
    return a + b;
  }
  var x = adder(412, 123);
}

// Variant 3: repeated creation via function expression
for (var i = 0; i < 100000; ++i) {
  var x = (function(a, b) { return a + b; })(412, 123);
}

นี้JS Perfแสดงให้เห็นว่าการสร้างฟังก์ชั่นเพียงครั้งเดียวจะเร็วตามที่คาดไว้ อย่างไรก็ตามแม้จะมีการดำเนินการที่รวดเร็วมากเช่นการเพิ่มแบบธรรมดาค่าใช้จ่ายในการสร้างฟังก์ชันซ้ำ ๆ ก็มีเพียงไม่กี่เปอร์เซ็นต์เท่านั้น

ความแตกต่างอาจมีนัยสำคัญเฉพาะในกรณีที่การสร้างอ็อบเจ็กต์ฟังก์ชันมีความซับซ้อนในขณะที่ยังคงรักษาเวลาการทำงานที่ไม่สำคัญไว้เช่นหากส่วนของฟังก์ชันทั้งหมดถูกรวมเข้ากับif (unlikelyCondition) { ... }ไฟล์.

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