2015-02-11 28 views
12

Tôi đã cố gắng thăm dò rằng cộng (+) chuyển đổi nhanh hơn parseInt với jsperf sau, và kết quả làm tôi ngạc nhiên:JsPerf: parseInt vs chuyển đổi Thêm

Parse vs Plus

đang chuẩn bị

<script> 
    Benchmark.prototype.setup = function() { 
    var x = "5555"; 
    }; 
</script> 

Parse mẫu

var y = parseInt(x); //<---80 million loops 

Thêm mẫu

var y = +x; //<--- 33 million loops 

Lý do là vì tôi đang sử dụng "Benchmark.prototype.setup" để khai báo biến của tôi, nhưng tôi không hiểu tại sao

Xem ví dụ thứ hai :

Parse vs Plus (local variable)

<script> 
    Benchmark.prototype.setup = function() { 
    x = "5555"; 
    }; 
</script> 

Parse mẫu

var y = parseInt(x); //<---89 million loops 

Thêm mẫu

var y = +x; //<--- 633 million loops 

Có thể ai đó giải thích kết quả?

Cảm ơn

+2

Bạn sẽ nhận được phản hồi tốt hơn ở đây nếu mã lõi ở đây, thay vì được giới thiệu bởi một liên kết (tôi đã cung cấp cho bạn một BTW +1, vì đây là một câu hỏi thú vị). – rfornal

+1

Một câu hỏi thú vị, nhưng đó là nhiều hơn về jsperf và các trường hợp thử nghiệm hơn về 'parseInt' so với' + '. Tôi nghĩ tiêu đề của bạn hơi gây hiểu lầm. –

+0

Tôi đã chỉnh sửa câu hỏi. Tôi hy vọng bây giờ nó tốt hơn –

Trả lời

-1

Tôi tin rằng lý do là vì parseInt tìm kiếm nhiều hơn chỉ là một sự chuyển đổi thành một số nguyên. Nó sẽ loại bất kỳ văn bản còn lại tắt của chuỗi như khi phân tích một giá trị pixel:

var width = parseInt(element.style.width);//return width as integer 

trong khi dấu cộng không thể xử lý trường hợp này:

var width = +element.style.width;//returns NaN 

Dấu cộng làm một chuyển đổi ngầm từ chuỗi để chỉ số và chỉ chuyển đổi đó. parseInt cố gắng tạo ra ý nghĩa của chuỗi đầu tiên (như với các số nguyên được gắn thẻ với một phép đo).

+0

câu hỏi là tại sao trong trường hợp đầu tiên ** parseInt ** đường nối nhanh hơn ** cộng ** –

30

Trong trường hợp thứ hai + là nhanh hơn bởi vì trong trường hợp đó V8 thực sự di chuyển nó ra khỏi vòng lặp điểm chuẩn - làm cho điểm chuẩn vòng lặp trống.

Điều này xảy ra do một số đặc thù của đường ống tối ưu hóa hiện tại. Nhưng trước khi chúng tôi nhận được các chi tiết đẫm máu, tôi muốn nhắc nhở cách Benchmark.js hoạt động.

Để đo test bạn đã viết nó mất Benchmark.prototype.setup rằng bạn cũng được cung cấp và các trường hợp thử nghiệm bản thân và tự động tạo ra một chức năng trông khoảng như thế này (tôi bỏ qua một số chi tiết không liên quan):

function (n) { 
    var start = Date.now(); 

    /* Benchmark.prototype.setup body here */ 
    while (n--) { 
    /* test body here */ 
    } 

    return Date.now() - start; 
} 

Khi chức năng được tạo Benchmark.js gọi nó để đo lường op của bạn cho một số lần lặp nhất định n. Quá trình này được lặp lại nhiều lần: tạo ra một hàm mới, gọi hàm đó để thu thập một mẫu đo lường. Số lần lặp được điều chỉnh giữa các mẫu để đảm bảo rằng chức năng chạy đủ lâu để cung cấp cho phép đo có ý nghĩa.

Điều quan trọng cần lưu ý ở đây là

  1. cả trường hợp của bạn và Benchmark.prototype.setup là những bản văn inlined;
  2. có một vòng lặp xung quanh hoạt động bạn muốn đo lường;

Về cơ bản, chúng tôi thảo luận về lý do tại sao đoạn code dưới đây với một biến địa phương x

function f(n) { 
    var start = Date.now(); 

    var x = "5555" 
    while (n--) { 
    var y = +x 
    } 

    return Date.now() - start; 
} 

chạy chậm hơn so với mã với biến toàn cầu x

function g(n) { 
    var start = Date.now(); 

    x = "5555" 
    while (n--) { 
    var y = +x 
    } 

    return Date.now() - start; 
} 

(Lưu ý: trường hợp này được gọi là địa phương biến số trong câu hỏi, nhưng không phải như vậy, x là global)

Điều gì sẽ xảy ra khi bạn thực hiện các chức năng này với một giá trị đủ lớn là n, ví dụ f(1e6)?

Đường ống tối ưu hóa hiện tại triển khai OSR theo cách đặc biệt. Thay vì tạo ra một phiên bản OSR cụ thể của mã được tối ưu hóa và loại bỏ nó sau này, nó tạo ra một phiên bản có thể được sử dụng cho cả OSR và mục nhập bình thường và thậm chí có thể được sử dụng lại nếu chúng ta cần thực hiện OSR tại cùng một vòng lặp. Điều này được thực hiện bằng cách tiêm một khối nhập đặc biệt OSR vào đúng vị trí trong biểu đồ luồng điều khiển.

OSR version of the control flow graph

OSR khối entry được tiêm khi SSA IR cho các chức năng được xây dựng và nó háo hức bản sao tất cả các biến địa phương ra khỏi tình trạng OSR đến. Kết quả là V8 không thấy rằng địa phương x thực sự là một hằng số và thậm chí mất bất kỳ thông tin về loại hình của nó. Đối với các lần tối ưu hóa tiếp theo, x2 có vẻ như nó có thể là bất cứ thứ gì.

Như x2 có thể là bất kỳ biểu thức nào +x2 cũng có thể có các tác dụng phụ tùy ý (ví dụ: nó có thể là đối tượng có valueOf được gắn với nó). Điều này ngăn chặn chuyển động mã biến đổi vòng lặp bất biến khi di chuyển +x2 ra khỏi vòng lặp.

Tại sao g nhanh hơn? V8 kéo một thủ thuật ở đây. Nó theo dõi các biến toàn cục có chứa hằng số: ví dụ: trong tiêu chuẩn này, toàn cầu x luôn chứa "5555" vì vậy V8 chỉ thay thế truy cập x với giá trị của nó và đánh dấu mã được tối ưu hóa này phụ thuộc vào giá trị của x. Nếu ai đó thay thế x giá trị với một cái gì đó khác với tất cả các mã phụ thuộc sẽ được deoptimized. Các biến toàn cầu cũng không phải là một phần của trạng thái OSR và không tham gia vào việc đổi tên SSA để V8 không bị nhầm lẫn bởi các hàm "hàm" tích hợp OSR và trạng thái nhập bình thường.Đó là lý do tại sao khi V8 tối ưu hóa g nó kết thúc lên tạo IR sau trong thân vòng lặp (sọc đỏ trên bên trái cho thấy vòng lặp):

IR before LICM

Lưu ý: +x được biên soạn để x * 1, nhưng điều này chỉ là một là chi tiết triển khai.

Sau đó LICM sẽ thực hiện thao tác này và di chuyển nó ra khỏi vòng lặp mà không có gì quan tâm trong chính vòng lặp đó. Điều này trở thành có thể vì bây giờ V8 biết rằng cả hai toán hạng của * đều là nguyên thủy - vì vậy có thể có không có tác dụng phụ.

IR after LICM

Và đó là lý do tại sao g là nhanh hơn, vì vòng lặp trống là khá rõ ràng là nhanh hơn so với một không trống. Điều này cũng có nghĩa là phiên bản thứ hai của điểm chuẩn không thực sự đo lường những gì bạn muốn nó đo lường, và trong khi phiên bản đầu tiên đã thực sự nắm bắt một số khác biệt giữa hiệu suất parseInt(x)+x được nhiều may mắn hơn: bạn nhấn một hạn chế trong đường ống tối ưu hóa hiện tại của V8 (Trục khuỷu) ngăn không cho nó ăn toàn bộ microbenchmark.

+0

... wow. Từ đồ thị biểu đồ dòng giấy đến một điều đồ thị tối ưu OSR V8 SSAIR phức tạp. Điều này có vẻ như một câu hỏi đơn giản như vậy, nhưng đây là một trong những câu trả lời phức tạp nhất mà tôi từng thấy. +1 – Cyoce

Các vấn đề liên quan