2014-07-20 31 views
15

为什么TypedArray不比通常的数组快?我想为CLZ使用precalc值(计算前导零功能)。我不希望他们像往常一样解释对象?Javascript TypedArray性能

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

制备代码:

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

测试1:

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

试验2:

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

enter image description here

PS可能是我的性能测试无效,随时纠正我。

+0

您是否删除了jsperf测试?我无法再访问它们 – Bergi

+0

不,我没有删除它。这很奇怪。 –

+1

很奇怪,他们会像那样消失!所有这些......根据[这个FAQ条目](http://jsperf.com/faq#test-availability)特别奇怪。我搜索过万一URL改变了,或者什么都没有。我可以找到阵列表现的其他测试,但不是Sukhanov和我的。我提出了一个[问题](https://github.com/mathiasbynens/jsperf.com/issues/197)。当然,如果我们中的更多人[捐献给jsPerf](http://jsperf.com/faq#donate)(我刚刚做了......第一次*鸭头*),它可能不会受到伤害。 –

回答

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

是不一样的东西:

var Uint32 = new Uint32Array(0x10000); 

因为新ArrayBuffer不(你总是得到一个数组缓冲区:看Uint32.buffer在这两种情况下),但由于长度参数:与ArrayBuffer你有每个元素1个字节,Uint32Array每个元素4个字节。因此,在第一种情况下(和在你的代码中),Uint32.length = 0x1000/4,并且你的循环超出4次3次。但遗憾的是,你永远不会得到错误,只有糟糕的表演。

使用 '新ArrayBuffer',你必须声明UINT32这样的:

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

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

+3

人们接受基于内容大小而不是正确性的答案是一种耻辱。 – MorrisLiang

+1

@MorrisLiang,这就是我的想法,但后来我意识到这个答案在问题发布半年后才发布。 – user2033427

+0

@MorrisLiang:请注意,我的回答*总是使用'​​new Uint32Array(0x10000)'来比较苹果和苹果的长度,并且仍然显示速度差异。但我并没有像以往的radsoc那样呼唤差异。 –

25

即使您使用Array,现代引擎也会在幕后使用真实数组,如果您认为他们可以使用Array,如果您执行的操作使他们认为无法使用真数组,则会回退到属性映射“数组”。

还要注意,如radsoc points outvar buffer = new ArrayBuffer(0x10000)然后var Uint32 = new Uint32Array(buffer)产生UINT32数组,其尺寸是0x4000的(0x10000的/ 4),而不是为0x10000,因为你给ArrayBuffer值以字节为单位,但当然也有每Uint32Array条目四个字节。以下全部使用new Uint32Array(0x10000)而不是(并且始终如此,甚至在此编辑之前)来比较苹果和苹果。

所以,让我们从这里开始,与new Uint32Array(0x10000)http://jsperf.com/array-access-speed-2/11(可悲的是,JSPerf已经失去了本次测试及其结果,而现在处于脱机状态完全)

graph showing roughly equivalent performance

这表明,因为你”以简单,可预测的方式重新填充阵列,现代引擎继续使用真正的阵列(以及其性能优势),而不是转移。我们看到两者的表现基本相同。速度的差异可能与类型转换有关,其中的值为Uint32,并将其作为sum作为number(尽管如果该类型转换未延迟,我感到很惊讶...)。

添加一些混乱,但:

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; 

...使发动机具有依傍老式的属性映射“阵列”,你看到被打的阵列明显优于老式类型:http://jsperf.com/array-access-speed-2/3(可悲的是,JSPerf已经失去了这个测试及其结果)

bar graph showing marked performance improvement for typed arrays

聪明,这些JavaSc ript引擎工程师...

但是,您对Array数组的非数组属性所做的具体事情很重要;考虑:

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; 

这仍然填充阵列可预见的,但我们非元素属性(foo)添加到它。 http://jsperf.com/array-access-speed-2/4(可悲的是,JSPerf已经失去了这个测试及其结果)显然,引擎是相当聪明,并保留非元素属性到一边,而继续使用的元素属性一个真正的数组:

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

我在一个小的损失来解释为什么标准阵列应该得到更快有相比,我们的第一个测试上方。测量错误?迷宫在Math.random?但是我们仍然确信Array中的阵列特定数据仍然是一个真正的数组。

而如果我们做同样的事情,但以相反的顺序填写:

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; 

...我们回到类型数组胜出  —除了在IE11:http://jsperf.com/array-access-speed-2/9(可悲的是,JSPerf有失去了这个测试及其结果)

graph showing typed arrays winning except on IE11

+2

有趣,谢谢。在实际使用情况下,情况会变得更加复杂,在这种情况下,各种对象/数组的连续分配/重新分配会导致内存碎片,并且如果未预先分配内存,处理时间会爆炸。在现实生活中,Typed Array会花费更少,因为它们是预先分配的(Rq:js Arrays可能是预先分配的......) – GameAlchemist

+0

美丽的解释,谢谢。 – qodeninja

+0

将预先调用的int32值写入键入的数组很快。该阵列不包含洞(是的,洞很快,但...)。数组是预先分配的。所以创建和初始化类型化数组很快。但是当你尝试从Uint32Array读取时,该值应该被转换为double(见下面的答案)。您应该使用Int32Array(在x64上)或Int16Array(在x86上)(请参阅下面的答案)。 – vitaliydev

0

在你的情况下,性能不佳的原因是你尝试使用Uint32Array读取数组之外的数组,因为数组长度错误。

但是,如果它不会是那么的真实原因:

尝试改用Uint32Array的Int32Array。我认为在V8变量中不能有uint32类型,但可以有int32/double /指针。所以当你将uint32类型赋值给变量时,它将被转换为较慢的double。

如果使用32位版本的V8,那么变量可以有int31/double /指针类型。所以int32也会被转换成double。但是如果你使用普通的数组并且所有的值都是int31,那么不需要转换,所以通常的数组可以更快。

另外使用int16可能需要一些转换来获得int32(因为符号和1的补码)。 Uint16不需要转换,因为V8可以在左侧添加零。

PS。你可能会感兴趣的指针和int31(或x64上的int32)在V8中是一样的。这也意味着int32在x64上需要8个字节。这也是x86上没有int32类型的原因:因为如果我们使用全部32位来存储整数,我们就不会有任何空间来保存指针了。