为什么 if(1) 比 if(true) 快
Why is if(1) faster than if(true)
我正在尝试在 JavaScript 和 canvas 中制作 Conway 的人生游戏,我有一个 1280x720 的矩阵用于存储单元格数据,我目前将数据存储为1 = 活着,0 = 死了,然后当我检查一个细胞是否活着时,我只是做:if(matrix[i][j])
我很好奇这是否可以改进并在
https://jsbench.me/ 复制类似的场景,发现如果使用“true/false”,整个过程会慢 +-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++
}
}
您看到的性能差异严格来说并不是由于 if
语句评估,而是由于访问值(1
或 true
)的数组元素种类从。 V8引擎区分arrays of different element kinds。 1
的数组将被视为 PACKED_SMI_ELEMENTS
,而 true
的数组将被视为 PACKED_ELEMENTS
。因此,使用布尔元素的版本会慢一点。
作为说明,这是在数组元素种类之间应用的相对性能优化的格子,左上角是最佳性能,右下角是最差性能:
和here's a benchmark comparing both your tests to one I added based on :
(此处为 V8 开发人员。)
简而言之,1/0 版本更快,因为数组的元素种类有助于 if
语句完成更少的工作。
更长的版本:
正如@PatrickRoberts 指出的那样,V8 会跟踪存储在数组中的值的类型。这种机制相当coarse-grained,它只区分“只是整数”、“只是双打”和“任何东西”。 if(array[i])
,当它知道数组只包含整数时,可以简单地与 0 进行比较,看看是否应该采取分支。没有比这更快的了。但是,如果数组包含“任何东西”(包括 true
),那么根据 JavaScript 的语义,V8 必须检查加载的值是否为“true-ish”,即计算为在条件上下文中为真。相反,即检查 false-ish 值,实际上是 easier/faster,因此 V8 检查:值是 false
吗?是""
吗?它是一个数字(可能是 0)吗?它是 BigInt(可能是 0n)吗?是document.all
(特别好玩的special-case古代遗物)吗?任何其他计算结果为 true
。 在这种特殊情况下,立即检查 true
将是“聪明的”/幸运的,但引擎不知道这一点,这样的启发式方法不会总的来说是有益的。
(请注意,得出 if(1)
比 if(true)
快的结论是错误的 - 特别重要的是条件中的值是从数组中加载的,并且这个数组保持跟踪可能值的范围,这会影响随后需要或不需要对加载值进行的检查。当您使用常量 1
和 true
时,两个评估具有相同的速度(事实上,在大多数情况下,优化编译器会完全删除它们,因为当然 if(true)
是正确的,duh!)。)
也就是说,您看到的大部分差异不是因为这个,因为测试在第一个循环中花费了 90% 以上的时间来填充数组。将数组从长度 0 增长到一百万意味着它的后备存储需要重复扩展,这意味着要分配一个新的后备存储并复制所有现有元素。这是 integer-only 元素具有速度优势的另一个操作:它们可以使用批量复制操作,移动数据的速度与 CPU 可以访问内存的速度一样快。然而,在“任何”数组中,垃圾收集器必须执行额外的传递以查看是否有任何值是它感兴趣的引用。在这种情况下,所有值都是 true
哨兵,但它们不是,但 GC 不检查就无法知道。
我正在尝试在 JavaScript 和 canvas 中制作 Conway 的人生游戏,我有一个 1280x720 的矩阵用于存储单元格数据,我目前将数据存储为1 = 活着,0 = 死了,然后当我检查一个细胞是否活着时,我只是做:if(matrix[i][j])
我很好奇这是否可以改进并在
https://jsbench.me/ 复制类似的场景,发现如果使用“true/false”,整个过程会慢 +-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++
}
}
您看到的性能差异严格来说并不是由于 if
语句评估,而是由于访问值(1
或 true
)的数组元素种类从。 V8引擎区分arrays of different element kinds。 1
的数组将被视为 PACKED_SMI_ELEMENTS
,而 true
的数组将被视为 PACKED_ELEMENTS
。因此,使用布尔元素的版本会慢一点。
作为说明,这是在数组元素种类之间应用的相对性能优化的格子,左上角是最佳性能,右下角是最差性能:
和here's a benchmark comparing both your tests to one I added based on
(此处为 V8 开发人员。)
简而言之,1/0 版本更快,因为数组的元素种类有助于 if
语句完成更少的工作。
更长的版本:
正如@PatrickRoberts 指出的那样,V8 会跟踪存储在数组中的值的类型。这种机制相当coarse-grained,它只区分“只是整数”、“只是双打”和“任何东西”。 if(array[i])
,当它知道数组只包含整数时,可以简单地与 0 进行比较,看看是否应该采取分支。没有比这更快的了。但是,如果数组包含“任何东西”(包括 true
),那么根据 JavaScript 的语义,V8 必须检查加载的值是否为“true-ish”,即计算为在条件上下文中为真。相反,即检查 false-ish 值,实际上是 easier/faster,因此 V8 检查:值是 false
吗?是""
吗?它是一个数字(可能是 0)吗?它是 BigInt(可能是 0n)吗?是document.all
(特别好玩的special-case古代遗物)吗?任何其他计算结果为 true
。 在这种特殊情况下,立即检查 true
将是“聪明的”/幸运的,但引擎不知道这一点,这样的启发式方法不会总的来说是有益的。
(请注意,得出 if(1)
比 if(true)
快的结论是错误的 - 特别重要的是条件中的值是从数组中加载的,并且这个数组保持跟踪可能值的范围,这会影响随后需要或不需要对加载值进行的检查。当您使用常量 1
和 true
时,两个评估具有相同的速度(事实上,在大多数情况下,优化编译器会完全删除它们,因为当然 if(true)
是正确的,duh!)。)
也就是说,您看到的大部分差异不是因为这个,因为测试在第一个循环中花费了 90% 以上的时间来填充数组。将数组从长度 0 增长到一百万意味着它的后备存储需要重复扩展,这意味着要分配一个新的后备存储并复制所有现有元素。这是 integer-only 元素具有速度优势的另一个操作:它们可以使用批量复制操作,移动数据的速度与 CPU 可以访问内存的速度一样快。然而,在“任何”数组中,垃圾收集器必须执行额外的传递以查看是否有任何值是它感兴趣的引用。在这种情况下,所有值都是 true
哨兵,但它们不是,但 GC 不检查就无法知道。