2011-12-28 37 views
32

Khi so sánh this benchmark với chrome 16 vs opera 11.6, chúng tôi thấy rằngTại sao Function.prototype.bind chậm?

  • trong ràng buộc bản chrome là chậm hơn gần 5 lần sau đó một phiên bản mô phỏng của bind
  • trong opera ràng buộc bản địa là gần 4 lần nhanh hơn sau đó một mô phỏng phiên bản của bind

đâu một phiên bản mô phỏng của ràng buộc trong trường hợp này là

var emulatebind = function (f, context) { 
    return function() { 
     f.apply(context, arguments); 
    }; 
}; 

Có lý do chính đáng tại sao có sự khác biệt như vậy hay đây chỉ là vấn đề v8 không tối ưu hóa đủ?

Lưu ý: emulatebind chỉ thực hiện một tập hợp con nhưng điều đó không thực sự có liên quan. Nếu bạn có một mô phỏng đầy đủ tính năng và được tối ưu hóa, hãy liên kết performance difference in the benchmark vẫn tồn tại.

+0

@RobW đã mô tả phiên bản mô phỏng ràng buộc mà tôi đang so sánh. – Raynos

+0

Tôi cho rằng, điều này là do tối ưu hóa mã khác nhau. Có lẽ, trình bao bọc với liên kết gốc không cho phép một số tối ưu hóa nhất định. FF10 thể hiện hành vi tương tự. – kirilloid

+3

Q. của bạn phải là _ "Tại sao giả lập của tôi .bind() nhanh hơn người bản xứ trong Chrome, FireFox và chậm hơn trong Opera và IE?" _. Và tại sao bạn nghĩ rằng phải khác? Tối ưu hóa mã khác nhau. Ràng buộc mô phỏng của bạn không cho phép thêm tham số, nhưng chỉ có ngữ cảnh, ví dụ. –

Trả lời

26

Dựa trên http://jsperf.com/bind-vs-emulate/6, có thêm phiên bản ES5-shim để so sánh, có vẻ như thủ phạm là chi nhánh phụ và instanceof rằng phiên bản ràng buộc phải thực hiện để kiểm tra nếu nó được gọi là một constructor.

Mỗi lần phiên bản giới hạn được điều hành, mã đó được thực hiện chủ yếu là:

if (this instanceof bound) { 
    // Never reached, but the `instanceof` check and branch presumably has a cost 
} else { 
    return target.apply(
    that, 
    args.concat(slice.call(arguments)) 
    ); 

    // args is [] in your case. 
    // So the cost is: 
    // * Converting (empty) Arguments object to (empty) array. 
    // * Concating two empty arrays. 
} 

In the V8 source code, việc kiểm tra này xuất hiện (bên boundFunction) như

if (%_IsConstructCall()) { 
    return %NewObjectFromBound(boundFunction); 
} 

(Plaintext link to v8natives.js khi Google Code Tìm kiếm chết.)

Có một chút khó hiểu rằng, đối với Chrome 16 ít nhất, phiên bản es5-shim vẫn nhanh hơn na phiên bản bổ sung. Và các trình duyệt khác có kết quả khá khác nhau đối với es5-shim so với gốc. Đầu cơ: có lẽ %_IsConstructCall() thậm chí còn chậm hơn this instanceof bound, có lẽ do vượt qua ranh giới mã nguồn gốc/JS. Và có lẽ các trình duyệt khác có cách kiểm tra nhanh hơn đối với cuộc gọi [[Construct]].

+1

[thêm thể hiện kiểm tra vào raynosBound] (http://jsperf.com/bind-vs-emulate/7). Tôi nghĩ rằng trên không chủ yếu là concating mảng trống. Bạn có nghĩ rằng nó đáng giá để tay tối ưu hóa shim ràng buộc ES5 cho trường hợp 'args.length === 0' để trả về một hàm chỉ' target.apply (đó, đối số) '? – Raynos

+3

Cá nhân tôi nghĩ rằng bất kỳ tối ưu hóa nào ở đây là vi mô hóa và tôi sẽ đợi cho đến khi điểm chuẩn của tôi cho thấy rằng các hàm bị ràng buộc đã vượt trội hơn ví dụ:độ trễ mạng khiến ứng dụng của tôi bị chậm trước khi cân nhắc bất kỳ điều gì sắp xếp. – Domenic

3

V8 source code for bind được triển khai trong JS.

OP không mô phỏng bind bởi vì nó không phản đối cách thức bind thực hiện. Dưới đây là một đầy đủ tính năng bind:

var emulatebind = function (f, context) { 
    var curriedArgs = Array.prototype.slice.call(arguments, 2); 
    return function() { 
    var allArgs = curriedArgs.slice(0); 
    for (var i = 0, n = arguments.length; i < n; ++i) { 
     allArgs.push(arguments[i]); 
    } 
    return f.apply(context, allArgs); 
    }; 
}; 

Rõ ràng, một tối ưu hóa nhanh chóng sẽ làm

return f.apply(context, arguments); 

thay vì nếu curriedArgs.length == 0, bởi vì nếu bạn có hai sáng tạo mảng không cần thiết, và một bản sao không cần thiết, nhưng có lẽ phiên bản gốc thực sự được triển khai trong JS và không thực hiện tối ưu hóa đó.

Lưu ý: Điều này đầy đủ tính năng bind không xử lý chính xác một số corner cases xung quanh this cưỡng chế cưỡng chế ở chế độ nghiêm ngặt. Đó có thể là một nguồn chi phí khác.

+0

Nó không mô phỏng ràng buộc đầy đủ. Nhưng các bài kiểm tra hiệu suất chỉ sử dụng tập con của các phần ràng buộc được mô phỏng. Bạn có thể giải thích tại sao các tham số currying gây ra một chi phí khi nó không được sử dụng trong các bài kiểm tra hiệu suất? Bạn có thể chỉnh sửa các bài kiểm tra với bài kiểm tra đầy đủ tính năng không? – Raynos

+0

[Hiệu suất khác biệt vẫn còn tồn tại] (http://jsperf.com/bind-vs-emulate/4) – Raynos

+0

@Raynos, thêm liên kết tới mã nguồn JS cho hàm liên kết từ 'v8natives.js'. –

7

Không thể triển khai hoàn toàn tính năng bind chỉ trong ES5. Trong các phần cụ thể 15.3.4.5.1 thông qua 15.3.4.5.3 của spec không thể được mô phỏng.

15.3.4.5.1, cụ thể, có vẻ như một gánh nặng hiệu suất có thể: trong các hàm giới hạn ngắn có các thuộc tính nội bộ khác nhau, vì vậy hãy gọi cho chúng có khả năng thực hiện đường dẫn mã bất thường và có thể phức tạp hơn.

Nhiều tính năng khác cụ thể un-emulatable của một hàm ràng buộc (như arguments/caller ngộ độc, và có thể là tùy chỉnh length độc lập với chữ ký gốc) có thể có thể thêm chi phí cho mỗi cuộc gọi, mặc dù tôi thừa nhận đó là một chút khó xảy ra. Mặc dù có vẻ như V8 thậm chí không thực hiện ngộ độc vào lúc này.

EDIT câu trả lời này là đầu cơ, nhưng câu trả lời khác của tôi có bằng chứng gần hơn. Tôi vẫn nghĩ đây là suy đoán hợp lệ, nhưng đó là một câu trả lời riêng biệt, vì vậy tôi sẽ để nó như vậy và chỉ giới thiệu bạn với người khác.