วงกลับเร็วขึ้นจริงๆหรือ


268

ฉันเคยได้ยินเรื่องนี้ค่อนข้างน้อย JavaScript มีการวนซ้ำเร็วขึ้นจริงหรือไม่เมื่อนับถอยหลัง ถ้าเป็นเช่นนั้นทำไม ฉันเห็นตัวอย่างชุดทดสอบสองสามตัวอย่างที่แสดงว่าลูปที่กลับด้านนั้นเร็วกว่า แต่ฉันไม่สามารถหาคำอธิบายได้ว่าทำไม!

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

กล่าวคือ

for (var i = count - 1; i >= 0; i--)
{
  // count is only evaluated once and then the comparison is always on 0.
}

6
ฮิฮิ. ที่จะใช้เวลาอย่างไม่มีกำหนด ลอง i--
StampedeXV

5
forวนย้อนหลังเร็วขึ้นเนื่องจากตัวแปรควบคุมการวนรอบบน (ฮิฮิ, ขอบล่าง) ไม่จำเป็นต้องกำหนดหรือดึงจากวัตถุ มันเป็นศูนย์คงที่
Josh Stodola

88
นอกจากนี้ไม่แตกต่างกันจริง สร้างห่วงพื้นเมืองมักจะไปได้อย่างรวดเร็วมาก ไม่ต้องกังวลกับการแสดงของพวกเขา
James Allardice

24
@Ashshin: สำหรับคำถามเช่นนี้โปรดแสดงบทความที่คุณอ้างอิงถึงเรา
การแข่งขัน Lightness ใน Orbit

22
มีความแตกต่างที่สำคัญส่วนใหญ่สำหรับอุปกรณ์ที่ใช้พลังงานต่ำและแบตเตอรี่ ความแตกต่างคือกับ i - คุณเปรียบเทียบกับ 0 สำหรับจุดสิ้นสุดของลูปในขณะที่ i ++ คุณเปรียบเทียบกับตัวเลข> 0 ฉันเชื่อว่าความแตกต่างของประสิทธิภาพการทำงานนั้นคล้ายกับ 20 nanoseconds (เช่น cmp axe, 0 กับ cmp axe , BX) - ซึ่งเป็นอะไร แต่ถ้าคุณพันห่วงครั้งต่อวินาทีทำไมไม่ได้ 20 นาโนวินาทีได้รับสำหรับแต่ละ :)
Luben

คำตอบ:


902

ไม่ใช่ว่าi--เร็วกว่าi++จะเร็วกว่าที่จริงแล้วทั้งคู่เร็วพอ ๆ กัน

สิ่งที่ต้องใช้เวลาในการขึ้นลูปคือการประเมินiขนาดของอาร์เรย์ของคุณ ในวงนี้:

for(var i = array.length; i--;)

คุณประเมิน.lengthเพียงครั้งเดียวเมื่อคุณประกาศiในขณะที่สำหรับวงนี้

for(var i = 1; i <= array.length; i++)

คุณประเมิน.lengthแต่ละครั้งที่คุณเพิ่มขึ้นiเมื่อคุณตรวจสอบว่าi <= array.lengthเมื่อคุณตรวจสอบว่า

ในกรณีส่วนใหญ่คุณไม่ควรกังวลเกี่ยวกับการเพิ่มประสิทธิภาพประเภทนี้


23
มันมีค่าที่จะแนะนำตัวแปรสำหรับ array.length และใช้ในส่วนหัวของ loop หรือไม่?
ragatskynet

39
@ragatskynet: ไม่ยกเว้นว่าคุณกำลังตั้งค่ามาตรฐานและต้องการสร้างจุด
Jon

29
@ragatskynet มันขึ้นอยู่กับ: มันจะเร็วกว่าที่จะประเมิน.lengthจำนวนครั้งหรือเพื่อประกาศและกำหนดตัวแปรใหม่หรือไม่? ในกรณีส่วนใหญ่นี่คือการเพิ่มประสิทธิภาพก่อนวัยอันควร (และผิด) เว้นแต่ว่าคุณlengthมีราคาแพงมากในการประเมิน
alestanis

10
@ Dr.Dredel: ไม่ใช่การเปรียบเทียบ - เป็นการประเมิน จะเร็วกว่าในการประเมิน0 array.lengthดีที่คาดคะเน
Lightness Races ในวงโคจร

22
สิ่งที่ควรค่าแก่การกล่าวถึงคือเรากำลังพูดถึงภาษาที่ตีความเช่น Ruby, Python ภาษาที่คอมไพล์แล้วเช่น Java มีการเพิ่มประสิทธิภาพในระดับคอมไพเลอร์ซึ่งจะ "ราบรื่น" ความแตกต่างเหล่านี้ไปยังจุดที่มันไม่สำคัญว่า.lengthจะอยู่ในการประกาศfor loopหรือไม่
Haris Krajina

198

ผู้ชายคนนี้เปรียบเทียบลูปจำนวนมากใน javascript ในเบราว์เซอร์จำนวนมาก เขายังมีชุดทดสอบเพื่อให้คุณสามารถรันด้วยตัวเอง

ในทุกกรณี (ยกเว้นกรณีที่ฉันไม่ได้อ่าน) การวนรอบที่เร็วที่สุดคือ:

var i = arr.length; //or 10
while(i--)
{
  //...
}

นิสัยดี :) แต่ไม่มีการย้อนกลับ "สำหรับ" - ทดสอบลูป ... แต่สำหรับลูปที่ถูกกล่าวถึงโดย peirix หรือ searlea ควรจะสวยมากเหมือนกับ "ในขณะที่" วนกับ "i--" ตามเงื่อนไข และนั่นคือการทดสอบลูปที่เร็วที่สุด
SvenFinke

11
ที่น่าสนใจ แต่ฉันสงสัยว่าการลดลงล่วงหน้าจะเร็วขึ้นหรือไม่ เนื่องจากไม่จำเป็นต้องเก็บค่ากลางของ i
tvanfosson

ถ้าฉันจำได้อย่างถูกต้องศาสตราจารย์หลักสูตรฮาร์ดแวร์ของฉันบอกว่าการทดสอบ 0 หรือไม่ 0 นั้นเป็นการ "คำนวณ" ที่เร็วที่สุด ในขณะที่ (i -) การทดสอบเป็นแบบทดสอบเสมอสำหรับ 0 บางทีนั่นอาจเป็นสาเหตุที่ทำให้นี่เร็วที่สุด?
StampedeXV

3
@tvanfosson ถ้าคุณล่วงหน้าลดลง--iแล้วคุณจะต้องใช้var current = arr[i-1];ภายในห่วงหรือมันจะปิดโดยหนึ่ง ...
DaveAlger

2
ฉันได้ยินมาว่า i- เร็วกว่า - i เพราะในกรณีที่สองตัวประมวลผลจำเป็นต้องลดค่าลงและทดสอบกับค่าใหม่ (มีการอ้างอิงข้อมูลระหว่างคำแนะนำ) ในขณะที่ในกรณีแรกตัวประมวลผลสามารถ ทดสอบค่าที่มีอยู่และลดค่าในภายหลัง ฉันไม่แน่ใจว่าสิ่งนี้ใช้กับ JavaScript หรือรหัส C ระดับต่ำมากเท่านั้น
marcus

128

ฉันพยายามที่จะให้ภาพกว้างกับคำตอบนี้

ความคิดในวงเล็บต่อไปนี้คือความเชื่อของฉันจนกระทั่งฉันเพิ่งทดสอบปัญหานี้เมื่อไม่นานมานี้:

[[ในแง่ของภาษาระดับต่ำเช่นC / C ++โค้ดจะถูกคอมไพล์เพื่อให้โปรเซสเซอร์มีคำสั่ง jump แบบมีเงื่อนไขพิเศษเมื่อตัวแปรเป็นศูนย์ (หรือไม่ใช่ศูนย์)
นอกจากนี้หากคุณสนใจเกี่ยวกับการเพิ่มประสิทธิภาพมากคุณสามารถไป++iแทนได้i++เพราะ++iเป็นคำสั่งของตัวประมวลผลเดียวโดยที่i++หมายความว่าj=i+1, i=j]]

การวนซ้ำเร็วมากสามารถทำได้โดยการคลี่ออก:

for(i=800000;i>0;--i)
    do_it(i);

มันอาจช้ากว่า

for(i=800000;i>0;i-=8)
{
    do_it(i); do_it(i-1); do_it(i-2); ... do_it(i-7);
}

แต่เหตุผลสำหรับสิ่งนี้อาจค่อนข้างซับซ้อน (เพียงแค่พูดถึงมีปัญหาของการประมวลผลคำสั่งประมวลผลล่วงหน้าและการจัดการแคชในเกม)

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

ดังนั้นใน JavaScript ฉันขอแนะนำให้ใช้สิ่งที่ต้องการ

array.forEach(function(i) {
    do_it(i);
});

นอกจากนี้ยังมีข้อผิดพลาดน้อยและเบราว์เซอร์มีโอกาสเพิ่มประสิทธิภาพรหัสของคุณ

[หมายเหตุ: ไม่เพียง แต่เบราว์เซอร์ แต่คุณก็มีพื้นที่สำหรับเพิ่มประสิทธิภาพได้อย่างง่ายดายเพียงแค่กำหนดforEachฟังก์ชันใหม่(เบราว์เซอร์พึ่งพา) เพื่อให้มันใช้กลอุบายที่ดีที่สุดล่าสุด! @AMK :) กล่าวว่าในกรณีพิเศษมันมีมูลค่าค่อนข้างใช้หรือarray.pop array.shiftหากคุณทำเช่นนั้นให้วางไว้หลังม่าน overkill สูงสุดคือการเพิ่มตัวเลือกในการforEachเลือกขั้นตอนวิธีการวนลูป.]

ยิ่งไปกว่านั้นสำหรับภาษาระดับต่ำแนวทางปฏิบัติที่ดีที่สุดคือการใช้ฟังก์ชั่นห้องสมุดอัจฉริยะสำหรับการดำเนินการที่ซับซ้อนและวนซ้ำหากเป็นไปได้

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

ฉันทำการตรวจสอบอีกเล็กน้อยและปรากฎว่าใน C / C ++ แม้สำหรับการดำเนินงาน 5e9 = (50,000x100,000) ไม่มีความแตกต่างระหว่างการขึ้นและลงหากการทดสอบทำกับค่าคงที่เช่น @alestanis พูด (บางครั้งผลลัพธ์ JsPerf ไม่สอดคล้องกัน แต่โดยมากแล้วพูดเหมือนกัน: คุณไม่สามารถสร้างความแตกต่างใหญ่ได้)
ดังนั้นจึง--iเป็นเรื่องที่ค่อนข้าง "หรูหรา" มันทำให้คุณดูเหมือนโปรแกรมเมอร์ที่ดีขึ้นเท่านั้น :)

ในทางตรงกันข้ามสำหรับการคลายในสถานการณ์ 5e9 นี้มันทำให้ฉันลดลงจาก 12 วินาทีเป็น 2.5 วินาทีเมื่อฉันไป 10 วินาทีและ 2.1 วินาทีเมื่อฉันอายุ 20 ปี มันไม่มีการเพิ่มประสิทธิภาพและการเพิ่มประสิทธิภาพได้ลดเวลาลงเล็กน้อย :) (การยกเลิกสามารถทำได้ในแบบของฉันด้านบนหรือการใช้i++แต่นั่นไม่ได้นำสิ่งต่าง ๆ ใน JavaScript)

ทั้งหมดในทั้งหมด:เก็บi--/ i++และ++i/ i++ความแตกต่างในการสัมภาษณ์งานติดarray.forEachหรือฟังก์ชั่นไลบรารีที่ซับซ้อนอื่น ๆ ;)


18
คำสำคัญคือ "สามารถ" การวนซ้ำที่ยังไม่ได้ควบคุมของคุณอาจช้ากว่าเดิม เมื่อปรับให้เหมาะสมให้ทำการวัดเสมอเพื่อให้คุณทราบว่าการเปลี่ยนแปลงของคุณมีผลกระทบอะไรบ้าง
jalf

1
@jalf จริงแน่นอน +1 ความยาวคลายที่แตกต่างกัน (> = 1) มีประสิทธิภาพแตกต่างกัน array.each(...)นั่นคือเหตุผลที่มีความสะดวกมากขึ้นในการออกจากงานนี้ไปยังห้องสมุดถ้าเป็นไปได้ไม่ต้องพูดถึงเบราว์เซอร์ที่ทำงานบนสถาปัตยกรรมที่แตกต่างกันดังนั้นมันอาจจะดีกว่าถ้าพวกเขาตัดสินใจว่าจะทำอย่างไร ฉันไม่คิดว่าพวกเขาจะลองและทดสอบกับวงวนซ้ำที่ไม่ได้วนลูป
บาร์นีย์

1
@BarnabasSzabolcs คำถามนี้มีไว้สำหรับ JavaScript โดยเฉพาะไม่ใช่ C หรือภาษาอื่น ๆ ใน JS มีคือความแตกต่างกันโปรดดูคำตอบของฉันด้านล่าง แม้ว่าจะไม่สามารถใช้ได้กับคำถาม แต่คำตอบที่ดี +1!
AMK

1
และคุณก็ไป - +1เพื่อรับGreat Answerตรา คำตอบที่ดีจริงๆ :)
Rohit Jain

1
APL / A ++ ไม่ใช่ภาษาใดภาษาหนึ่ง ผู้คนใช้สิ่งเหล่านี้เพื่อแสดงว่าพวกเขาไม่สนใจเฉพาะภาษา แต่สิ่งที่ภาษาเหล่านั้นแบ่งปัน
บาร์นีย์

33

i-- เร็วเท่า i++

รหัสด้านล่างนี้เร็วพอ ๆ กับคุณ แต่ใช้ตัวแปรพิเศษ:

var up = Things.length;
for (var i = 0; i < up; i++) {
    Things[i]
};

คำแนะนำคือการไม่ประเมินขนาดของอาร์เรย์ในแต่ละครั้ง สำหรับอาร์เรย์ขนาดใหญ่เราสามารถเห็นความเสื่อมประสิทธิภาพ


6
คุณผิดอย่างชัดเจนที่นี่ ตอนนี้การควบคุมลูปต้องการตัวแปรภายในเพิ่มเติม (i และสูงกว่า) และขึ้นอยู่กับเอ็นจิ้น JavaScript สิ่งนี้อาจ จำกัด โอกาสในการปรับโค้ดให้เหมาะสม JIT จะแปลลูปง่าย ๆ เป็น opcodes ของเครื่องจักรโดยตรง แต่ถ้าลูปมีตัวแปรมากเกินไป JIT จะไม่สามารถปรับให้เหมาะสมได้ดี โดยทั่วไปจะถูก จำกัด ด้วยสถาปัตยกรรมหรือการลงทะเบียน cpu ที่ JIT ใช้ การเริ่มต้นเพียงครั้งเดียวและลดลงเป็นเพียงวิธีแก้ปัญหาที่สะอาดที่สุดและนั่นคือสาเหตุที่แนะนำให้ใช้ทั่วทุกสถานที่
Pavel P

ตัวแปรเพิ่มเติมจะถูกกำจัดโดยเครื่องมือเพิ่มประสิทธิภาพของล่าม / คอมไพเลอร์ทั่วไป ประเด็นคือ 'เปรียบเทียบกับ 0' - ดูคำตอบของฉันสำหรับคำอธิบายเพิ่มเติม
H. -Dirk Schmitt

1
ดีกว่าที่จะใส่ 'up' ในวง:for (var i=0, up=Things.length; i<up; i++) {}
15dodo

22

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

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

คุณยังสามารถทำการทดสอบประสิทธิภาพบนลูปได้ด้วยการเปิดhttps://blogs.oracle.com/greimer/resource/loop-test.html(ไม่ทำงานหากจาวาสคริปต์ถูกบล็อกในเบราว์เซอร์เช่นNoScript )

แก้ไข:

มาตรฐานล่าสุดที่สร้างขึ้นโดย Milan Adamovsky สามารถดำเนินการได้ที่นี่สำหรับเบราว์เซอร์ที่แตกต่างกัน

สำหรับการทดสอบใน Firefox 17.0 บน Mac OS X 10.6ฉันได้รับการวนซ้ำต่อไปนี้:

var i, result = 0;
for (i = steps - 1; i; i--) {
  result += i;
}

ตามที่เร็วที่สุดโดย:

var result = 0;
for (var i = steps - 1; i >= 0; i--) {
  result += i;
}

ตัวอย่างเกณฑ์มาตรฐาน


5
โพสต์นั้นมีอายุ 4 ปีแล้ว เมื่อพิจารณาถึงความเร็ว (ha!) ที่มีการอัปเดตเครื่องมือ JavaScript คำแนะนำส่วนใหญ่น่าจะล้าสมัย - บทความไม่ได้กล่าวถึง Chrome และเป็นเครื่องมือ V8 JavaScript ที่เป็นประกาย
Yi Jiang

ใช่ฉันคิดถึงรายละเอียดนั้นขอบคุณที่ชี้ให้เห็น เหตุการณ์ในเวลานั้นมีความแตกต่างไม่มากระหว่าง --i หรือ i ++ ในการวนซ้ำสำหรับ
dreamcrash

19

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

a++ < length

รวบรวมจริง ๆ แล้วเป็น

a++
test (a-length)

ดังนั้นจึงใช้เวลานานในการประมวลผลเมื่อรวบรวม


15

คำตอบสั้น ๆ

สำหรับรหัสปกติโดยเฉพาะอย่างยิ่งในภาษาระดับสูงเช่นJavaScriptไม่มีความแตกต่างของประสิทธิภาพในi++และi--และ

เกณฑ์ประสิทธิภาพคือการใช้ในforลูปและการเปรียบเทียบคำสั่ง

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

คำอธิบายโดยละเอียด

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

การเปรียบเทียบนี้ออกให้กับทุกรอบของลูปและอาจส่งผลให้เกิดการปรับปรุงประสิทธิภาพที่วัดได้

for(var i = array.length; i--; )

จะถูกประเมินเป็นรหัสหลอกเช่นนี้

 i=array.length
 :LOOP_START
 decrement i
 if [ i = 0 ] goto :LOOP_END
 ... BODY_CODE
 :LOOP_END

โปรดทราบว่า0คือตัวอักษรหรือค่าคงที่

for(var i = 0 ; i < array.length; i++ )

จะถูกประเมินเป็นรหัสหลอกเช่นนี้ (ควรปรับการใช้ล่ามปกติ):

 end=array.length
 i=0
 :LOOP_START
 if [ i < end ] goto :LOOP_END
 increment i
 ... BODY_CODE
 :LOOP_END

โปรดทราบว่าท้ายเป็นตัวแปรที่ต้องลงทะเบียนCPU สิ่งนี้อาจเรียกใช้การแลกเปลี่ยนรีจิสเตอร์เพิ่มเติมในโค้ดและต้องการคำสั่งเปรียบเทียบที่มีราคาแพงกว่าในifคำสั่ง

แค่ 5 เซ็นต์ของฉัน

สำหรับภาษาระดับสูงความสามารถในการอ่านซึ่งอำนวยความสะดวกในการบำรุงรักษามีความสำคัญมากกว่าการปรับปรุงประสิทธิภาพเล็กน้อย

โดยปกติแล้วการวนซ้ำแบบดั้งเดิมตั้งแต่ต้นจนจบจะดีกว่า

การวนซ้ำที่เร็วขึ้นจากจุดสิ้นสุดอาเรย์เพื่อเริ่มผลลัพธ์ในลำดับที่ตรงกันข้ามที่ไม่ต้องการ

โพสต์สคริปต์

ตามที่ถามในความคิดเห็น: ความแตกต่าง--iและi--อยู่ในการประเมินของiก่อนหรือหลังการลดลง

คำอธิบายที่ดีที่สุดคือลอง ;-) นี่คือตัวอย่างของBash

 % i=10; echo "$((--i)) --> $i"
 9 --> 9
 % i=10; echo "$((i--)) --> $i"
 10 --> 9

1
1+ คำอธิบายที่ดี แค่คำถามเล็ก ๆ น้อย ๆ คุณช่วยอธิบายความแตกต่างระหว่าง--iและi--ด้วยได้หรือไม่?
Afshin Mehrabani

14

ฉันเห็นคำแนะนำเดียวกันใน Sublime Text 2

เช่นเดียวกับที่ได้กล่าวไปแล้วการปรับปรุงหลักไม่ได้ประเมินความยาวของอาร์เรย์ในการวนซ้ำแต่ละครั้งใน for for loop นี่เป็นเทคนิคการเพิ่มประสิทธิภาพที่รู้จักกันดีและมีประสิทธิภาพโดยเฉพาะอย่างยิ่งใน JavaScript เมื่ออาร์เรย์เป็นส่วนหนึ่งของเอกสาร HTML (ทำforเพื่อทั้งหมดliองค์ประกอบ )

ตัวอย่างเช่น,

for (var i = 0; i < document.getElementsByTagName('li').length; i++)

ช้ากว่ามาก

for (var i = 0, len = document.getElementsByTagName('li').length; i < len; i++)

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

แต่ถ้าคุณถามฉันจุดทั้งหมดไม่เกี่ยวกับi++vs i--optimization แต่ไม่ต้องประเมินความยาวของอาร์เรย์ในแต่ละการวนซ้ำ (คุณสามารถดูการทดสอบเกณฑ์มาตรฐานในjsperf )


1
ฉันต้องคำนึงถึงคำที่คำนวณที่นี่ ดูความคิดเห็นของฉันในคำตอบของพาเวล ข้อมูลจำเพาะ ECMA ระบุว่าจะไม่คำนวณความยาวของอาเรย์เมื่อคุณอ้างอิง
kojiro

'การประเมิน' จะเป็นตัวเลือกที่ดีกว่าหรือไม่ ข้อเท็จจริงที่น่าสนใจ btw ฉันไม่ทราบว่า
BBog

ตัวแปรเพิ่มเติมจะถูกกำจัดโดยเครื่องมือเพิ่มประสิทธิภาพของล่าม / คอมไพเลอร์ทั่วไป เช่นเดียวกับการประเมินผล array.length ประเด็นคือ 'เปรียบเทียบกับ 0' - ดูคำตอบของฉันสำหรับคำอธิบายเพิ่มเติม
H. -Dirk Schmitt

@ H. -DirkSchmitt คำตอบสามัญสำนึกของคุณกันเป็นเวลานานในประวัติศาสตร์ของ JavaScript คอมไพเลอร์ไม่ได้เพิ่มประสิทธิภาพค่าใช้จ่ายของห่วงโซ่การค้นหา AFAIK V8 เป็นคนแรกที่ลอง
kojiro

@ H. -DirkSchmitt เช่นเดียวกับ kojiro กล่าวว่านี่เป็นเคล็ดลับที่รู้จักกันดีและเป็นที่ยอมรับ แม้ว่าจะไม่เกี่ยวข้องกับเบราว์เซอร์รุ่นใหม่ แต่ก็ยังไม่ทำให้ล้าสมัย นอกจากนี้การทำมันด้วยการแนะนำตัวแปรใหม่สำหรับความยาวหรือด้วยเคล็ดลับในคำถามของ OP ก็ยังคงเป็นวิธีที่ดีที่สุด imo เป็นเพียงสิ่งที่ต้องทำและการฝึกฝนที่ดีฉันไม่คิดว่าจะมีอะไรเกี่ยวข้องกับความจริงที่ว่าคอมไพเลอร์ได้รับการปรับให้เหมาะกับการดูแลบางสิ่งที่มักจะทำในทางที่ไม่ดีใน JS
BBog

13

ฉันไม่คิดว่ามันสมเหตุสมผลที่จะพูดว่าi--มันเร็วกว่าi++ใน JavaScript

ก่อนอื่นมันทั้งหมดขึ้นอยู่กับการใช้งานเครื่องมือ JavaScript

ประการที่สองหากว่าการสร้าง JIT'ed และการแปลเป็นคำสั่งแบบดั้งเดิมนั้นง่ายที่สุดแล้วi++vs i--จะขึ้นอยู่กับ CPU ทั้งหมดที่เรียกใช้งาน นั่นคือบน ARMs (โทรศัพท์มือถือ) จะเร็วกว่าที่จะลงไปที่ 0 เนื่องจากการลดลงและเปรียบเทียบกับศูนย์จะถูกดำเนินการในคำสั่งเดียว

อาจเป็นไปได้ว่าคุณคิดว่าอันใดอันหนึ่งนั้นดีกว่าอันอื่นเพราะวิธีที่แนะนำคือ

for(var i = array.length; i--; )

แต่วิธีที่แนะนำไม่ใช่เพราะเร็วกว่าอย่างอื่น แต่เพียงเพราะถ้าคุณเขียน

for(var i = 0; i < array.length; i++)

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

อีกเหตุผลหนึ่งที่i--ทำให้การพิจารณาว่า "เร็วขึ้น" นั้นเป็นเพราะโปรแกรมจาวาสคริปต์จำเป็นต้องจัดสรรตัวแปรภายในหนึ่งตัวเพื่อควบคุมการวนซ้ำ (ตัวแปรให้กับvar i) หากคุณเปรียบเทียบกับ array.length หรือกับตัวแปรอื่น ๆ จะต้องมีตัวแปรภายในมากกว่าหนึ่งตัวในการควบคุมการวนซ้ำและจำนวนตัวแปรภายในเป็นสินทรัพย์ที่ จำกัด ของเอ็นจิ้น JavaScript มีการใช้ตัวแปรน้อยลงในลูปที่ JIT มีโอกาสมากขึ้นในการปรับให้เหมาะสม นั่นเป็นเหตุผลที่i--สามารถพิจารณาได้เร็วขึ้น ...


3
อาจเป็นการใช้ถ้อยคำที่รอบคอบเกี่ยวกับวิธีarray.lengthประเมินผล ความยาวจะไม่ถูกคำนวณเมื่อคุณอ้างถึง (เป็นเพียงคุณสมบัติที่ได้รับการตั้งค่าเมื่อใดก็ตามที่ดัชนีอาร์เรย์ถูกสร้างหรือเปลี่ยนแปลง ) เมื่อมีค่าใช้จ่ายเพิ่มเติมอาจเป็นเพราะเอ็นจิน JS ไม่ปรับเชนการค้นหาให้เหมาะสมสำหรับชื่อนั้น
kojiro

ไม่แน่ใจว่าสเป็คของ Ecma บอกไว้อย่างไร แต่การรู้เกี่ยวกับเครื่องมือ JavaScript ที่แตกต่างกันบางอย่างมันไม่ง่ายgetLength(){return m_length; }เลย แต่ถ้าคุณพยายามที่จะคิดไปข้างหลัง: ที่จะค่อนข้างแยบยลในการดำเนินการเขียนอาร์เรย์ที่ยาวจะต้องมีการคำนวณจริง :)
พาเวล P

ข้อมูลจำเพาะ ECMA ต้องการให้มีการคำนวณคุณสมบัติความยาวแล้ว lengthต้องได้รับการปรับปรุงทันทีเมื่อสถานที่ให้บริการที่เป็น-ใช้อาร์เรย์ดัชนีจะเพิ่มหรือเปลี่ยนแปลง
kojiro

สิ่งที่ฉันพยายามจะพูดคือมันยากที่จะละเมิดสเป็คถ้าคุณลองคิดดู
Pavel P

1
x86 นั้นเหมือนกับ ARM ในแง่นั้น dec/jnzกับinc eax / cmp eax, edx / jne.
Peter Cordes

13

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

ดังนั้นไปเลย:

คำตอบง่ายๆ: i--โดยทั่วไปจะเร็วกว่าเพราะไม่ต้องทำการเปรียบเทียบกับ 0 ในแต่ละครั้งที่รันผลลัพธ์การทดสอบของวิธีการต่างๆจะอยู่ด้านล่าง:

ผลการทดสอบ:ในฐานะที่เป็น "การพิสูจน์" โดยนี้ jsPerf, arr.pop()เป็นจริงห่วงเร็วที่สุดโดยไกล แต่มุ่งเน้นไปที่--i, i--, i++และ++iในขณะที่คุณถามในคำถามของคุณที่นี่ jsPerf (พวกเขามาจากหลาย jsPerf โปรดดูแหล่งที่มาด้านล่าง) ผลสรุป:

--iและi--จะเหมือนกันใน Firefox ในขณะที่i--เร็วกว่าใน Chrome

ใน Chrome พื้นฐานสำหรับลูป ( for (var i = 0; i < arr.length; i++)) นั้นเร็วกว่าi--และ--iใน Firefox จะช้ากว่า

ทั้งใน Chrome และ Firefox เป็นแคช arr.lengthจะเร็วขึ้นอย่างมากเมื่อใช้ Chrome ล่วงหน้าประมาณ 170,000 ops / วินาที

ไม่มีความแตกต่างที่สำคัญ++iคือเร็วกว่าi++ในเบราว์เซอร์ส่วนใหญ่ AFAIK มันจะไม่เหมือนเบราว์เซอร์ใด ๆ

สรุปสั้น ๆ : arr.pop()เป็นวงที่เร็วที่สุดโดยไกล สำหรับลูปที่ระบุเฉพาะi--คือลูปที่เร็วที่สุด

แหล่งที่มา: http://jsperf.com/fastest-array-loops-in-javascript/15 , http://jsperf.com/ipp-vs-ppi-2

ฉันหวังว่านี้ตอบคำถามของคุณ.


1
ดูเหมือนว่าpopการทดสอบของคุณจะดูเหมือนเร็วมากเพราะมันลดขนาดอาร์เรย์ลงเหลือ 0 สำหรับส่วนใหญ่ของลูป - เท่าที่ฉันจะบอกได้ อย่างไรก็ตามเพื่อให้แน่ใจว่าสิ่งต่าง ๆ เป็นธรรมฉันได้ออกแบบ jsperf นี้เพื่อสร้างอาร์เรย์ในแบบเดียวกันกับการทดสอบแต่ละครั้ง - ซึ่งดูเหมือนว่าจะแสดง.shift()ว่าเป็นผู้ชนะสำหรับเบราว์เซอร์ไม่กี่ตัวของฉัน - ไม่ใช่สิ่งที่ฉันคาดหวัง :) jsperf.com / compare-different-types-of-looping
Pebbl

โหวตขึ้นเป็นคนเดียวที่พูดถึง++i: D
jaggedsoft

12

ขึ้นอยู่กับตำแหน่งของอาเรย์ในหน่วยความจำและอัตราการเข้าชมของหน้าหน่วยความจำในขณะที่คุณเข้าถึงอาเรย์นั้น

ในบางกรณีการเข้าถึงสมาชิกอาร์เรย์ในลำดับคอลัมน์จะเร็วกว่าลำดับแถวเนื่องจากการเพิ่มขึ้นของอัตราส่วนการเข้าชม


1
ถ้าเพียง แต่ OP ได้ถามว่าทำไมภายในเมทริกซ์เดียวกันในการสั่งซื้อที่แตกต่างกันสามารถใช้จำนวนแตกต่างของเวลา ..
dcow

1
พิจารณาว่าในระบบปฏิบัติการที่การจัดการหน่วยความจำของพวกเขาจะขึ้นอยู่กับเพจเมื่อกระบวนการที่ต้องการข้อมูลที่ไม่ได้อยู่ในหน้าแคชเกิดขึ้นหน้าผิดพลาดในระบบปฏิบัติการและจะต้องนำหน้าเป้าหมายไปยังแคช CPU และแทนที่ด้วยหน้าอื่นดังนั้น ทำให้โอเวอร์เฮดในการประมวลผลที่ใช้เวลานานกว่าเมื่อเพจเป้าหมายอยู่ในแคชของ CPU สมมติว่าเรากำหนดอาร์เรย์ขนาดใหญ่ที่แต่ละแถวในนั้นมีขนาดใหญ่กว่าขนาดหน้า OS และเราเข้าถึงในลำดับแถวในกรณีนี้อัตราส่วนความผิดหน้าเพิ่มขึ้นและผลลัพธ์จะช้ากว่าการเข้าถึงลำดับคอลัมน์ไปยังอาร์เรย์นั้น
Akbar Bayati

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

10

ครั้งสุดท้ายที่ฉันกังวลเกี่ยวกับเรื่องนี้คือเมื่อเขียนชุดประกอบ6502 (8 บิตใช่!) กำไรขนาดใหญ่คือการดำเนินการทางคณิตศาสตร์ส่วนใหญ่ (โดยเฉพาะการลดลง) ปรับปรุงชุดของธงหนึ่งในนั้นคือZตัวบ่งชี้ 'ถึงศูนย์'

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


ในกรณีที่เห็นได้ชัดว่า JavaScript ใช้ไม่ได้ (เนื่องจากทำงานบน CPU ที่ไม่มีรหัส op-code ดังกล่าว) ส่วนใหญ่แล้วเหตุผลที่แท้จริงของi--vs i++คือในอดีตคุณไม่ได้แนะนำตัวแปรควบคุมเพิ่มเติมในขอบเขตของลูป ดูคำตอบของฉันด้านล่าง ...
Pavel P

1
ใช่มันใช้ไม่ได้จริง ๆ แต่มันเป็นสไตล์ C ทั่วไปและมันดูดีกว่าพวกเราที่ชินกับมัน :-)
Javier

x86 และ ARM นั้นมีทั้ง 6502 ในแง่นี้ dec/jnzแทนinc/cmp/jneสำหรับเคส x86 คุณจะไม่เห็นการวนซ้ำที่ว่างเปล่าทำงานได้เร็วขึ้น (ทั้งสองอย่างจะทำให้ปริมาณงานของสาขาอิ่มตัว) แต่การนับลงเล็กน้อยจะลดค่าใช้จ่ายของลูป prefetchers HW ของ HW ในปัจจุบันจะไม่ถูกรบกวนจากรูปแบบการเข้าถึงหน่วยความจำที่เรียงลำดับจากมากไปน้อย ซีพียูรุ่นเก่ากว่าฉันคิดว่าสามารถติดตามสตรีมแบบย้อนหลัง 4 ลำและสตรีมไปข้างหน้า 6 หรือ 10 ลำ IIRC
Peter Cordes

7

มันสามารถอธิบายได้โดย JavaScript (และทุกภาษา) ในที่สุดก็ถูกเปลี่ยนเป็น opcodes เพื่อให้ทำงานบน CPU ซีพียูมีคำสั่งเดียวเสมอสำหรับเปรียบเทียบกับศูนย์ซึ่งด่าอย่างรวดเร็ว

หากคุณสามารถรับประกันcountได้เสมอ>= 0คุณสามารถลดความซับซ้อนลงใน:

for (var i = count; i--;)
{
  // whatever
}

1
ซอร์สโค้ดที่สั้นกว่าไม่ได้แปลว่ามันจะเร็วขึ้น คุณวัดมันได้หรือไม่
เกร็ก Hewgill

อุ๊ปส์ฉันคิดถึงสิ่งนี้ เล็บบนหัวมีรอยแตก
Robert

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

7
@Brian Knoblauch: หากคุณใช้คำสั่งเช่น "dec eax" (x86 code) ดังนั้นคำสั่งนั้นจะตั้งค่าสถานะ Z (ศูนย์) โดยอัตโนมัติซึ่งคุณสามารถทดสอบได้ทันทีโดยไม่ต้องใช้คำสั่งเปรียบเทียบอื่นในระหว่างนั้น
เกร็ก Hewgill

1
เมื่อตีความ Javascript ฉันสงสัยว่า opcodes จะเป็นคอขวด มีแนวโน้มที่จะมีโทเค็นน้อยลงหมายความว่าล่ามสามารถประมวลผลซอร์สโค้ดได้เร็วขึ้น
ทอดด์โอเว่น

6

สิ่งนี้ไม่ได้ขึ้นอยู่กับ--หรือ++เครื่องหมาย แต่ขึ้นอยู่กับเงื่อนไขที่คุณใช้ในการวนซ้ำ

ตัวอย่างเช่น: การวนซ้ำของคุณเร็วขึ้นหากตัวแปรมีค่าคงที่มากกว่าการวนซ้ำตรวจสอบเงื่อนไขทุกครั้งเช่นความยาวของอาร์เรย์หรือเงื่อนไขอื่น ๆ

แต่ไม่ต้องกังวลเกี่ยวกับการเพิ่มประสิทธิภาพนี้เพราะเวลานี้ผลของมันจะถูกวัดเป็นนาโนวินาที


ลูปหลายนาโนวินาทีอาจกลายเป็นวินาที ... มันเป็นความคิดที่ไม่ดีเลยที่จะปรับให้เหมาะสมเมื่อคุณมีเวลาสำหรับมัน
# # # Buksy

6

for(var i = array.length; i--; )ไม่เร็วเท่าไหร่ แต่เมื่อคุณแทนที่array.lengthด้วยsuper_puper_function()ที่อาจจะมีนัยสำคัญได้เร็วขึ้น (ตั้งแต่มันเรียกว่าในทุก ๆ ซ้ำ) นั่นคือความแตกต่าง

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

PS: ไม่ได้เร็วกว่าi--i++


6

หากต้องการตัดสั้น: ไม่มีความแตกต่างในการทำเช่นนี้ใน JavaScript

ก่อนอื่นคุณสามารถทดสอบด้วยตัวเอง:

ไม่เพียง แต่คุณสามารถทดสอบและเรียกใช้สคริปต์ใด ๆ ในไลบรารี JavaScript ใด ๆ เท่านั้น แต่คุณยังสามารถเข้าถึงสคริปต์ที่เขียนก่อนหน้านี้ทั้งหมดรวมถึงความสามารถในการดูความแตกต่างระหว่างเวลาดำเนินการในเบราว์เซอร์ที่แตกต่างกันบนแพลตฟอร์มต่างๆ

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

หากคุณต้องการปรับปรุงประสิทธิภาพของสคริปต์คุณสามารถลองทำสิ่งต่อไปนี้

  1. มีvar a = array.length;คำสั่งเพื่อที่คุณจะไม่คำนวณค่าของมันในแต่ละครั้งในลูป
  2. อย่าวนซ้ำการเปิดใช้งานhttp://en.wikipedia.org/wiki/Loop_unwinding

แต่คุณต้องเข้าใจว่าการปรับปรุงที่คุณสามารถได้รับจะไม่มีความสำคัญมากนักซึ่งส่วนใหญ่คุณไม่ควรใส่ใจด้วย

ความเห็นของฉันเองว่าทำไมความเข้าใจผิด (Dec vs Inc) จึงปรากฏขึ้น

นานมาแล้วมีคำสั่งเครื่องทั่วไป DSZ (Decrement และ Skip on Zero) ผู้ที่ตั้งโปรแกรมในภาษาแอสเซมบลีใช้คำสั่งนี้เพื่อใช้ลูปเพื่อบันทึกการลงทะเบียน ตอนนี้ข้อเท็จจริงโบราณนี้ล้าสมัยแล้วและฉันค่อนข้างมั่นใจว่าคุณจะไม่ได้รับการพัฒนาประสิทธิภาพในภาษาใด ๆ โดยใช้การปรับปรุงหลอกนี้

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


ที่น่าสนใจ แต่สำหรับการทดสอบของคุณทำงานภายใต้ firefox 16.0.2 บน win7 การวนรอบการลดลงนั้นช้ากว่า 29% ... แก้ไข: ไม่สนใจสิ่งนั้น การทดสอบซ้ำ ๆ พิสูจน์แล้วว่าไม่สามารถสรุปได้มีจำนวนเสียงรบกวนที่น่าประหลาดใจในผลการทดสอบของฉันสำหรับการวิ่งครั้งต่อไป ไม่แน่ใจว่าทำไม

ใช่ฉันพยายามคิดเรื่องนั้นโดยปิดทุกอย่างลงและทดสอบ ยังคงได้ผลลัพธ์ที่ไร้มลทิน ที่แปลกมาก.

ฉันคิดว่าคุณพลาดจุดที่แท้จริงว่าทำไมการไปที่ศูนย์จึงถือว่าดีกว่าใน JavaScript ส่วนใหญ่เป็นเพราะวิธีนี้มีเพียงตัวแปรเดียวเท่านั้นที่ควบคุมการดำเนินการวนรอบเช่นวิธีนี้เครื่องมือเพิ่มประสิทธิภาพ / JITer มีพื้นที่เพิ่มขึ้นสำหรับการปรับปรุง การใช้array.lengthไม่จำเป็นต้องมีการปรับประสิทธิภาพเพียงเพราะว่าเครื่องเสมือน JS นั้นฉลาดพอที่จะรู้ได้ว่าอาร์เรย์ไม่ได้ถูกปรับเปลี่ยนโดยเนื้อความของลูป ดูคำตอบของฉันด้านล่าง
Pavel P

1
nitpicking: ความจริงโบราณนี้ (การเพิ่มประสิทธิภาพภาษาประกอบ) ไม่ล้าสมัยเพียงแค่ความลับ ในขณะที่คุณไม่จำเป็นต้องรู้จนกว่าคุณจะทำจริงๆ :-)
Javier

6

ฉันทำเปรียบเทียบ jsbench

ดังที่อะเลสตานิชี้ให้เห็นสิ่งหนึ่งที่ต้องใช้เวลาในการวนซ้ำมากขึ้นคือการประเมินขนาดของอาเรย์แต่ละครั้ง ในวงนี้:

for ( var i = 1; i <= array.length; i++ )

คุณประเมินแต่ละครั้งที่คุณเพิ่มขึ้น.length iในอันนี้:

for ( var i = 1, l = array.length; i <= l; i++ )

คุณประเมินเพียงครั้งเดียวเมื่อคุณประกาศ.length iในอันนี้:

for ( var i = array.length; i--; )

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

วนซ้ำด้วยฟังก์ชัน call to (กำหนดไว้ที่อื่น):

for (i = values.length; i-- ;) {
  add( values[i] );
}

วนซ้ำด้วยโค้ดอินไลน์:

var sum = 0;
for ( i = values.length; i-- ;) {
  sum += values[i];
}

หากคุณสามารถอินไลน์โค้ดของคุณแทนที่จะเรียกฟังก์ชั่นโดยไม่ต้องเสียสละความชัดเจนคุณสามารถมีลำดับความสำคัญได้เร็วขึ้น!


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

  1. คอขวดอาจอยู่ที่อื่น (อาแจ็กซ์ reflow ... )
  2. คุณอาจเลือกอัลกอริทึมที่ดีกว่า
  3. คุณอาจเลือกโครงสร้างข้อมูลที่ดีกว่า

แต่จำไว้:

รหัสถูกเขียนขึ้นเพื่อให้ผู้คนอ่านและบังเอิญให้เครื่องทำงานเท่านั้น


+1 สำหรับคำตอบนี้และสำหรับมาตรฐาน ฉันได้เพิ่มการทดสอบสำหรับแต่ละคนและทำมาตรฐานใหม่ให้กับไฟล์แบบสแตนด์อโลนที่รันได้ในเบราว์เซอร์และในโหนด jsfiddle.net/hta69may/2 สำหรับโหนด "Reverse loop การเปรียบเทียบโดยนัยโค้ดแบบอินไลน์" นั้นเร็วที่สุด แต่การทดสอบใน FF 50 แสดงผลลัพธ์ที่น่าประหลาดใจ: ไม่เพียง แต่การกำหนดเวลาเกือบจะน้อยกว่า 10 เท่า (!) แต่การทดสอบ "forEach" ทั้งสองแบบนั้นเร็วเท่ากับ "reverse loop" บางทีพวกโหนดควรใช้โปรแกรม JS ของ Mozilla แทน V8? :)
Fr0sT

4

วิธีที่คุณทำตอนนี้ไม่เร็ว (นอกเหนือจากการเป็นวงแบบไม่ จำกัด ฉันเดาว่าคุณตั้งใจจะทำ i--(นอกเหนือจากมันเป็นห่วงแน่นอนผมคิดว่าคุณหมายถึงการทำ

หากคุณต้องการทำให้เร็วขึ้นให้ทำ:

for (i = 10; i--;) {
    //super fast loop
}

แน่นอนคุณจะไม่สังเกตเห็นมันในวงเล็ก ๆ สาเหตุที่เร็วขึ้นก็เพราะคุณกำลังลดค่า i ในขณะที่ตรวจสอบว่าเป็น "จริง" (ประเมินเป็น "false" เมื่อถึง 0)


1
คุณหายเซมิโคลอนหรือไม่? (i = 10; i--;)
searlea

ใช่ฉันแก้ไขข้อผิดพลาด และถ้าเราจะจู้จี้จุกจิกฉันจะชี้ให้เห็นว่าคุณลืมลำไส้ใหญ่กึ่งของคุณหลังจากที่คุณ -! หึ
djdd87

ทำไมจะเร็วกว่า แหล่งที่สั้นกว่าไม่ได้หมายความว่าจะเร็วขึ้น คุณวัดมันได้หรือไม่
Greg Hewgill

4
ใช่ฉันวัดแล้วและฉันไม่ได้บอกว่าแหล่งที่สั้นกว่าจะทำให้เร็วขึ้น แต่การดำเนินการที่น้อยลงก็จะทำให้เร็วขึ้น
peirix

4

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

วิธีการเข้ารหัสสำหรับ for loop เพื่อประมวลผล array lis ดังนี้:

for (var i = 0; i < myArray.length; i++) {...

ปัญหาเกี่ยวกับสิ่งนี้คือการประเมินความยาวของอาร์เรย์โดยใช้ myArray.length ต้องใช้เวลาและวิธีการที่เราได้เข้ารหัสลูปหมายความว่าการประเมินนี้จะต้องดำเนินการทุกครั้งที่วนรอบ หากอาร์เรย์มีองค์ประกอบ 1,000 รายการความยาวของอาร์เรย์จะถูกประเมิน 1001 ครั้ง หากเราดูที่ปุ่มตัวเลือกและมี myForm.myButtons.length แล้วจะใช้เวลาในการประเมินนานกว่าเนื่องจากกลุ่มของปุ่มที่เหมาะสมภายในแบบฟอร์มที่ระบุจะต้องอยู่ก่อนจึงจะสามารถประเมินความยาวได้ทุกครั้งที่วนรอบ

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

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

รหัสที่ต้องทำคือ:

for (var i = 0, var j = myArray.length; i < j; i++) {...

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

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

รหัสสุดท้ายที่ประมวลผลอาร์เรย์ของเราอย่างมีประสิทธิภาพมากที่สุดคือ:

for (var i = myArray.length-1; i > -1; i--) {...

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


4

++vs. --ไม่สำคัญเนื่องจาก JavaScript เป็นภาษาที่ตีความไม่ใช่ภาษาที่รวบรวม แต่ละคำสั่งแปลเป็นภาษาเครื่องมากกว่าหนึ่งภาษาและคุณไม่ควรใส่ใจในรายละเอียดที่เต็มไปด้วยเลือด

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

คุณควรเขียนโค้ดที่อ่านได้


3

ในหลายกรณีสิ่งนี้ไม่มีอะไรเกี่ยวข้องกับข้อเท็จจริงที่ว่าโปรเซสเซอร์สามารถเปรียบเทียบกับศูนย์ได้เร็วกว่าการเปรียบเทียบอื่น ๆ

นี่เป็นเพราะเอ็นจินJavascript เพียงไม่กี่เอ็นจิน (ที่อยู่ในรายการ JIT) สร้างรหัสภาษาของเครื่อง

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

โปรดจำไว้ว่าเมื่อมีงานง่าย ๆ เช่นการเพิ่ม / ลบออกจากตัวแปรหรือเปรียบเทียบตัวแปรกับบางสิ่งค่าใช้จ่ายของล่ามที่ย้ายจาก "คำสั่ง" ภายในหนึ่งไปยังอีกค่อนข้างสูงดังนั้นคำสั่ง "น้อย" ที่มี ใช้ภายในโดยเอ็นจิน JS ยิ่งดี


3

ดีฉันไม่ทราบเกี่ยวกับ JavaScript มันควรจะเป็นเพียงเรื่องของการประเมินความยาวของอาเรย์และบางทีสิ่งที่จะทำอย่างไรกับอาร์เรย์ที่เชื่อมโยง อาร์เรย์มีความหนาแน่นสูงนั่นคือบางคนอาจปรับให้เหมาะสมสำหรับสิ่งนั้น)

ในชุดประกอบระดับต่ำจะมีคำสั่งวนลูปเรียกว่า DJNZ (ลดค่าและกระโดดถ้าไม่ใช่ศูนย์) ดังนั้นการลดลงและการกระโดดคือทั้งหมดในการเรียนการสอนทำให้เร็วขึ้นเล็กน้อยกว่า INC และ JL / JB เล็กน้อย (เพิ่มขึ้นกระโดดถ้าน้อยกว่า / กระโดดถ้าด้านล่าง) การเปรียบเทียบกับศูนย์นั้นง่ายกว่าการเปรียบเทียบกับหมายเลขอื่น แต่ทั้งหมดนั้นอยู่ในขอบเขตที่น้อยมากและยังขึ้นอยู่กับสถาปัตยกรรมเป้าหมาย (สามารถสร้างความแตกต่างได้เช่นบน Arm ในสมาร์ทโฟน)

ฉันไม่คาดหวังว่าความแตกต่างระดับต่ำนี้จะมีผลกระทบอย่างมากต่อภาษาที่ตีความฉันแค่ไม่เห็น DJNZ ท่ามกลางคำตอบดังนั้นฉันคิดว่าฉันจะแบ่งปันความคิดที่น่าสนใจ


สำหรับบันทึกDJNZนั้นเป็นคำสั่งใน 8051 (z80) ISA x86 มีdec/jnzแทนinc/cmp/jneและเห็นได้ชัดว่าแขนมีบางอย่างที่คล้ายกัน ฉันค่อนข้างแน่ใจว่านี่ไม่ใช่สาเหตุของความแตกต่างของ Javascript นั่นคือจากการมีมากขึ้นที่จะเห็นได้ชัดในสภาพวง
Peter Cordes

3

มันใช้ถูกกล่าวว่า --i เร็วขึ้น (ใน C ++) เพราะมีเพียงผลเดียวคือค่าที่ลดลง i - ต้องเก็บค่าที่ลดลงกลับไปที่ i และเก็บค่าเดิมไว้เป็นผลลัพธ์ (j = i--;) ในคอมไพเลอร์ส่วนใหญ่จะใช้รีจิสเตอร์สองตัวแทนที่จะเป็นหนึ่งตัวซึ่งอาจทำให้ตัวแปรอื่นต้องถูกเขียนไปยังหน่วยความจำแทนที่จะเก็บไว้เป็นตัวแปรรีจิสเตอร์

ฉันเห็นด้วยกับคนอื่น ๆ ที่บอกว่ามันไม่ได้ทำให้ความแตกต่างในวันนี้


3

ในคำง่าย ๆ

"i-- และ i ++ ที่จริงแล้วทั้งคู่ใช้เวลาเดียวกัน"

แต่ในกรณีนี้เมื่อคุณมีการดำเนินการแบบเพิ่มหน่วยประมวลผลประเมิน. ความยาวทุกครั้งที่ตัวแปรเพิ่มขึ้น 1 และในกรณีที่ลดลง .. โดยเฉพาะในกรณีนี้มันจะประเมิน. ความยาวเพียงครั้งเดียวจนกว่าเราจะได้รับ 0


3

ก่อนอื่นi++และi--ใช้เวลาเดียวกันกับภาษาการเขียนโปรแกรมใด ๆ รวมถึง JavaScript

รหัสต่อไปนี้ใช้เวลาต่างกันมาก

อย่างรวดเร็ว:

for (var i = 0, len = Things.length - 1; i <= len; i++) { Things[i] };

ช้า:

for (var i = 0; i <= Things.length - 1; i++) { Things[i] };

ดังนั้นรหัสต่อไปนี้ก็ใช้เวลาต่างกันเช่นกัน

อย่างรวดเร็ว:

for (var i = Things.length - 1; i >= 0; i--) { Things[i] };

ช้า:

for (var i = 0; i <= Things.length - 1; i++) { Things[i] };

PS Slowช้าสำหรับบางภาษาเท่านั้น (เอ็นจิน JavaScript) เนื่องจากการเพิ่มประสิทธิภาพของคอมไพเลอร์ วิธีที่ดีที่สุดคือการใช้'<' แทน '<=' (หรือ '=') และ'--i' แทน 'i--'


3

i-- หรือ i ++ ใช้เวลาไม่นาน หากคุณเข้าไปในส่วนลึกของสถาปัตยกรรมซีพียู++จะเร็วกว่า--เนื่องจากการ--ดำเนินการจะทำส่วนเสริม 2 แต่มันเกิดขึ้นภายในฮาร์ดแวร์ดังนั้นสิ่งนี้จะทำให้มันรวดเร็วและไม่แตกต่างที่สำคัญระหว่าง++และ--การดำเนินการเหล่านี้ถือว่าเป็น ใช้เวลาน้อยที่สุดใน CPU

สำหรับวงทำงานเช่นนี้

  • เริ่มต้นตัวแปรทันทีที่เริ่มต้น
  • ตรวจสอบข้อ จำกัด ในตัวถูกดำเนินการที่สองของวง<, >, <=ฯลฯ
  • จากนั้นใช้ลูป
  • การเพิ่มลูปและลูปจะทำให้กระบวนการเหล่านี้ซ้ำอีกครั้ง

ดังนั้น,

for (var i = Things.length - 1; i >= 0; i--) {
    Things[i]
}; 

จะคำนวณความยาวของอาเรย์เพียงครั้งเดียวเมื่อเริ่มต้นและนี่ไม่ใช่เวลามาก แต่

for(var i = array.length; i--; ) 

จะคำนวณความยาวของแต่ละวงดังนั้นมันจะใช้เวลามาก


var i = Things.length - 1; i >= 0; i--จะคำนวณความยาว 1 ครั้งด้วย
Dmitry

1
ฉันไม่แน่ใจว่า " --การดำเนินการจะทำส่วนเสริมของ 2" หมายความว่าอย่างไร แต่ฉันคิดว่าหมายความว่ามันจะลบล้างบางอย่าง ไม่มันไม่ได้ลบล้างสิ่งใดในสถาปัตยกรรมใด ๆ การลบ 1 นั้นง่ายเหมือนการบวก 1 คุณแค่สร้างวงจรที่ยืมมากกว่าถือ
โอเลเท

1

วิธีที่ดีที่สุดในการตอบคำถามประเภทนี้คือลองทำดู ตั้งค่าการวนซ้ำที่นับซ้ำหรือนับล้านและทำทั้งสองวิธี เวลาทั้งลูปและเปรียบเทียบผลลัพธ์

คำตอบอาจขึ้นอยู่กับเบราว์เซอร์ที่คุณใช้ บางคนจะมีผลลัพธ์ที่แตกต่างกว่าคนอื่น ๆ


4
นั่นยังคงไม่ตอบคำถามของเขาว่าทำไมมันถึงเร็วกว่า เขาต้องการเพียงแค่ได้รับมาตรฐานโดยไม่มีความรู้ว่าทำไมมันเป็นวิธีการที่ใด ๆ ...
peirix

3
นั่นเป็นเรื่องจริง อย่างไรก็ตามโดยที่ไม่รู้ว่าการนำ Javascript ไปใช้งานจริงในแต่ละเบราว์เซอร์นั้นเป็นไปไม่ได้เลยที่จะตอบส่วน "ทำไม" คำตอบมากมายที่นี่มีคำแนะนำเกี่ยวกับประวัติเช่น "ใช้การเตรียมล่วงหน้าแทนการโพสต์การตกแต่ง" (เคล็ดลับการเพิ่มประสิทธิภาพ C ++) และ "เปรียบเทียบกับศูนย์" ซึ่งอาจเป็นจริงในภาษาที่รวบรวมรหัสภาษา แต่ Javascript ค่อนข้างไกลจากโลหะเปลือยของ ซีพียู
เกร็ก Hewgill

4
-1 ฉันไม่เห็นด้วยอย่างยิ่งว่าวิธีที่ดีที่สุดคือลอง ตัวอย่างสองตัวอย่างไม่ได้แทนที่ความรู้โดยรวมและเป็นจุดรวมของฟอรัมเช่นนี้
Christophe

1

ชอบมากคะแนนมากขึ้น แต่ไม่มีคำตอบ: D

เพียงแค่ใส่การเปรียบเทียบกับศูนย์มักจะเป็นการเปรียบเทียบที่เร็วที่สุด

ดังนั้น (a == 0) จึงเร็วกว่าเมื่อส่งคืน True จริงกว่า (a == 5)

มีขนาดเล็กและไม่สำคัญและมีจำนวน 100 ล้านแถวในคอลเลกชันสามารถวัดได้

เช่นในการวนซ้ำคุณอาจจะพูดว่า i <= array.length และเพิ่มขึ้น i

ในลูปดาวน์คุณอาจบอกว่า i> = 0 และลดค่า i แทน

การเปรียบเทียบนั้นเร็วกว่า ไม่ใช่ 'ทิศทาง' ของลูป


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

ไม่มันเป็นการเปรียบเทียบที่ได้รับการยอมรับโดยพื้นฐานกับศูนย์เป็นวิธีที่เร็วที่สุด แม้ว่าข้อความของคุณจะถูกต้องเช่นกันกฎทองของการเปรียบเทียบกับศูนย์นั้นสมบูรณ์
Robert

4
นั่นเป็นความจริงเฉพาะในกรณีที่คอมไพเลอร์เลือกที่จะทำการปรับให้เหมาะสมซึ่งไม่ได้รับประกันอย่างแน่นอน คอมไพเลอร์อาจสร้างรหัสเดียวกันสำหรับ (a == 0) และ (a == 5) ยกเว้นค่าของค่าคงที่ CPU จะไม่เปรียบเทียบกับ 0 อย่างรวดเร็วกว่าเทียบกับค่าอื่น ๆ หากทั้งสองด้านของการเปรียบเทียบเป็นตัวแปร (จากมุมมองของ CPU) โดยทั่วไปแล้วคอมไพเลอร์โค้ดเนทีฟเท่านั้นที่มีโอกาสเพิ่มประสิทธิภาพในระดับนี้
เกร็ก Hewgill

1

ช่วยคนอื่นหลีกเลี่ยงการเป็นหัวหน้า --- โหวตให้ดีขึ้น !!!

คำตอบที่ได้รับความนิยมสูงสุดในหน้านี้ใช้ไม่ได้กับ Firefox 14 และไม่ผ่าน jsLinter "ขณะที่" ลูปต้องการตัวดำเนินการเปรียบเทียบไม่ใช่การกำหนด มันทำงานได้กับโครเมี่ยมซาฟารีและเช่น แต่เสียชีวิตใน firefox

นี่มันหัก!

var i = arr.length; //or 10
while(i--)
{
  //...
}

สิ่งนี้จะทำงาน! (ทำงานกับ firefox ผ่าน jsLinter)

var i = arr.length; //or 10
while(i>-1)
{
  //...
  i = i - 1;
}

2
ฉันเพิ่งลองใช้ Firefox 14 และใช้งานได้ดี ฉันได้เห็นตัวอย่างจากไลบรารีที่ใช้งานจริงwhile (i--)และที่ทำงานกับเบราว์เซอร์จำนวนมาก มีอะไรแปลก ๆ เกี่ยวกับการทดสอบของคุณบ้างไหม? คุณใช้ Firefox 14 รุ่นเบต้าหรือไม่
Matt Browne

1

นี่เป็นเพียงการคาดเดา แต่อาจเป็นเพราะโปรเซสเซอร์ประมวลผลเปรียบเทียบได้ง่ายกว่าด้วย 0 (i> = 0) แทนที่จะเป็นค่าอื่น (i <Things.length)


3
+1 อย่าให้คนอื่นโหวต แม้ว่าการประเมินความยาว. ซ้ำ ๆ จะมีปัญหามากกว่าการ inc / ลดจริงการตรวจสอบปลายลูปอาจมีความสำคัญ คอมไพเลอร์ C ของฉันให้คำเตือนเกี่ยวกับลูปเช่น: "สังเกต # 1544-D: (ULP 13.1) ตรวจพบการวนซ้ำนับลูปแนะนำนับถอยหลังตามการตรวจจับเลขศูนย์ง่ายขึ้น"
ฮาร์เปอร์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.