繁体   English   中英

JavaScript 数组性能在 13k-16k 元素时下降

[英]JavaScript array performance drop at 13k-16k elements

我正在对数组的创建和编辑性能进行一些性能测试,并注意到具有大约 13k-16k 元素的数组周围有一些奇怪的特征。

下图显示了创建数组并从中读取每个元素所需的时间(在这种情况下,将其中的数字相加)。 capacitypush与创建阵列的方式有关:

  • 容量const arr = new Array(length)然后arr[i] = data
  • const arr = []; 然后arr.push(data)

基于长度创建数组的时间 基于长度的数组操作时间

如您所见,在这两种情况下,创建一个数组并从中读取,与减少 1k 个元素的每个元素的性能相比,性能降低了大约 2-3 倍。
使用 push 方法创建数组时,与预先使用正确容量创建数组相比,此跳转发生得更早一些。 我认为发生这种情况是因为,当推送到已经处于最大容量的阵列时,添加的额外容量超过了实际需要的额外容量(以避免很快再次添加新容量),并且达到了较慢性能路径的阈值早些时候。

如果想看代码或者自己测试: github

我的问题,为什么性能下降到 13k-16k 左右?

对我来说,在 v8 中似乎对较大的数组进行了不同的处理,从大约 13k-16k 元素开始以提高它们的性能,但是截止点(至少在我的代码中)有点太早了,所以在优化之前性能下降了任何好处。

您可以看到性能改进在大约 500 个元素后下降,并在下降后再次上升。

可悲的是,我找不到任何有关此的信息。

另外,如果您碰巧知道为什么在使用容量创建和通过推送求和时会出现这些峰值,请随时告诉我:)

编辑:

正如@ggorlen 所建议的那样,我在另一台机器上运行相同的测试以排除缓存作为所见行为的原因(使用不同的、较弱的 CPU 和更少的 RAM)。 结果似乎非常相似。

在不同机器上根据长度创建数组的时间 在不同机器上根据长度对数组进行操作的时间

编辑:

我使用--allow-natives-syntax标志运行节点来调试使用%DebugPrint(array); ,希望看到不同数组长度之间的差异,但除了长度和内存地址之外,它们看起来都一样。 这是一个例子:

 // For array created with capacity DebugPrint: 000002CB8E1ACE19: [JSArray] - map: 0x035206283321 <Map(HOLEY_SMI_ELEMENTS)> [FastProperties] - prototype: 0x036b86245b19 <JSArray[0]> - elements: 0x02cb8e1ace39 <FixedArray[1]> [HOLEY_SMI_ELEMENTS] - length: 1 - properties: 0x0114c5d01309 <FixedArray[0]> - All own properties (excluding elements): { 00000114C5D04D41: [String] in ReadOnlySpace: #length: 0x03f907ac1189 <AccessorInfo> (const accessor descriptor), location: descriptor } 0000035206283321: [Map] - type: JS_ARRAY_TYPE - instance size: 32 - inobject properties: 0 - elements kind: HOLEY_SMI_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x035206283369 <Map(PACKED_SMI_ELEMENTS)> - prototype_validity cell: 0x03f907ac15e9 <Cell value= 1> - instance descriptors #1: 0x009994a6aa31 <DescriptorArray[1]> - transitions #1: 0x009994a6a9d1 <TransitionArray[4]>Transition array #1: 0x0114c5d05949 <Symbol: (elements_transition_symbol)>: (transition to PACKED_DOUBLE_ELEMENTS) -> 0x0352062832d9 <Map(PACKED_DOUBLE_ELEMENTS)> - prototype: 0x036b86245b19 <JSArray[0]> - constructor: 0x031474c124e9 <JSFunction Array (sfi = 000003CECD93C3A9)> - dependent code: 0x0114c5d01239 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0 // For array created with push DebugPrint: 000003B09882CE19: [JSArray] - map: 0x02ff94f83369 <Map(PACKED_SMI_ELEMENTS)> [FastProperties] - prototype: 0x0329b3805b19 <JSArray[0]> - elements: 0x03b09882ce39 <FixedArray[17]> [PACKED_SMI_ELEMENTS] - length: 1 - properties: 0x03167aa81309 <FixedArray[0]> - All own properties (excluding elements): { 000003167AA84D41: [String] in ReadOnlySpace: #length: 0x02094f941189 <AccessorInfo> (const accessor descriptor), location: descriptor } 000002FF94F83369: [Map] - type: JS_ARRAY_TYPE - instance size: 32 - inobject properties: 0 - elements kind: PACKED_SMI_ELEMENTS - unused property fields: 0 - enum length: invalid - back pointer: 0x03167aa81599 <undefined> - prototype_validity cell: 0x02094f9415e9 <Cell value= 1> - instance descriptors #1: 0x00d25122aa31 <DescriptorArray[1]> - transitions #1: 0x00d25122aa01 <TransitionArray[4]>Transition array #1: 0x03167aa85949 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x02ff94f83321 <Map(HOLEY_SMI_ELEMENTS)> - prototype: 0x0329b3805b19 <JSArray[0]> - constructor: 0x009ff8a524e9 <JSFunction Array (sfi = 0000025A84ABC3A9)> - dependent code: 0x03167aa81239 <Other heap object (WEAK_FIXED_ARRAY_TYPE)> - construction counter: 0

编辑

从大小为 13_994 的数组变为 13_995 时,求和的性能下降:

在此处输入图像描述

(这里是 V8 开发人员。)

这里有两个单独的效果:

(1) 在 16384 个元素处发生的情况是后备存储被分配在“大对象空间”中,这是针对大对象优化的堆的特殊区域。 在启用了指针压缩的 Chrome 中,发生这种情况的元素数量恰好是禁用指针压缩的 Node 中的两倍。 其结果是分配本身不能再作为内联指令序列直接在优化代码中发生; 相反,它是对 C++ 函数的调用; 除了有一些调用开销之外,它还是一个更通用的实现,因此速度有点慢(可能有一些优化潜力,不确定)。 所以这不是太早开始的优化。 这只是巨大的物体所付出的代价。 并且它只会在分配许多大型数组然后对它们没有太多作用的情况下(微小的微基准测试?)突出显示。

(2) 在 13995 个元素处发生的情况是对于特定函数sum ,即优化编译器启动“OSR”(堆栈上替换)函数时,即函数在运行时被优化代码替换。 无论何时发生,这都是一定的一次性成本,并且很快就会收回成本。 因此,将其视为特定时间的特定命中是典型的微基准测试工件,与实际使用无关。 (例如,如果您在同一进程中多次运行测试,则不会再看到 13995 处的步骤。如果您以多种尺寸运行它,则可能不需要 OSR(因为该函数可以下次调用时切换到优化代码)并且这种一次性成本根本不会发生。)

TL;DR:这里没什么可看的,只是产生令人困惑的伪影的微基准。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM