เหตุใดฟังก์ชันหลังจึงเร็วกว่า 10% ถึงแม้ว่ามันจะต้องสร้างตัวแปรซ้ำแล้วซ้ำอีก?


14
var toSizeString = (function() {

 var KB = 1024.0,
     MB = 1024 * KB,
     GB = 1024 * MB;

  return function(size) {
    var gbSize = size / GB,
        gbMod  = size % GB,
        mbSize = gbMod / MB,
        mbMod  = gbMod % MB,
        kbSize = mbMod / KB;

    if (Math.floor(gbSize)) {
      return gbSize.toFixed(1) + 'GB';
    } else if (Math.floor(mbSize)) {
      return mbSize.toFixed(1) + 'MB';
    } else if (Math.floor(kbSize)) {
      return kbSize.toFixed(1) + 'KB';
    } else {
      return size + 'B';
    }
  };
})();

และฟังก์ชั่นที่เร็วขึ้น: (โปรดทราบว่ามันจะต้องคำนวณตัวแปร kb / mb / gb เดียวกันซ้ำแล้วซ้ำอีกเสมอ) ประสิทธิภาพเพิ่มขึ้นที่ไหน?

function toSizeString (size) {

 var KB = 1024.0,
     MB = 1024 * KB,
     GB = 1024 * MB;

 var gbSize = size / GB,
     gbMod  = size % GB,
     mbSize = gbMod / MB,
     mbMod  = gbMod % MB,
     kbSize = mbMod / KB;

 if (Math.floor(gbSize)) {
      return gbSize.toFixed(1) + 'GB';
 } else if (Math.floor(mbSize)) {
      return mbSize.toFixed(1) + 'MB';
 } else if (Math.floor(kbSize)) {
      return kbSize.toFixed(1) + 'KB';
 } else {
      return size + 'B';
 }
};

3
ในภาษาที่พิมพ์แบบคงที่ "ตัวแปร" จะถูกรวบรวมเป็นค่าคงที่ บางทีเครื่องยนต์ JS ที่ทันสมัยนั้นสามารถทำการเพิ่มประสิทธิภาพเดียวกันได้ ดูเหมือนว่าจะไม่ทำงานหากตัวแปรเป็นส่วนหนึ่งของการปิด
usr

6
นี่คือรายละเอียดการนำไปใช้ของเอ็นจิน JavaScript ที่คุณใช้ เวลาและพื้นที่ทางทฤษฎีนั้นเท่ากันมันเป็นเพียงการนำเครื่องมือจาวาสคริปต์มาใช้ซึ่งจะเปลี่ยนแปลงสิ่งเหล่านี้ ดังนั้นเพื่อตอบคำถามของคุณอย่างถูกต้องคุณต้องแสดงรายการเครื่องมือ JavaScript เฉพาะที่คุณวัดด้วย บางทีใครบางคนรู้รายละเอียดของการใช้งานเพื่อบอกว่า / ทำไมมันทำให้ดีกว่าอีกวิธีหนึ่ง นอกจากนี้คุณควรโพสต์รหัสการวัดของคุณ
Jimmy Hoffa

คุณใช้คำว่า "คำนวณ" ในการอ้างอิงถึงค่าคงที่; ไม่มีอะไรที่จะคำนวณได้ในสิ่งที่คุณอ้างอิง เลขคณิตของค่าคงที่เป็นหนึ่งในคอมไพเลอร์การปรับให้เหมาะสมที่ง่ายที่สุดและชัดเจนที่สุดดังนั้นเมื่อใดก็ตามที่คุณเห็นนิพจน์ที่มีค่าคงที่เพียงอย่างเดียว
Jimmy Hoffa

@JimmyHoffa นั้นเป็นจริง แต่ในทางกลับกันก็จำเป็นต้องสร้างตัวแปรคงที่ 3 ทุกการเรียกใช้ฟังก์ชั่น ...
Tomy

ค่าคงที่ @Tomy ไม่ใช่ตัวแปร พวกเขาไม่แตกต่างกันดังนั้นพวกเขาไม่จำเป็นต้องสร้างใหม่หลังจากการรวบรวม โดยทั่วไปค่าคงที่จะถูกวางไว้ในหน่วยความจำและการเข้าถึงในอนาคตสำหรับค่าคงที่นั้นจะถูกนำไปยังสถานที่เดียวกันแน่นอนไม่จำเป็นต้องสร้างขึ้นใหม่เพราะค่าของมันจะไม่แปรผันดังนั้นจึงไม่ใช่ตัวแปร โดยทั่วไปแล้วคอมไพเลอร์จะไม่ปล่อยโค้ดที่สร้างค่าคงที่คอมไพเลอร์ทำการสร้างและจะนำรหัสอ้างอิงทั้งหมดไปยังสิ่งที่ทำ
จิมมี่ฮอฟฟา

คำตอบ:


23

เอ็นจิ้น JavaScript สมัยใหม่ทั้งหมดทำการรวบรวมแบบทันเวลา คุณไม่สามารถทำการสันนิษฐานเกี่ยวกับสิ่งที่มัน "ต้องสร้างซ้ำแล้วซ้ำอีก" การคำนวณแบบนั้นค่อนข้างง่ายในการปรับให้เหมาะสมไม่ว่าในกรณีใด

ในทางกลับกันการปิดทับตัวแปรคงไม่ใช่กรณีทั่วไปที่คุณต้องการกำหนดเป้าหมายการรวบรวม JIT โดยทั่วไปคุณจะสร้างการปิดเมื่อคุณต้องการที่จะสามารถเปลี่ยนแปลงตัวแปรเหล่านั้นในการร้องขอที่แตกต่างกัน นอกจากนี้คุณยังสร้างตัวชี้ความสนใจเพิ่มเติมเพื่อเข้าถึงตัวแปรเหล่านั้นเช่นความแตกต่างระหว่างการเข้าถึงตัวแปรสมาชิกและ Local int ใน OOP

สถานการณ์เช่นนี้เป็นสาเหตุที่ทำให้ผู้คนออกจากสาย "การปรับให้เหมาะสมก่อนวัย" คอมไพเลอร์ได้ทำการปรับให้เหมาะสมแล้ว


ฉันสงสัยว่ามันเป็นขอบเขตการสำรวจเส้นทางสำหรับความละเอียดตัวแปรที่ก่อให้เกิดการสูญเสียตามที่คุณพูดถึง ดูเหมือนสมเหตุสมผล แต่ใครจะรู้ว่าบ้าจริง ๆ อยู่ในเครื่องมือ JavaScript JIT ...
Jimmy Hoffa

1
การขยายคำตอบที่เป็นไปได้: เหตุผลที่ JIT จะเพิกเฉยต่อการเพิ่มประสิทธิภาพที่ง่ายสำหรับคอมไพเลอร์ออฟไลน์เพราะประสิทธิภาพของคอมไพเลอร์ทั้งหมดมีความสำคัญมากกว่ากรณีผิดปกติ
Leushenko

12

ตัวแปรมีราคาถูก บริบทการดำเนินการและขอบเขตของขอบเขตมีราคาแพง

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

ดังนั้นในฟังก์ชั่นแรกของคุณเมื่อเอ็นจิ้นเห็นข้อความนี้:

var gbSize = size / GB;

จะต้องทำตามขั้นตอนต่อไปนี้:

  1. ค้นหาตัวแปรsizeในขอบเขตปัจจุบัน (พบมัน)
  2. ค้นหาตัวแปรGBในขอบเขตปัจจุบัน (ไม่พบ.)
  3. ค้นหาตัวแปรGBในขอบเขตพาเรนต์ (พบมัน)
  4. gbSizeทำคำนวณและกำหนดที่จะ

ขั้นตอนที่ 3 มีราคาแพงกว่าการจัดสรรตัวแปร นอกจากนี้คุณทำเช่นนี้ห้าครั้งรวมทั้งครั้งที่สองสำหรับทั้งสองและGB MBฉันสงสัยว่าถ้าคุณใช้นามแฝงเหล่านี้ที่จุดเริ่มต้นของฟังก์ชั่น (เช่นvar gb = GB) และอ้างอิงนามแฝงแทนจริง ๆ แล้วมันจะเร่งความเร็วขนาดเล็กถึงแม้ว่ามันอาจเป็นไปได้ว่าเอ็นจิน JS บางตัวทำการเพิ่มประสิทธิภาพนี้แล้ว และแน่นอนว่าวิธีที่มีประสิทธิภาพมากที่สุดในการเพิ่มความเร็วในการดำเนินการก็คือไม่ต้องข้ามห่วงโซ่ขอบเขตเลย

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

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


และแม้ว่า "การเพิ่มประสิทธิภาพ" ไม่ได้ส่งผลกระทบต่อผลการดำเนินงานในเชิงลบก็เกือบจะแน่นอนจะไปส่งผลกระทบต่อการอ่านรหัสในเชิงลบ ซึ่งหากคุณไม่ได้ทำสิ่งที่ใช้ในการคำนวณแบบบ้า ๆส่วนใหญ่มักจะเป็นการแลกเปลี่ยนที่ไม่ดีในการทำ (ดูเหมือนจะไม่มีความคิดเห็นที่ไม่เหมาะสม แต่น่าเสียดายที่ค้นหา "2009-02-17 11:41") ในขณะที่บทสรุปมีดังนี้: "เลือกความคมชัดมากกว่าความเร็วหากไม่จำเป็นต้องใช้ความเร็ว"
CVn

แม้ว่าการเขียนล่ามขั้นพื้นฐานมากสำหรับภาษาแบบไดนามิกการเข้าถึงตัวแปรระหว่างรันไทม์มีแนวโน้มที่จะเป็นการดำเนินการ O (1) และการข้ามผ่านขอบเขต O (n) ไม่จำเป็นแม้แต่ในระหว่างการรวบรวมครั้งแรก ในแต่ละขอบเขตตัวแปรแต่ละเพิ่งประกาศรับมอบหมายจำนวนที่กำหนดเพื่อให้var a, b, cเราสามารถเข้าถึงเป็นb scope[1]ขอบเขตทั้งหมดจะถูกกำหนดหมายเลขและหากขอบเขตนี้ซ้อนกันห้าขอบเขตที่ลึกขอบเขตนั้นbจะถูกระบุอย่างสมบูรณ์โดยenv[5][1]ที่รู้จักกันระหว่างการวิเคราะห์คำ ในโค้ดเนทีฟขอบเขตจะตรงกับเซ็กเมนต์สแต็ก การปิดทำการมีความซับซ้อนมากขึ้นเนื่องจากต้องสำรองและแทนที่env
amon

@amon: นั่นอาจจะเป็นวิธีที่คุณจะนึกคิดเช่นมันไปทำงาน แต่ก็ไม่ได้ว่ามันใช้งานได้จริง ผู้คนมีความรู้และประสบการณ์มากกว่าที่ฉันเขียนหนังสือเกี่ยวกับเรื่องนี้ โดยเฉพาะฉันจะชี้ให้คุณไปที่JavaScript ประสิทธิภาพสูงโดย Nicholas C. Zakas นี่คือตัวอย่างและเขายังได้พูดคุยกับมาตรฐานเพื่อสำรองข้อมูล แน่นอนว่าเขาไม่ใช่คนเดียว แต่เป็นที่รู้จักมากที่สุด JavaScript มีการกำหนดขอบเขตคำศัพท์ดังนั้นการปิดจริง ๆ จึงไม่พิเศษ - โดยพื้นฐานแล้วทุกอย่างเป็นการปิด
Aaronaught

@Aaraught ที่น่าสนใจ เนื่องจากหนังสือเล่มนั้นอายุ 5 ปีฉันสนใจว่าเครื่องยนต์ JS ปัจจุบันจัดการกับการค้นหาที่หลากหลายและดูแบ็กเอนด์ x64 ของเครื่องยนต์ V8 ได้อย่างไร ในระหว่างการวิเคราะห์แบบสแตติกตัวแปรส่วนใหญ่จะได้รับการแก้ไขแบบสแตติกและกำหนดออฟเซ็ตหน่วยความจำในขอบเขต ขอบเขตฟังก์ชันถูกแสดงเป็นรายการที่เชื่อมโยงและแอสเซมบลีถูกปล่อยออกมาเป็นลูปที่ไม่ได้ควบคุมเพื่อเข้าถึงขอบเขตที่ถูกต้อง ที่นี่เราจะได้รับเทียบเท่ากับรหัส C *(scope->outer + variable_offset)สำหรับการเข้าถึง; แต่ละระดับขอบเขตฟังก์ชั่นพิเศษมีค่าใช้จ่ายเพิ่มเติมหนึ่ง dereference ดูเหมือนว่าเราทั้งคู่ขวา :)
อมร

2

ตัวอย่างหนึ่งเกี่ยวข้องกับการปิดตัวอื่นไม่ได้ การนำการปิดมาใช้นั้นค่อนข้างยุ่งยากเนื่องจากการปิดทับตัวแปรไม่ทำงานเหมือนตัวแปรปกติ สิ่งนี้ชัดเจนมากขึ้นในภาษาระดับต่ำเช่น C แต่ฉันจะใช้ JavaScript เพื่ออธิบายสิ่งนี้

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

function add(vars, y) {
  vars.x += y;
}

function getSum(vars) {
  return vars.x;
}

function makeAdder(x) {
  return { x: x, add: add, getSum: getSum };
}

var adder = makeAdder(40);
adder.add(adder, 2);
console.log(adder.getSum(adder));  //=> 42

หมายเหตุเรียกประชุมที่น่าอึดอัดใจclosure.apply(closure, ...realArgs)นี้ต้อง

การสนับสนุนวัตถุในตัวของ JavaScript ทำให้สามารถละเว้นvarsอาร์กิวเมนต์ที่ชัดเจนและให้เราใช้thisแทน:

function add(y) {
  this.x += y;
}

function getSum() {
  return this.x;
}

function makeAdder(x) {
  return { x: x, add: add, getSum: getSum };
}

var adder = makeAdder(40);
adder.add(2);
console.log(adder.getSum());  //=> 42

ตัวอย่างเหล่านี้เทียบเท่ากับรหัสนี้โดยใช้การปิดจริง:

function makeAdder(x) {
  return {
    add: function (y) { x += y },
    getSum: function () { return x },
  };
}

var adder = makeAdder(40);
adder.add(2);
console.log(adder.getSum());  //=> 42

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

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

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