【问题标题】:Why is if(1) faster than if(true)为什么 if(1) 比 if(true) 快
【发布时间】:2020-08-12 21:51:33
【问题描述】:

我正在尝试用 JavaScript 和画布制作 Conway 的生命游戏,我有一个 1280x720 的矩阵用于存储单元格数据,我目前将数据存储为 1 = 活着,0 = 死亡,然后当我检查一个细胞是否活着时,我只是这样做:if(matrix[i][j]) 我很好奇这是否可以改进并在 https://jsbench.me/ 复制了一个类似的场景并注意到如果使用“真/假”,整个事情会慢 +-11%,为什么会这样?不是应该更快吗?

示例基准,只需将 1 更改为 true 即可测试其他场景

let array = []
for(let i = 0; i<1000000; i++){
   array.push(1)
}
let sum = 0
for(let i = 0; i<1000000;i++){
    if(array[i]){
        sum++
    }
}

【问题讨论】:

  • 你能分享你的基准代码吗?如果您使用的是布尔值,它应该是相同的,但可能还有其他事情会导致这一点。
  • @PatrickRoberts 我更新了问题
  • @PabloKvitca 更新
  • 您在哪个浏览器中测量了性能?
  • @PatrickRoberts Google Chrome

标签: javascript arrays performance v8


【解决方案1】:

您看到的性能差异并不完全是由于if 语句评估,而是由于访问值(1true)的数组元素类型。 V8 引擎区分arrays of different element kinds1 的数组将被视为PACKED_SMI_ELEMENTS,而true 的数组将被视为PACKED_ELEMENTS。因此,使用布尔元素的版本会慢一些。

作为说明,这里是在数组元素类型之间应用的相对性能优化的网格,左上角的性能最佳,右下角的性能最差:

并且here's a benchmark 将您的两个测试与我基于my comment below 添加的测试进行比较:

【讨论】:

  • 所以我最好的选择是继续使用 1/0? , 还有其他方法可以提高性能吗?
  • @Specy 您可以使用长度为 921600 (1280x720) 的 Uint8Array 并通过 if (matrix[j * 1280 + i]) 访问每个索引。如果您想以较小的速度使用更少的内存,您甚至可以为每个索引存储 8 个单元格并使用位运算来访问每个单元格的数据。
  • 如果您希望提高游戏的性能,这是康威的人生游戏,您应该查看实际代码中的瓶颈。不是人为的基准。但是,如果在您的代码中迭代一组值,例如在基准测试中,您可能会更好地使用 PACKED_SMI_ELEMENTS(即 1/0)。您可能会获得一些性能,如 Patrick 所说,将 2D 数组更改为 1D 实现。
  • 这个答案不正确;所有元素类型的迭代速度完全相同(您可以查看生成的优化代码自己验证!)。这里观察到的差异是由于两件事:(1) 大约 90% 的总时间花费在 创建 数组上,这意味着重复增长它,这必须做更多(与 GC 相关的)工作PACKED_ELEMENTS 个。 (2) 最后迭代时,if 语句在加载对象时,必须检查该对象是否为真值,而对于 SMI 数组,它可以简单地进行 `!= 0` 比较,这样更快.
  • @jmrk (1) 数组创建不包括在基准的定时区域内,因此这无关紧要。 (2) 数组元素种类会影响元素访问的性能(进而影响迭代)。正如here 所证明的那样,这与条件语句中的“真实”评估无关。事实上,在当前版本的 Chrome 中,布尔条件比数字条件要快一些。
【解决方案2】:

(这里是 V8 开发人员。)

简而言之,1/0 版本更快,因为数组的元素种类有助于 if 语句做更少的工作。

加长版: 正如@PatrickRoberts 指出的那样,V8 会跟踪存储在数组中的值的类型。这种机制是相当粗粒度的,它只区分“just integers”、“just doubles”和“anything”。 if(array[i]),当它知道数组只包含整数时,可以简单地与 0 进行比较以查看是否应采用分支。它不会比这更快。但是,如果数组包含“任何东西”(包括 true),那么根据 JavaScript 的语义,V8 必须检查加载的值是否为“true-ish”,即在条件上下文中评估为 true。相反,即检查虚假值实际上更容易/更快,因此 V8 检查:值是false?是""吗?它是一个数字(可能是 0)吗?它是 BigInt(可能是 0n)吗?是document.all(特别好玩的古代特例遗物)?任何其他计算结果为true在这种特殊情况下,立即检查true 是“聪明的”/幸运的,但引擎不知道这一点,而且这种启发式一般不会有好处。

(请注意,得出if(1)if(true) 快的结论是错误的——特别重要的是条件中的值是从数组加载的,并且该数组跟踪可能的范围值,这会影响随后需要或不需要对加载的值进行的检查。当您使用常量 1true 时,两个评估具有相同的速度(事实上,在大多数情况下,优化编译器会完全删除它们,因为if(true) 是真的,呵呵!)。)

也就是说,您看到的大部分差异并不是由于这个原因,因为测试在第一个循环中花费了超过 90% 的时间来填充数组。将一个数组从长度 0 增加到一百万意味着它的后备存储需要重复扩展,这意味着分配一个新的后备存储并复制所有现有元素。这是仅整数元素具有速度优势的另一种操作:它们可以使用批量复制操作,移动数据的速度与 CPU 访问内存的速度一样快。然而,在“anything”数组中,垃圾收集器必须执行额外的传递以查看是否有任何值是它感兴趣的引用。在这种情况下,所有值都是 true 哨兵,它们不是,但 GC 不检查就无法知道。

【讨论】:

  • 非常感谢您的解释,我已经做了一些其他测试,最后我将二维数组转换为一维数组,我将使用 Uint8Array 并存储 1/0 .这应该会提高一点性能。有人建议我使用 bitset,所以我也在研究。
  • @Specy 是我建议的。这不是让你的程序更快,而是消耗更少的内存。
猜你喜欢
  • 2013-08-10
  • 2011-10-06
  • 2021-07-11
  • 1970-01-01
  • 2021-10-26
  • 2016-08-31
  • 2019-12-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多