เหตุใดการต่อสตริงจึงเร็วกว่าการรวมอาร์เรย์


114

วันนี้ฉันอ่านกระทู้นี้เกี่ยวกับความเร็วของการต่อสายอักขระ

น่าแปลกที่การต่อสตริงเป็นผู้ชนะ:

http://jsben.ch/#/OJ3vo

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

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


ปรับปรุง

ดังนั้นในการต่อสายอักขระในเบราว์เซอร์สมัยใหม่จึงได้รับการปรับให้เหมาะสมดังนั้นการใช้+เครื่องหมายจึงเร็วกว่าการใช้joinเมื่อคุณต้องการต่อสตริง

แต่@Arthur ชี้ให้เห็นว่าjoinเร็วกว่าถ้าคุณต้องการเข้าร่วมสตริงด้วยตัวคั่น


อัปเดต - 2020
Chrome: Array joinเกือบจะ2 times fasterเป็น String concat + See: https://stackoverflow.com/a/54970240/984471

หมายเหตุ:

  • อาร์เรย์joinจะดีกว่าถ้าคุณมีlarge strings
  • หากเราต้องการสร้างseveral small stringsในผลลัพธ์สุดท้ายจะเป็นการดีกว่าที่จะใช้การต่อสตริง+เนื่องจากมิฉะนั้นการใช้ Array จะต้องมีการแปลง Array to String หลายรายการในตอนท้ายซึ่งเป็นประสิทธิภาพที่เกินพิกัด


1
รหัสนี้ควรจะสร้างขยะ 500 เทราไบต์ แต่ทำงานใน 200 มิลลิวินาที ฉันคิดว่าพวกเขาแค่จัดสรรพื้นที่ให้สตริงมากขึ้นเล็กน้อยและเมื่อคุณเพิ่มสตริงสั้น ๆ เข้าไปก็มักจะพอดีกับพื้นที่เพิ่มเติม
Ivan Kuckir

คำตอบ:


149

การเพิ่มประสิทธิภาพสตริงของเบราว์เซอร์ได้เปลี่ยนรูปภาพการต่อสายอักขระ

Firefox เป็นเบราว์เซอร์แรกที่เพิ่มประสิทธิภาพการต่อสายอักขระ เริ่มต้นด้วยเวอร์ชัน 1.0 จริง ๆ แล้วเทคนิคอาร์เรย์จะช้ากว่าการใช้ตัวดำเนินการบวกในทุกกรณี เบราว์เซอร์อื่น ๆ ได้เพิ่มประสิทธิภาพการเชื่อมต่อสตริงดังนั้น Safari, Opera, Chrome และ Internet Explorer 8 จึงแสดงประสิทธิภาพที่ดีขึ้นโดยใช้ตัวดำเนินการบวก Internet Explorer ก่อนเวอร์ชัน 8 ไม่มีการปรับให้เหมาะสมดังกล่าวดังนั้นเทคนิคอาร์เรย์จึงเร็วกว่าตัวดำเนินการบวกเสมอ

- การเขียน JavaScript ที่มีประสิทธิภาพ: บทที่ 7 - แม้แต่เว็บไซต์ที่เร็วขึ้น

V8 javascript engine (ใช้ใน Google Chrome) ใช้รหัสนี้เพื่อทำการเชื่อมต่อสตริง:

// ECMA-262, section 15.5.4.6
function StringConcat() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined", ["String.prototype.concat"]);
  }
  var len = %_ArgumentsLength();
  var this_as_string = TO_STRING_INLINE(this);
  if (len === 1) {
    return this_as_string + %_Arguments(0);
  }
  var parts = new InternalArray(len + 1);
  parts[0] = this_as_string;
  for (var i = 0; i < len; i++) {
    var part = %_Arguments(i);
    parts[i + 1] = TO_STRING_INLINE(part);
  }
  return %StringBuilderConcat(parts, len + 1, "");
}

ดังนั้นภายในพวกเขาจึงเพิ่มประสิทธิภาพโดยการสร้าง InternalArray ( partsตัวแปร) ซึ่งจะเติมเต็ม ฟังก์ชัน StringBuilderConcat ถูกเรียกด้วยส่วนเหล่านี้ มันเร็วเพราะฟังก์ชัน StringBuilderConcat เป็นโค้ด C ++ ที่ได้รับการปรับให้เหมาะสมอย่างมาก มันยาวเกินไปที่จะพูดที่นี่ แต่ค้นหาในไฟล์runtime.ccRUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderConcat)เพื่อดูโค้ด


4
คุณทิ้งสิ่งที่น่าสนใจจริงๆไว้อาร์เรย์ใช้เพื่อเรียก Runtime_StringBuilderConcat ด้วยจำนวนอาร์กิวเมนต์ที่แตกต่างกันเท่านั้น แต่งานจริงทำที่นั่น
evilpie

41
การเพิ่มประสิทธิภาพ 101: คุณควรตั้งเป้าให้ช้าที่สุด! ตัวอย่างเช่นarr.join vs str+ในโครเมี่ยมที่คุณได้รับ 25k/s vs 52k/s(ในการดำเนินงานต่อวินาที) ใน Firefox 76k/s vs 212k/sใหม่ที่คุณจะได้รับ เพื่อให้str+ได้เร็วขึ้น แต่ลองดูเบราว์เซอร์อื่น ๆ Opera ให้ 43k / s เทียบกับ 26k / s IE 1300/s vs 1002/sให้ ดูว่าเกิดอะไรขึ้น? เพียงเบราว์เซอร์ที่เพิ่มประสิทธิภาพจำเป็นต้องจะดีกว่าการใช้สิ่งที่ช้าลงกับคนอื่น ๆ ทั้งหมดที่มันไม่ได้เรื่องที่ทุกคน ดังนั้นไม่มีบทความใดที่เข้าใจอะไรเกี่ยวกับประสิทธิภาพเลย
gcb

45
@gcb เบราว์เซอร์เดียวที่เข้าร่วมเร็วกว่าไม่ควรใช้ 95% ของผู้ใช้ของฉันมี FF และ Chrome ฉันจะเพิ่มประสิทธิภาพสำหรับกรณีการใช้งาน 95%
Paul Draper

7
@PaulDraper หากผู้ใช้ 90% ใช้เบราว์เซอร์ที่รวดเร็วและตัวเลือกใดตัวเลือกหนึ่งที่คุณเลือกจะได้รับ 0.001 วินาที แต่ 10% ของผู้ใช้ของคุณจะได้รับ 2 วินาทีหากคุณเลือกที่จะลงโทษผู้ใช้รายอื่นจาก 0.001 วินาทีนั้น ... ชัดเจน. ถ้าคุณมองไม่เห็นฉันขอโทษสำหรับใครก็ตามที่คุณให้รหัส
gcb

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

23

Firefox ทำงานได้เร็วเพราะใช้สิ่งที่เรียกว่า Ropes ( Ropes: an Alternative to Strings ) โดยพื้นฐานแล้วเชือกเป็นเพียง DAG โดยที่ทุกโหนดเป็นสตริง

ตัวอย่างเช่นถ้าคุณจะทำa = 'abc'.concat('def')วัตถุที่สร้างขึ้นใหม่จะมีลักษณะเช่นนี้ แน่นอนว่านี่ไม่ใช่ลักษณะของสิ่งนี้ในหน่วยความจำเพราะคุณยังต้องมีฟิลด์สำหรับประเภทสตริงความยาวและอื่น ๆ

a = {
 nodeA: 'abc',
 nodeB: 'def'
}

และ b = a.concat('123')

b = {
  nodeA: a, /* {
             nodeA: 'abc',
             nodeB: 'def'
          } */
  nodeB: '123'
}           

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

ในทางกลับกัน['abc', 'def'].join('')มักจะจัดสรรหน่วยความจำเพื่อจัดวางสตริงใหม่ในหน่วยความจำ (บางทีสิ่งนี้ควรได้รับการปรับให้เหมาะสม)


6

ฉันรู้ว่านี่เป็นกระทู้เก่า แต่การทดสอบของคุณไม่ถูกต้อง คุณกำลังทำoutput += myarray[i];ในขณะที่ควรจะชอบมากกว่าoutput += "" + myarray[i];เพราะคุณลืมไปว่าคุณต้องติดกาวสิ่งของเข้าด้วยกัน รหัส concat ควรเป็นดังนี้:

var output = myarray[0];
for (var i = 1, len = myarray.length; i<len; i++){
    output += "" + myarray[i];
}

ด้วยวิธีนี้คุณกำลังดำเนินการสองอย่างแทนการดำเนินการเดียวเนื่องจากการติดองค์ประกอบเข้าด้วยกัน

Array.join() เร็วกว่า.


ฉันไม่ได้รับคำตอบของคุณ พัตต์"" +กับของแท้ต่างกันอย่างไร?
Sanghyun Lee

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

1
และทำไมเราต้องใส่? เรากำลังติดกาวไอเท็มที่outputไม่มีมันอยู่แล้ว
Sanghyun Lee

เพราะนี่คือวิธีการทำงานร่วมกัน ตัวอย่างเช่นคุณสามารถทำสิ่งArray.join(",")ที่ไม่ได้ผลกับforลูปของคุณ
Arthur

โอ้ฉันเข้าใจแล้ว คุณได้ทดสอบแล้วว่า join () เร็วขึ้นหรือไม่?
Sanghyun Lee

5

สำหรับการรวมข้อมูลจำนวนมากจะเร็วกว่าดังนั้นคำถามจึงระบุไม่ถูกต้อง

let result = "";
let startTime = new Date().getTime();
for (let i = 0; i < 2000000; i++) {
    result += "x";
}
console.log("concatenation time: " + (new Date().getTime() - startTime));

startTime = new Date().getTime();
let array = new Array(2000000);
for (let i = 0; i < 2000000; i++) {
    array[i] = "x";
}
result = array.join("");
console.log("join time: " + (new Date().getTime() - startTime));

ทดสอบบน Chrome 72.0.3626.119, Firefox 65.0.1, Edge 42.17134.1.0 โปรดทราบว่าเร็วกว่าแม้จะมีการสร้างอาร์เรย์ด้วยก็ตาม!


~ ส.ค. 2020 จริง ใน Chrome: Array เวลาเข้าร่วม: 462 เวลา String Concat (+): 827 เข้าร่วมเร็วขึ้นเกือบ 2 เท่า
Manohar Reddy Poreddy

3

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

Array เข้าร่วม FTW!


2

ฉันจะบอกว่าด้วยสตริงการจัดสรรบัฟเฟอร์ที่ใหญ่กว่านั้นง่ายกว่า แต่ละองค์ประกอบมีขนาดเพียง 2 ไบต์ (ถ้าเป็น UNICODE) ดังนั้นแม้ว่าคุณจะเป็นแบบอนุรักษ์นิยม แต่คุณสามารถจัดสรรบัฟเฟอร์ขนาดใหญ่สำหรับสตริงได้ล่วงหน้า ด้วยarraysแต่ละองค์ประกอบมีมากขึ้น "ซับซ้อน" เพราะแต่ละองค์ประกอบเป็นObjectเพื่อให้การดำเนินงานอนุรักษ์นิยมจะเป็น preallocate พื้นที่สำหรับองค์ประกอบน้อย

หากคุณพยายามที่จะเพิ่มfor(j=0;j<1000;j++)ก่อนforคุณจะเห็นว่า (ภายใต้โครเมี่ยม) ความแตกต่างของความเร็วจะน้อยลง ในท้ายที่สุดมันก็ยังคงเป็น 1.5x สำหรับการต่อสตริง แต่มีขนาดเล็กกว่า 2.6 ที่เป็นมาก่อน

และต้องคัดลอกองค์ประกอบอักขระ Unicode อาจเล็กกว่าการอ้างอิงไปยัง JS Object

โปรดทราบว่ามีความเป็นไปได้ที่การใช้งาน JS engine จำนวนมากจะมีการเพิ่มประสิทธิภาพสำหรับอาร์เรย์ประเภทเดียวซึ่งจะทำให้สิ่งที่ฉันเขียนไปนั้นไร้ประโยชน์ :-)


1

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


0

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

ฉันจะบอกว่าString.concatมีประสิทธิภาพที่ดีกว่าใน V8 เวอร์ชันล่าสุด แต่สำหรับ Firefox และ Opera Array.joinเป็นผู้ชนะ


-1

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

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