Tại sao nối chuỗi nhanh hơn nối mảng?


114

Hôm nay, tôi đọc chủ đề này về tốc độ nối chuỗi.

Đáng ngạc nhiên, nối chuỗi là người chiến thắng:

http://jsben.ch/#/OJ3vo

Kết quả là trái ngược với những gì tôi nghĩ. Bên cạnh đó, có rất nhiều bài báo về việc này giải thích một cách trái chiều như thế này .

Tôi có thể đoán rằng các trình duyệt được tối ưu hóa để chuỗi concattrên phiên bản mới nhất, nhưng làm thế nào để họ làm điều đó? Chúng ta có thể nói rằng nó tốt hơn để sử dụng +khi nối các chuỗi không?


Cập nhật

Vì vậy, trong các trình duyệt hiện đại, việc nối chuỗi được tối ưu hóa nên việc sử dụng +các dấu hiệu sẽ nhanh hơn so với việc sử dụng joinkhi bạn muốn nối các chuỗi.

Nhưng @Arthur đã chỉ ra rằng joinnhanh hơn nếu bạn thực sự muốn nối các chuỗi bằng dấu phân cách.


Cập nhật - 2020
Chrome: Array joingần như 2 times fasterlà String concat + Xem: https://stackoverflow.com/a/54970240/984471

Như một lưu ý:

  • Mảng joinsẽ tốt hơn nếu bạn cólarge strings
  • Nếu chúng ta cần tạo several small stringsở đầu ra cuối cùng, tốt hơn là nên đi với chuỗi kết hợp +, vì nếu không với Mảng sẽ cần một số chuyển đổi Mảng sang Chuỗi ở cuối là quá tải hiệu suất.


1
này được cho là tạo ra 500 terabyte rác, nhưng nó chạy trong 200 ms. Tôi nghĩ rằng họ chỉ phân bổ thêm một chút không gian cho một chuỗi và khi bạn thêm một chuỗi ngắn vào nó, nó thường vừa với một khoảng trống thừa.
Ivan Kuckir,

Câu trả lời:


149

Tối ưu hóa chuỗi trình duyệt đã thay đổi hình ảnh nối chuỗi.

Firefox là trình duyệt đầu tiên tối ưu hóa việc nối chuỗi. Bắt đầu từ phiên bản 1.0, kỹ thuật mảng thực sự chậm hơn so với việc sử dụng toán tử cộng trong mọi trường hợp. Các trình duyệt khác cũng đã tối ưu hóa việc nối chuỗi, vì vậy Safari, Opera, Chrome và Internet Explorer 8 cũng hiển thị hiệu suất tốt hơn bằng cách sử dụng toán tử dấu cộng. Internet Explorer trước phiên bản 8 không có tính năng tối ưu hóa như vậy, và do đó, kỹ thuật mảng luôn nhanh hơn toán tử cộng.

- Viết JavaScript hiệu quả: Chương 7 - Trang web nhanh hơn nữa

Công cụ javascript V8 (được sử dụng trong Google Chrome) sử dụng mã này để thực hiện nối chuỗi:

// 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, "");
}

Vì vậy, bên trong họ tối ưu hóa nó bằng cách tạo InternalArray ( partsbiến), sau đó được lấp đầy. Hàm StringBuilderConcat được gọi với các phần này. Nó nhanh chóng vì hàm StringBuilderConcat là một số mã C ++ được tối ưu hóa rất nhiều. Quá dài để trích dẫn ở đây, nhưng hãy tìm kiếm trong tệp runtime.ccRUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderConcat) để xem mã.


4
Bạn đã bỏ qua điều thực sự thú vị, mảng chỉ được sử dụng để gọi Runtime_StringBuilderConcat với số lượng đối số khác nhau. Nhưng công việc thực sự được thực hiện ở đó.
evilpie

41
Tối ưu hóa 101: Bạn nên nhắm mục tiêu ít chậm nhất! ví dụ: arr.join vs str+trên chrome, bạn nhận được (tính bằng các hoạt động trên giây) 25k/s vs 52k/s. trên firefox mới bạn nhận được 76k/s vs 212k/s. str+NHANH HƠN cũng vậy . nhưng hãy xem các trình duyệt khác. Opera cho 43k / s so với 26k / s. IE cho 1300/s vs 1002/s. xem điều gì xảy ra? các chỉ trình duyệt CẦN tối ưu hóa sẽ tốt hơn bằng cách sử dụng những gì là chậm hơn trên tất cả những người khác, nơi nó không quan trọng chút nào. Vì vậy, không có bài báo nào hiểu bất cứ điều gì về hiệu suất.
gcb 21/09/13

45
@gcb, không nên sử dụng các trình duyệt duy nhất tham gia nhanh hơn. 95% người dùng của tôi có FF và Chrome. Tôi sẽ tối ưu hóa cho 95% trường hợp sử dụng.
Paul Draper

7
@PaulDraper nếu 90% người dùng đang sử dụng trình duyệt nhanh và một trong hai tùy chọn bạn chọn sẽ nhận được 0,001 giây của họ, nhưng 10% người dùng của bạn sẽ nhận được 2 giây nếu bạn chọn phạt những người dùng khác trong số 0,001 giây đó ... quyết định là rõ ràng. nếu bạn không thể nhìn thấy nó, tôi xin lỗi cho bất kỳ ai mà bạn viết mã.
gcb

7
Các trình duyệt cũ hơn cuối cùng sẽ biến mất, nhưng khả năng ai đó quay lại để chuyển đổi tất cả các phép tham gia mảng đó là không cao. Tốt hơn là bạn nên viết mã cho tương lai miễn là nó không gây bất tiện lớn cho người dùng hiện tại của bạn. Tỷ lệ cược là có nhiều điều quan trọng cần lo lắng hơn hiệu suất nối khi xử lý các trình duyệt cũ.
Thomas Higginbotham

23

Firefox nhanh bởi vì nó sử dụng một thứ gọi là Ropes ( Ropes: một thay thế cho chuỗi ). Một sợi dây về cơ bản chỉ là một DAG, trong đó mỗi Node là một chuỗi.

Vì vậy, ví dụ, nếu bạn muốn a = 'abc'.concat('def'), đối tượng mới được tạo sẽ trông như thế này. Tất nhiên đây không phải là chính xác như thế nào trong bộ nhớ, bởi vì bạn vẫn cần có một trường cho kiểu chuỗi, độ dài và có thể khác.

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

b = a.concat('123')

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

Vì vậy, trong trường hợp đơn giản nhất, VM hầu như không phải làm gì. Vấn đề duy nhất là điều này làm chậm các hoạt động khác trên chuỗi kết quả một chút. Ngoài ra, điều này tất nhiên làm giảm chi phí bộ nhớ.

Mặt khác, ['abc', 'def'].join('')thường chỉ cấp phát bộ nhớ để bố trí chuỗi mới trong bộ nhớ. (Có lẽ điều này nên được tối ưu hóa)


6

Tôi biết đây là một chủ đề cũ, nhưng thử nghiệm của bạn không chính xác. Bạn đang làm output += myarray[i];trong khi nó giống như output += "" + myarray[i];vì bạn đã quên, rằng bạn phải dán các vật dụng lại với nhau bằng một thứ gì đó. Mã concat phải giống như sau:

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

Bằng cách đó, bạn đang thực hiện hai hoạt động thay vì một do các yếu tố kết dính với nhau.

Array.join() nhanh hơn.


Tôi không nhận được câu trả lời của bạn. Sự khác biệt giữa đặt "" +và ban đầu là gì?
Sanghyun Lee

Đó là hai hoạt động thay vì một trên mỗi lần lặp lại tốn nhiều thời gian hơn.
Arthur

1
Và tại sao chúng ta cần đặt điều đó? Chúng tôi đã dán các mục vào outputmà không có nó.
Sanghyun Lee

Bởi vì đây là cách tham gia hoạt động. Ví dụ: bạn cũng có thể làm điều Array.join(",")này sẽ không hoạt động với forvòng lặp của bạn
Arthur

Ồ, tôi hiểu rồi. Bạn đã thử nghiệm để xem tham gia () có nhanh hơn không?
Sanghyun Lee

5

Đối với số lượng lớn dữ liệu tham gia nhanh hơn, vì vậy câu hỏi được nêu không chính xác.

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));

Đã thử nghiệm trên Chrome 72.0.3626.119, Firefox 65.0.1, Edge 42.17134.1.0. Lưu ý rằng nó nhanh hơn ngay cả khi bao gồm tạo mảng!


~ Tháng 8 năm 2020. Đúng. Trong Chrome: Thời gian tham gia mảng: 462. Thời gian kết nối chuỗi (+): 827. Tham gia nhanh hơn gần 2 lần.
Manohar Reddy Poreddy

3

Các tiêu chuẩn ở đó rất nhỏ. Việc kết hợp ba mục giống nhau nhiều lần sẽ được liên kết, kết quả sẽ được chứng minh là xác định và được ghi nhớ, trình xử lý rác sẽ chỉ vứt bỏ các đối tượng mảng (sẽ không có kích thước nào) và có thể chỉ được đẩy và bật ra khỏi ngăn xếp do không tham chiếu bên ngoài và vì các chuỗi không bao giờ thay đổi. Tôi sẽ ấn tượng hơn nếu thử nghiệm là một số lượng lớn các chuỗi được tạo ngẫu nhiên. Như trong một hoặc hai chuỗi giá trị của hợp đồng biểu diễn.

Array. tham gia FTW!


2

Tôi sẽ nói rằng với các chuỗi, việc phân bổ trước một bộ đệm lớn hơn sẽ dễ dàng hơn. Mỗi phần tử chỉ có 2 byte (nếu UNICODE), vì vậy ngay cả khi bạn thận trọng, bạn có thể phân bổ trước một bộ đệm khá lớn cho chuỗi. Với arraysmỗi phần tử thì "phức tạp" hơn, bởi vì mỗi phần tử là một Object, vì vậy việc triển khai bảo thủ sẽ phân bổ trước không gian cho ít phần tử hơn.

Nếu bạn cố gắng thêm một for(j=0;j<1000;j++)trước mỗi cái, forbạn sẽ thấy rằng (dưới chrome) sự khác biệt về tốc độ trở nên nhỏ hơn. Cuối cùng, nó vẫn là 1,5x cho việc nối chuỗi, nhưng nhỏ hơn 2,6 lần trước đó.

VÀ phải sao chép các phần tử, một ký tự Unicode có thể nhỏ hơn một tham chiếu đến một Đối tượng JS.

Hãy lưu ý rằng có khả năng nhiều triển khai của công cụ JS có tối ưu hóa cho các mảng kiểu đơn sẽ làm cho tất cả những gì tôi đã viết trở nên vô dụng :-)


1

Bài kiểm tra này cho thấy hình phạt của việc thực sự sử dụng một chuỗi được thực hiện bằng phép ghép nối so với được thực hiện bằng phương thức array.join. Mặc dù tốc độ chuyển nhượng tổng thể vẫn nhanh gấp đôi trong Chrome v31 nhưng nó không còn lớn như khi không sử dụng chuỗi kết quả.


0

Điều này rõ ràng phụ thuộc vào việc triển khai công cụ javascript. Ngay cả đối với các phiên bản khác nhau của một công cụ, bạn có thể nhận được kết quả khác nhau đáng kể. Bạn nên làm điểm chuẩn của riêng mình để xác minh điều này.

Tôi muốn nói rằng điều đó String.concatcó hiệu suất tốt hơn trong các phiên bản gần đây của V8. Nhưng đối với Firefox và Opera, Array.joinlà một người chiến thắng.


-1

Tôi đoán rằng, trong khi mọi phiên bản đều có chi phí của nhiều phép nối, các phiên bản nối đang xây dựng các mảng thêm vào đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.