在尝试为自己构建Webgl 3d库时(主要是学习目的)我遵循了从各种来源发现的文档,其中声明TypedArray函数set()(特别是Float32Array),应该是“跟C中的memcpy一样快(显然是脸颊上的舌头),根据html5rocks字面上最快。看起来是正确的外观(在javascript中没有循环设置,消失在一些uberfast类型数组纯C无意义等等)。
我在glMatrix采取了一个勇气(顺便说一下好了!),并注意到他(作者)说他为了速度而展开了所有的循环。这显然是一个javascript专家通常会以尽可能快的速度做的事情,但是,基于我之前的阅读,我认为我在这个库上有1-up,特别是,他创建了他的lib以使用这两个数组并输入数组,因此我认为通过使用“set()”可以获得更高的速度,因为我只对保留类型数组类型感兴趣。
为了测试我的理论,我设置了这个jsperf。 set()相对缺乏速度,我尝试过的所有其他技术(在jsperf中)都胜过它。这是迄今为止最慢的。
最后,我的问题:为什么?我理论上可以理解一个循环展开在spidermonkey或chrome V8 js-engines中变得高度优化,但是丢失到for循环看起来很荒谬(jsperf中的copy2),特别是如果它的意图理论上是由于原始连续而加速副本内存数据类型(typedarrays)。无论哪种方式,感觉set()函数都被破坏了。
这是我的代码吗?我的浏览器? (即时使用Firefox 24)还是我错过了其他一些优化理论?理解这一相反结果的任何帮助对我的想法和理解都会非常有帮助。
答案 0 :(得分:2)
这是一个老问题,但如果您有特殊需要优化某些效果不佳的代码,则有理由使用TypedArray
。了解JavaScript中TypedArray
个对象的重要一点是它们是 views ,它们代表ArrayBuffer
内的范围字节。底层ArrayBuffer
实际上表示要操作的二进制数据的连续块,但我们需要一个视图来访问和操作该二进制数据的窗口。
多个不同的ArrayBuffer
对象可以查看同一TypedArray
中的单独(甚至重叠)范围。当您有两个共享相同TypedArray
的{{1}}个对象时,ArrayBuffer
操作极快。这是因为机器正在使用连续的内存块。
这是一个例子。我们将创建一个32个字节的set
,一个长度为16 ArrayBuffer
来表示缓冲区的前16个字节,另一个长度为16 Uint8Array
来表示最后16个字节:
Uint8Array
现在我们可以在缓冲区的前半部分初始化一些值:
var buffer = new ArrayBuffer(32);
var array1 = new Uint8Array(buffer, 0, 16);
var array2 = new Uint8Array(buffer, 16, 16);
然后非常有效地将这8个字节复制到缓冲区的后半部分:
for (var i = 0; i < 16; i++) array1[i] = i;
console.log(array1); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
console.log(array2); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
我们可以通过查看另一个视图的缓冲区来确认这两个数组实际上共享同一个缓冲区。例如,我们可以使用跨越缓冲区的整个32个字节的长度为8 array2.set(array1);
console.log(array1); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
console.log(array2); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
:
Uint32Array
我修改了一个JSPerf测试,我发现它可以在同一个缓冲区上展示一个副本的巨大性能提升:
http://jsperf.com/typedarray-set-vs-loop/3
我们在Chrome和Firefox上的性能提升了一个数量级,它甚至比采用正常的双倍长度数组并将前半部分复制到下半部分要快得多。但我们必须考虑周期/内存权衡。只要我们引用{em> var array3 = new Uint32Array(buffer)
console.log(array3); // [50462976, 117835012, 185207048, 252579084,
// 50462976, 117835012, 185207048, 252579084]
的任何单个视图,就不能对其余的缓冲区数据进行垃圾回收。为ES7 Harmony提出了一个ArrayBuffer.transfer
函数,它可以解决这个问题,它使我们能够在不等待垃圾收集器的情况下显式释放内存,并且能够动态增长ArrayBuffer
而无需复制。
答案 1 :(得分:0)
好set
并没有像这样的简单语义,在执行some figuring out of what should be done之后在V8中它将基本上到达exactly the same loop其他方法直接在第一个进行的地方。
请注意,如果你正确地玩牌(所有的测试都是这样),Javascript会被编译成高度优化的机器码,所以不应该有#34;崇拜&#34;一些方法只是因为它们是原生的&#34;。
答案 2 :(得分:0)
我也一直在探索set()的执行情况,我不得不说对于较小的块(例如原始海报使用的16个索引),set()仍然比可比的展开慢约5倍循环,即使在连续的内存块上运行。
我已经改编了原始的jsperf测试here。我认为可以说,对于像这样的小块传输,set()根本无法与展开的索引分配性能竞争。对于更大的块传输(如在sbking测试中所见),set()确实表现更好,但它与100万个数组索引操作竞争,所以看起来很难通过一条指令来解决这个问题
我的测试中的连续缓冲区set()的执行效果略好于单独的缓冲区set(),但同样,在这种传输大小下,性能优势是微不足道的