JavaScript中Typed Arrays的优点是它们在C中的工作方式相同或相似吗?

我一直在使用JavaScript中的Typed Arrays 。

var buffer = new ArrayBuffer(16); var int32View = new Int32Array(buffer); 

我认为JavaScript中的正常数组( [1, 257, true])性能较差,因为它们的值可能是任何类型,因此,在内存中达到偏移并非易事。

我原本以为JavaScript数组下标的工作方式与对象相同(因为它们有许多相似之处),并且基于哈希映射 ,需要基于散列的查找。 但我没有找到太多可信的信息来证实这一点。

所以,我假设Typed Arrays表现如此出色的原因是因为它们像C中的普通数组一样工作,它们总是被输入。 鉴于上面的初始代码示例,并希望获得键入数组中的第10个值…

 var value = int32View[10]; 
  • 类型为Int32 ,因此每个值必须由32位或4个字节组成。
  • 下标是10
  • 因此,该值在内存中的位置是 + (4 * 10) ,然后读取4个字节以获得总值。

我基本上只想确认我的假设。 我的想法是否正确,如果没有,请详细说明。

我检查了V8源代码 ,看看我是否可以自己回答,但我的C生锈了,我对C ++不太熟悉。

由于性能原因,类型数组由WebGL标准委员会设计。 通常Javascript数组是通用的,可以保存对象,其他数组等等 – 并且元素在内存中不一定是顺序的,就像它们在C中一样.WebGL要求缓冲区在内存中是顺序的,因为这就是底层C API所期望的他们。 如果不使用Typed Arrays,将普通数组传递给WebGL函数需要大量工作:必须检查每个元素,检查类型,如果它是正确的(例如浮点数),则将其复制到单独的顺序类C缓冲区,然后将该顺序缓冲区传递给C API。 哎哟 – 很多工作! 对于性能敏感的WebGL应用程序,这可能会导致帧速率大幅下降。

另一方面,正如您在问题中所建议的那样,Typed Arrays在其幕后存储中已经使用了顺序C类缓冲区。 当您写入一个类型化数组时,您确实在后台分配了一个类似C的数组。 出于WebGL的目的,这意味着缓冲区可以由相应的C API直接使用。

请注意,您的内存地址计算还不够:浏览器还必须检查数组,以防止超出范围的访问。 这必须发生在任何类型的Javascript数组中,但在许多情况下,聪明的Javascript引擎可以省略检查,因为它可以certificate索引值已经在边界内(例如从0循环到数组的长度)。 它还必须检查数组索引是否真的是一个数字而不是字符串或其他东西! 但它本质上就像你描述的那样,使用类似C的寻址。

但是……那还不是全部! 在某些情况下,聪明的Javascript引擎也可以推断出普通Javascript数组的类型 。 在像V8这样的引擎中,如果你创建一个普通的Javascript数组并且只存储浮点数,V8可以乐观地决定它是一个浮点数组并优化它为它生成的代码。 然后,性能可以等同于类型化数组。 因此,实际上不需要类型化数组来达到最大性能:只需使用可预测的数组(每个元素都是相同的类型),并且某些引擎也可以对其进行优化。

那么为什么类型数组仍然需要存在?

  • 推断数组类型的优化非常复杂 。 如果V8推断出一个普通数组只有浮点数,那么你将一个对象存储在一个元素中, 它必须去优化并重新生成使该数组再次通用的代码。 所有这一切都是透明的,这是一项非常成就的成就。 类型化数组更加简单:它们保证是一种类型,并且您无法在其中存储其他类似对象的东西。
  • 永远不会保证优化; 您可以只在普通arrays中存储浮动,但引擎可能会出于各种原因决定不对其进行优化。
  • 它们更简单的事实意味着其他不太复杂的JavaScript引擎可以轻松实现它们。 他们不需要所有先进的去优化支持。
  • 即使使用非常先进的发动机,也可以使用certificate优化是非常困难的,有时甚至是不可能的。 类型化arrays显着简化了引擎需要围绕它进行优化的certificate级别。 从类型化数组返回的值肯定是某种类型,引擎可以针对该类型的结果进行优化。 从普通数组返回的值理论上可以是任何类型,并且引擎可能无法certificate它将始终具有相同类型的结果,因此生成效率较低的代码。 因此,更容易优化类型化数组周围的代码。
  • 类型化数组删除了出错的机会。 你不能意外地存储一个物体,突然间性能会更差。

因此,简而言之,普通数组在理论上可以与类型数组一样快。 但是,类型化arrays可以更轻松地达到最佳性能。

是的,你大多是正确的。 使用标准的JavaScript数组,JavaScript引擎必须假设数组中的数据都是对象。 它仍然可以将其存储为类似C的数组/向量,其中对内存的访问仍然与您描述的一样。 问题是数据不是值,而是引用该值(对象)的东西。

因此,执行a[i] = b[i] + 2需要引擎:

  1. 访问索引i的b中的对象;
  2. 检查对象的类型;
  3. 从对象中提取值;
  4. 将值加2;
  5. 使用4中新计算的值创建一个新对象;
  6. 将步骤5中的新对象分配到at索引i。

使用类型化数组,引擎可以:

  1. 访问索引i的b中的值(包括将其放入CPU寄存器);
  2. 将值递增2;
  3. 将步骤2中的新对象分配到at索引i。

注意:这些不是JavaScript引擎将执行的确切步骤,因为这取决于正在编译的代码(包括周围代码)和相关引擎。

这允许得到的计算更有效。 此外,类型化数组具有内存布局保证(n字节值的数组),因此可用于直接与数据(音频,video等)接口。

在性能方面,事情可以快速变化。 正如AshleysBrain所说,归结为VM是否可以推断出普通arrays可以快速准确地实现为类型化arrays。 这取决于特定JavaScript VM的特定优化,并且它可以在任何新的浏览器版本中更改。

此Chrome开发者评论提供了截至2012年6月的一些指导:

  1. 如果您执行大量顺序访问,则正常数组可以与类型数组一样快。 在数组边界之外的随机访问会导致数组增长。
  2. 类型化数组的访问速度很快,但分配速度很慢。 如果经常创建临时数组,请避免使用类型化数组。 (修复此问题是可能的,但它的优先级较低。)
  3. 诸如JSPerf之类的微基准测试对于实际性能来说并不可靠。

如果我可以详细说明最后一点,我已经用Java看过这种现象多年了。 当您通过一次又一次地单独运行来测试一小段代码的速度时,VM会优化它。 它使得优化只对特定测试有意义。 与在另一个程序中运行相同代码相比,您的基准测试可以获得百倍的速度提升,或者在运行几个不同的测试后立即运行它们,以便以不同的方式优化相同的代码。

我不是任何javascript引擎的贡献者,只在v8上有一些读数,所以我的答案可能不完全正确:

数组中的井值(只有没有孔/间隙的正常数组,不稀疏。稀疏数组被视为对象。)都是指针或具有固定长度的数字(在v8中它们是32位,如果是31位整数那么它最后标记为0位,否则它是指针)。

所以我不认为找到内存位置与typedArray有任何不同,因为整个数组的字节数是相同的。 但不同的是,如果它是一个对象,那么你必须添加一个拆箱层,这对普通的typedArrays不会发生。

当然,在访问typedArrays时,肯定没有普通数组所具有的类型检查(尽管可能会在高度优化的代码中删除,这只是为热代码生成的)。

对于写作,如果它是相同的类型不应该慢得多。 如果它是一个不同的类型,那么JS引擎可能会为它生成多态代码,这会更慢。

你也可以尝试在jsperf.com上做一些基准来确认。