2014-07-20 19 views
15

Tại sao TypedArrays không nhanh hơn các mảng thông thường? Tôi muốn sử dụng giá trị precalc cho CLZ (tính toán hàm số 0 đứng đầu). Và tôi không muốn họ giải thích như các đối tượng thông thường?Hiệu suất của JavaScript TypedArray

http://jsperf.com/array-access-speed-2/2

đang chuẩn bị:

Benchmark.prototype.setup = function() { 
    var buffer = new ArrayBuffer(0x10000); 
    var Uint32 = new Uint32Array(buffer); 
    var arr = []; 
    for(var i = 0; i < 0x10000; ++i) { 
    Uint32[i] = (Math.random() * 0x100000000) | 0; 
    arr[i] = Uint32[i]; 
    } 
    var sum = 0; 
}; 

Test 1:

sum = arr[(Math.random() * 0x10000) | 0]; 

thử nghiệm 2:

sum = Uint32[(Math.random() * 0x10000) | 0]; 

enter image description here

PS Có thể các bài kiểm tra perf của tôi không hợp lệ, cảm thấy tự do để sửa tôi.

+0

Bạn có xóa các kiểm tra jsperf không? Tôi không thể truy cập chúng nữa – Bergi

+0

Không, tôi đã không xóa nó. Điều này thật kỳ lạ. –

+1

Rất, rất lạ là chúng sẽ biến mất như thế! Tất cả trong số họ ... Đặc biệt kỳ lạ trong ánh sáng của [mục FAQ này] (http://jsperf.com/faq#test-availability). Tôi đã tìm kiếm trong trường hợp URL thay đổi hoặc một cái gì đó, và ... không có gì. Tôi có thể tìm thấy các xét nghiệm khác về hiệu suất mảng, nhưng không phải của Sukhanov và của tôi. Tôi đã nêu ra [vấn đề cho nó] (https://github.com/mathiasbynens/jsperf.com/issues/197). Tất nhiên, nó có thể sẽ không làm tổn thương nếu nhiều người trong chúng ta [quyên góp cho jsPerf] (http://jsperf.com/faq#donate) (và tôi đã làm ... lần đầu tiên * đầu vịt *). –

Trả lời

27
var buffer = new ArrayBuffer(0x10000); 
var Uint32 = new Uint32Array(buffer); 

không phải là điều tương tự như:

var Uint32 = new Uint32Array(0x10000); 

không phải vì các ArrayBuffer mới (bạn luôn nhận được một bộ đệm mảng: xem Uint32.buffer trong cả hai trường hợp) nhưng vì tham số chiều dài: với ArrayBuffer bạn có 1 byte cho mỗi phần tử, với Uint32Array bạn có 4 byte cho mỗi phần tử.

Vì vậy, trong trường hợp đầu tiên (và trong mã của bạn), Uint32.length = 0x1000/4 và các vòng lặp của bạn vượt quá giới hạn 3 trong số 4 lần. Nhưng thật đáng buồn là bạn sẽ không bao giờ gặp lỗi, chỉ có màn trình diễn kém.

Sử dụng 'ArrayBuffer mới', bạn phải khai báo uint32 như thế này:

var buffer = new ArrayBuffer(0x10000 * 4); 
var Uint32 = new Uint32Array(buffer); 

Xem jsperf with (0x10000)jsperf with (0x10000 * 4).

+3

Thật đáng tiếc khi mọi người chấp nhận câu trả lời dựa trên kích thước nội dung, nhưng không phải là chính xác. – MorrisLiang

+1

@MorrisLiang, đó là những gì tôi nghĩ là tốt, nhưng sau đó tôi nhận ra câu trả lời này đã được đăng hơn nửa năm sau câu hỏi. – user2033427

+0

@MorrisLiang: Lưu ý rằng câu trả lời của tôi * luôn luôn * sử dụng 'new Uint32Array (0x10000)' để so sánh táo với táo dài khôn ngoan, và vẫn tiết lộ sự khác biệt về tốc độ. Nhưng tôi đã không gọi ra sự khác biệt theo cách radsoc đã làm ở trên. –

25

Động cơ hiện đại sẽ sử dụng mảng thực sau hậu trường ngay cả khi bạn sử dụng Array nếu họ nghĩ rằng chúng có thể rơi xuống bản đồ thuộc tính "mảng" nếu bạn làm điều gì đó khiến họ nghĩ rằng họ không thể sử dụng đúng mảng.

Cũng lưu ý rằng như radsoc points out, var buffer = new ArrayBuffer(0x10000) sau đó var Uint32 = new Uint32Array(buffer) tạo ra một mảng uint32 có kích thước là 0x4000 (0x10000/4), không 0x10000, bởi vì giá trị mà bạn cung cấp cho ArrayBuffer là tính theo byte, nhưng tất nhiên có bốn byte cho mỗi Uint32Array nhập . Tất cả những điều sau đây sử dụng new Uint32Array(0x10000) thay vì (và luôn làm như vậy, ngay cả trước chỉnh sửa này) để so sánh táo với táo.

Vì vậy, hãy bắt đầu ở đó, với new Uint32Array(0x10000): http://jsperf.com/array-access-speed-2/11(buồn bã, JSPerf đã mất bài kiểm tra này và kết quả của nó, và bây giờ đang ẩn hoàn toàn)

graph showing roughly equivalent performance

Điều đó cho thấy rằng vì bạn' tái điền vào mảng theo một cách đơn giản, có thể dự đoán được, một động cơ hiện đại tiếp tục sử dụng một mảng thực (với lợi ích hiệu suất của chúng) dưới các bìa chứ không phải chuyển dịch. Chúng tôi nhìn thấy cơ bản cùng một hiệu suất cho cả hai. Sự khác biệt về tốc độ có thể liên quan đến chuyển đổi loại có giá trị Uint32 và gán nó cho sum làm number (mặc dù tôi ngạc nhiên nếu chuyển đổi loại đó không bị trì hoãn ...).

Thêm một số hỗn loạn, mặc dù:

var Uint32 = new Uint32Array(0x10000); 
var arr = []; 
for (var i = 0x10000 - 1; i >= 0; --i) { 
    Uint32[Math.random() * 0x10000 | 0] = (Math.random() * 0x100000000) | 0; 
    arr[Math.random() * 0x10000 | 0] = (Math.random() * 0x100000000) | 0; 
} 
var sum = 0; 

... để động cơ đã rơi trở lại trên bản đồ bất động sản kiểu cũ "mảng", và bạn thấy rằng mảng đánh máy rõ rệt làm tốt hơn cái cũ lỗi thời loại: http://jsperf.com/array-access-speed-2/3(buồn bã, JSPerf đã mất bài kiểm tra này và kết quả của nó)

bar graph showing marked performance improvement for typed arrays

Clever, những JavaSc kỹ sư động cơ ript ...

Điều cụ thể bạn làm với bản chất không phải mảng của các vấn đề mảng Array, mặc dù; xem xét:

var Uint32 = new Uint32Array(0x10000); 
var arr = []; 
arr.foo = "bar";       // <== Non-element property 
for (var i = 0; i < 0x10000; ++i) { 
    Uint32[i] = (Math.random() * 0x100000000) | 0; 
    arr[i] = (Math.random() * 0x100000000) | 0; 
} 
var sum = 0; 

Điều đó vẫn lấp đầy mảng dự đoán, nhưng chúng tôi thêm thuộc tính không phải là phần tử (foo) vào đó. http://jsperf.com/array-access-speed-2/4(buồn bã, JSPerf đã mất bài kiểm tra này và kết quả của nó) Rõ ràng, động cơ là khá thông minh, và giữ cho rằng tài sản phi yếu tố sang một bên trong khi tiếp tục sử dụng một mảng đúng đối với các thuộc tính nguyên tố:

bar graph showing performance improvement for standard arrays when <code>Array</code> array gets non-element property

Tôi đang mất một chút để giải thích tại sao mảng chuẩn nên nhận được nhanh hơn ở đó so với thử nghiệm đầu tiên ở trên. Lỗi đo lường? Âm đạo trong Math.random? Nhưng chúng tôi vẫn khá chắc chắn rằng dữ liệu mảng cụ thể trong các Array vẫn là một mảng thực sự.

Trong khi đó, nếu chúng ta làm điều tương tự nhưng điền theo thứ tự ngược:

var Uint32 = new Uint32Array(0x10000); 
var arr = []; 
arr.foo = "bar";       // <== Non-element property 
for (var i = 0x10000 - 1; i >= 0; --i) { // <== Reverse order 
    Uint32[i] = (Math.random() * 0x100000000) | 0; 
    arr[i] = (Math.random() * 0x100000000) | 0; 
} 
var sum = 0; 

... chúng tôi lấy lại cho mảng gõ thắng ra   — trừ trên IE11: http://jsperf.com/array-access-speed-2/9(buồn bã, JSPerf có mất bài kiểm tra này và kết quả của nó)

graph showing typed arrays winning except on IE11

+2

Thú vị, cảm ơn. Mọi thứ trở nên phức tạp hơn trong trường hợp sử dụng thực, nơi phân bổ/disallocations liên tục của các đối tượng/mảng khác nhau dẫn đến phân mảnh bộ nhớ và thời gian xử lý phát nổ nếu bộ nhớ không được phân bổ trước. Trong thực tế, Typed Array sẽ tốn ít hơn vì chúng được phân bổ trước (Rq: js Mảng có thể được preallocated ...) – GameAlchemist

+0

giải thích tuyệt vời, cảm ơn bạn vì điều này. – qodeninja

+0

Viết các giá trị int32 đã được xử lý trước để nhập mảng nhanh. Mảng không chứa các lỗ (vâng, các lỗ rất nhanh nhưng…). Mảng được preallocated. Vì vậy, tạo và khởi tạo mảng được đánh máy nhanh. Nhưng khi bạn cố gắng đọc từ Uint32Array giá trị cần được chuyển đổi thành gấp đôi (xem câu trả lời của tôi bên dưới). Bạn nên sử dụng Int32Array (trên x64) hoặc Int16Array (trên x86) (xem câu trả lời của tôi dưới đây một lần nữa). – vitaliydev

0

Trong trường hợp của bạn, lý do hiệu suất kém là bạn cố gắng đọc bên ngoài mảng khi sử dụng Uint32Array do lỗi có độ dài mảng.

Nhưng nếu đó không phải là lý do thực sự thì:

Hãy thử sử dụng Int32Array thay vì Uint32Array. Tôi nghĩ rằng trong các biến V8 không thể có loại uint32 nhưng có thể có int32/đôi/con trỏ. Vì vậy, khi bạn gán kiểu uint32 cho biến nó sẽ được chuyển đổi thành gấp đôi chậm hơn.

Nếu bạn sử dụng phiên bản V8 32 bit thì các biến có thể có loại int31/double/pointer. Vì vậy, int32 sẽ được chuyển đổi thành đôi quá. Nhưng nếu bạn sử dụng mảng thông thường và tất cả các giá trị là int31 thì chuyển đổi là không cần thiết để mảng thông thường có thể nhanh hơn.

Cũng sử dụng int16 có thể yêu cầu một số chuyển đổi để có được int32 (do dấu và bổ sung '). Uint16 sẽ không yêu cầu chuyển đổi vì V8 chỉ có thể thêm số 0 ở bên trái.

PS. Bạn có thể quan tâm đến con trỏ và int31 (hoặc int32 trên x64) là những thứ tương tự trong V8. Điều này cũng có nghĩa là int32 sẽ yêu cầu 8 byte trên x64. Đây cũng là lý do tại sao không có loại int32 trên x86: bởi vì nếu chúng ta sử dụng tất cả 32 bit để lưu trữ số nguyên, chúng ta sẽ không có bất kỳ không gian nào để lưu con trỏ nữa.