ทำไม arr = [] เร็วกว่า arr = new Array


146

ฉันรันรหัสนี้และได้ผลลัพธ์ด้านล่าง ฉันอยากรู้ว่าทำไม[]เร็วขึ้น

console.time('using[]')
for(var i=0; i<200000; i++){var arr = []};
console.timeEnd('using[]')

console.time('using new')
for(var i=0; i<200000; i++){var arr = new Array};
console.timeEnd('using new')
  • ใช้[]: 299ms
  • ใช้new: 363 มิลลิวินาที

ขอบคุณRaynosที่นี่เป็นเกณฑ์มาตรฐานของรหัสนี้และวิธีที่เป็นไปได้ในการกำหนดตัวแปร

ป้อนคำอธิบายรูปภาพที่นี่


5
คุณอาจจะสนใจในjsperf
Pointy


หมายเหตุคำหลักใหม่ ซึ่งหมายความว่า "โปรดมีประสิทธิภาพน้อยลง" มันไม่สมเหตุสมผลและต้องใช้เบราว์เซอร์ในการทำอินสแตนซ์ปกติแทนที่จะพยายามปรับให้เหมาะสม
beatgammit

2
@kinakuta ไม่มี พวกเขาทั้งสองสร้างวัตถุที่ไม่เท่ากันใหม่ ฉันหมายถึง[]มีค่าเท่ากันnew Array()ในแง่ของซอร์สโค้ดไม่ใช่วัตถุที่ส่งคืนการแสดงออกของรูปแบบ
Raynos

1
ใช่มันไม่สำคัญมาก แต่ฉันชอบที่จะรู้
Mohsen

คำตอบ:


195

ขยายเพิ่มเติมในคำตอบก่อนหน้า ...

จากมุมมองของคอมไพเลอร์ทั่วไปและไม่คำนึงถึงการปรับให้เหมาะสมเฉพาะกับ VM:

อันดับแรกเราต้องผ่านขั้นตอนการวิเคราะห์คำซึ่งเราทำเครื่องหมายโค้ด

โดยวิธีตัวอย่างโทเค็นต่อไปนี้อาจถูกสร้าง:

[]: ARRAY_INIT
[1]: ARRAY_INIT (NUMBER)
[1, foo]: ARRAY_INIT (NUMBER, IDENTIFIER)
new Array: NEW, IDENTIFIER
new Array(): NEW, IDENTIFIER, CALL
new Array(5): NEW, IDENTIFIER, CALL (NUMBER)
new Array(5,4): NEW, IDENTIFIER, CALL (NUMBER, NUMBER)
new Array(5, foo): NEW, IDENTIFIER, CALL (NUMBER, IDENTIFIER)

หวังว่าสิ่งนี้จะช่วยให้คุณมีการสร้างภาพข้อมูลที่เพียงพอเพื่อให้คุณสามารถเข้าใจว่าต้องมีการประมวลผลมากขึ้น (หรือน้อยลง)

  1. จากโทเค็นข้างต้นเรารู้ว่า ARRAY_INIT จะสร้างอาร์เรย์ขึ้นมาเสมอ เราเพียงแค่สร้างอาร์เรย์และเติมข้อมูล เท่าที่คลุมเครือขั้นตอนการวิเคราะห์คำศัพท์มีความโดดเด่นแล้ว ARRAY_INIT จากการเข้าถึงคุณสมบัติของวัตถุ (เช่นobj[foo]) หรือวงเล็บภายในสตริง / ตัวอักษร regex (เช่น "foo [] บาร์" หรือ / [] /)

  2. นี่คือ miniscule new Arrayแต่เรายังมีราชสกุลมากขึ้นด้วย นอกจากนี้ยังไม่ชัดเจนเลยว่าเราเพียงแค่ต้องการสร้างอาร์เรย์ เราเห็นโทเค็น "ใหม่" แต่ "ใหม่" คืออะไร จากนั้นเราจะเห็นโทเค็น IDENTIFIER ซึ่งหมายความว่าเราต้องการ "Array" ใหม่ แต่โดยทั่วไปแล้ว JavaScript ของ VM VM จะไม่แยกโทเค็น IDENTIFIER และโทเค็นสำหรับ "ออบเจ็กต์พื้นเมืองดั้งเดิม" ดังนั้น...

  3. เราต้องค้นหาห่วงโซ่ขอบเขตทุกครั้งที่เราพบโทเค็น IDENTIFIER Javascript VMs มี "วัตถุการเปิดใช้งาน" สำหรับแต่ละบริบทการดำเนินการซึ่งอาจมีวัตถุ "ข้อโต้แย้ง" ตัวแปรที่กำหนดไว้ในเครื่อง ฯลฯ หากเราไม่พบมันในวัตถุการเปิดใช้งานเราจะเริ่มค้นหาห่วงโซ่ขอบเขตจนกว่าจะถึงขอบเขตทั่วโลก . ReferenceErrorถ้าไม่มีอะไรจะพบเราโยน

  4. เมื่อเราพบการประกาศตัวแปรแล้วเราจะเรียกใช้ตัวสร้าง new Arrayเป็นการเรียกใช้ฟังก์ชันโดยปริยายและกฎของหัวแม่มือก็คือการเรียกใช้ฟังก์ชั่นจะช้าลงในระหว่างการดำเนินการ (ดังนั้นทำไมคอมไพเลอร์ C / C ++ คงที่อนุญาตให้ "ฟังก์ชั่น inlining" - เครื่องยนต์ JS JIT

  5. ตัวArrayสร้างมีการโอเวอร์โหลด ตัวสร้าง Array มีการใช้งานเป็นรหัสเนทีฟเพื่อให้มีการปรับปรุงประสิทธิภาพบางอย่าง แต่ยังคงต้องตรวจสอบความยาวอาร์กิวเมนต์และดำเนินการตามนั้น ยิ่งไปกว่านั้นในกรณีที่มีอาร์กิวเมนต์เพียงหนึ่งตัวเท่านั้นเราจำเป็นต้องตรวจสอบประเภทของอาร์กิวเมนต์เพิ่มเติม new Array ("foo") สร้าง ["foo"] โดยที่ new Array (1) สร้าง [undefined]

ดังนั้นเพื่อทำให้ง่ายขึ้นทั้งหมด: ด้วยตัวอักษรอาร์เรย์ VM รู้ว่าเราต้องการอาร์เรย์ ด้วยnew Array, VM ต้องใช้รอบ CPU เพิ่มเติมเพื่อหาว่าnew Array จริง ๆ แล้วอะไร


ไม่ใช่ a = new Array (1,000); สำหรับ (จาก 0 ถึง 999) {a [i] = i} เร็วกว่า a = []; สำหรับ (ตั้งแต่ 0 ถึง 999) {a [i] = i} เนื่องจาก การปันส่วนค่าใช้จ่าย
Y. Yoshii

เพิ่งทำกรณีทดสอบ ใหม่อาร์เรย์ (n) เร็วขึ้นในกรณีที่คุณรู้ขนาดของอาร์เรย์ล่วงหน้าเวลาjsperf.com/square-braces-vs-new-array
Y. Yoshii

27

เหตุผลหนึ่งที่เป็นไปได้คือnew Arrayต้องมีการค้นหาชื่อArray(คุณสามารถมีตัวแปรที่มีชื่ออยู่ในขอบเขต) ได้ในขณะที่[]ไม่มี


4
การตรวจสอบข้อโต้แย้งอาจมีส่วนร่วมเช่นกัน
Leonid

Arrayยกเว้นทั้งอาร์กิวเมนต์เดียวlenและหลายอาร์กิวเมนต์ โดยที่[]ยอมรับเฉพาะอาร์กิวเมนต์จำนวนมากเท่านั้น นอกจากนี้การทดสอบ Firefox แสดงให้เห็นว่าเกือบจะไม่แตกต่างกัน
Raynos

ฉันคิดว่ามีความจริงบางอย่างที่ การรันการทดสอบลูปของ OP ใน IIFE ทำให้(ค่อนข้าง)ส่งผลกระทบต่อประสิทธิภาพอย่างมากรวมถึงvar Array = window.Arrayปรับปรุงประสิทธิภาพของการnew Arrayทดสอบ
user113716

ฉันไม่คิดว่ามันถูกต้องเพราะ console.time ('เพิ่มเติม vars ใหม่'); สำหรับ (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('เพิ่ม vars ใหม่'); vars ใหม่เพิ่มเติม: 390ms และ console.time ('vars เพิ่มเติมใหม่'); var myOtherObject = {}, myOtherArray = []; สำหรับ (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('เพิ่ม vars ใหม่'); vars ใหม่เพิ่มเติม: 369ms กลับมาในเวลาเดียวกัน
Mohsen

2

คำถามที่ดี. ตัวอย่างแรกเรียกว่าตัวอักษรของอาร์เรย์ มันเป็นวิธีที่ต้องการสร้างอาร์เรย์ในหมู่นักพัฒนาหลายคน อาจเป็นไปได้ว่าความแตกต่างของประสิทธิภาพนั้นเกิดจากการตรวจสอบข้อโต้แย้งของการเรียก Array () ใหม่แล้วสร้างวัตถุในขณะที่ตัวอักษรสร้างอาร์เรย์โดยตรง

ความแตกต่างเล็กน้อยในการทำงานสนับสนุนจุดนี้ฉันคิดว่า คุณสามารถทำแบบทดสอบเดียวกันกับ Object และ object ตามตัวอักษร {} ได้


1

นี่จะทำให้รู้สึกบางอย่าง

ตัวอักษรออบเจ็กต์ทำให้เราสามารถเขียนโค้ดที่รองรับฟีเจอร์มากมาย แต่ก็ยังทำให้มันง่ายสำหรับผู้พัฒนาโค้ดของเรา ไม่จำเป็นต้องเรียกใช้งาน Constructor โดยตรงหรือรักษาลำดับของอาร์กิวเมนต์ที่ถูกต้องที่ส่งไปยังฟังก์ชัน ฯลฯ

http://www.dyn-web.com/tutorials/obj_lit.php


1

นอกจากนี้น่าสนใจหากทราบความยาวของอาเรย์ล่วงหน้า (องค์ประกอบจะถูกเพิ่มหลังจากการสร้าง) การใช้ตัวสร้างอาเรย์ที่มีความยาวที่ระบุจะเร็วกว่ามากใน Google Chrome 70+

  • " อาร์เรย์ใหม่ ( % ARR_LENGTH% ) " - 100% (เร็วกว่า) !

  • " [] " - 160-170% (ช้ากว่า)

แผนภูมิด้วยผลลัพธ์ของการวัด

การทดสอบสามารถพบได้ที่นี่ - https://jsperf.com/small-arr-init-with-known-length-brackets-vs-new-array/2

หมายเหตุ: ผลการทดสอบนี้ในGoogle Chrome v.70 + ; ในFirefox v.70และ IE ทั้งสองรุ่นมีค่าเท่ากัน

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