Javascript 为什么if(1)比if(true)快

Javascript 为什么if(1)比if(true)快,javascript,arrays,performance,v8,Javascript,Arrays,Performance,V8,我试图用JavaScript和画布制作Conway的生命游戏,我有一个1280x720的矩阵,我用它来存储单元格数据,我目前将数据存储为1=活着,0=死亡,然后当我检查单元格是否活着时,我只需执行以下操作:ifmatrix[I][j] 我很好奇这是否可以改进,并在实验室做了一些测试 复制一个类似的场景,并注意到如果使用true/false,整个过程会慢+-11%,为什么会这样?不是应该快一点吗 示例基准测试,只需将1更改为true即可测试其他场景 let array = [] for(let i

我试图用JavaScript和画布制作Conway的生命游戏,我有一个1280x720的矩阵,我用它来存储单元格数据,我目前将数据存储为1=活着,0=死亡,然后当我检查单元格是否活着时,我只需执行以下操作:ifmatrix[I][j] 我很好奇这是否可以改进,并在实验室做了一些测试 复制一个类似的场景,并注意到如果使用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发动机可区分两种发动机。1的数组将被视为压缩元素,而true的数组将被视为压缩元素。因此,使用布尔元素的版本会稍微慢一点

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

并将您的两个测试与我添加的测试进行比较:


您看到的性能差异严格来说不是由于if语句求值,而是由于从中访问值1或true的数组元素类型。V8发动机可区分两种发动机。1的数组将被视为压缩元素,而true的数组将被视为压缩元素。因此,使用布尔元素的版本会稍微慢一点

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

并将您的两个测试与我添加的测试进行比较:

这里是V8开发者

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

较长版本: 正如@PatrickRoberts所指出的,V8跟踪存储在数组中的值的类型。这种机制是相当粗粒度的,它只区分整数、双精度和任何东西。当ifarray[i]知道数组只包含整数时,可以简单地与0进行比较,看看是否应该执行分支。没有比这更快的了。但是,如果数组包含任何包含true的内容,那么根据JavaScript的语义,V8必须检查加载的值是否为true,即在条件上下文中计算为true。相反,即检查假值实际上更容易/更快,因此V8检查:值是否为假?它是?这是一个可能是0的数字吗?它是一个可能是0n的BigInt吗?这是一份特别有趣的古代特例文物吗?其他任何东西都是正确的。在这种特殊情况下,立即检查true是明智的/幸运的,但引擎无法知道这一点,这样的启发式通常不会有好处

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

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

V8开发者在这里

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

较长版本: 正如@PatrickRoberts所指出的,V8跟踪存储在数组中的值的类型。这种机制是相当粗粒度的,它只区分整数、双精度和任何东西。当ifarray[i]知道数组只包含整数时,可以简单地与0进行比较,看看是否应该执行分支。没有比这更快的了。如果数组包含任何包含 然而,根据JavaScript的语义,V8必须检查加载的值是否为true,即在条件上下文中计算为true。相反,即检查假值实际上更容易/更快,因此V8检查:值是否为假?它是?这是一个可能是0的数字吗?它是一个可能是0n的BigInt吗?这是一份特别有趣的古代特例文物吗?其他任何东西都是正确的。在这种特殊情况下,立即检查true是明智的/幸运的,但引擎无法知道这一点,这样的启发式通常不会有好处

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



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

您能分享来自基准测试的代码吗?如果你用的是布尔函数,它应该是一样的,但可能还有其他的原因促成了这一点。@PatrickRoberts我更新了question@PabloKvitca更新您在哪个浏览器中测量性能?@PatrickRoberts Google Chromecould您可以共享来自基准测试的代码吗?如果你用的是布尔函数,它应该是一样的,但可能还有其他的原因促成了这一点。@PatrickRoberts我更新了question@PabloKvitca更新您在哪个浏览器中测量性能?@PatrickRoberts Google ChromeSo我的最佳选择是继续使用1/0,还有其他方法可以提高性能吗?@Specy您可以使用长度为921600 1280x720的UINT8数组,并通过if矩阵[j*1280+I]访问每个索引。如果您想以较小的速度成本使用更少的内存,您甚至可以在每个索引中存储8个单元,并使用位运算来访问每个单元的数据。如果您希望提高您的游戏(康威的生命游戏)的性能,您应该查看实际代码中的瓶颈。不是人为的基准。但是,如果在代码中迭代一组值,比如在基准测试中,那么使用压缩的元素(即1/0)可能会更好。如Patrick所说,更改2D阵列以实现一维实现可能会获得一些性能;迭代速度对于所有元素都是完全相同的,您可以查看生成的优化代码,以便自己验证!。这里观察到的差异是由两件事造成的:1大约90%的总时间用于创建数组,这意味着要重复地增长它,这必须为压缩元素做更多与GC相关的工作。2当最后迭代时,if语句在加载对象时必须检查该对象是否为真ish值,而对于SMI数组,它只需执行`!=0`比较,速度更快。@jmrk 1数组创建不包括在基准测试的定时区域内,因此这与此无关。2数组元素类型影响元素访问的性能,从而影响迭代。事实证明,这与条件语句中的真正ish求值无关。事实上,在当前版本的Chrome中,布尔条件比数字条件稍微快一些。所以我的最佳选择是继续使用1/0,还有其他方法可以提高性能吗?@Specy您可以使用长度为921600 1280x720的UINT8数组,并通过if矩阵[j*1280+I]访问每个索引。如果您想以较小的速度成本使用更少的内存,您甚至可以在每个索引中存储8个单元,并使用位运算来访问每个单元的数据。如果您希望提高您的游戏(康威的生命游戏)的性能,您应该查看实际代码中的瓶颈。不是人为的基准。但是,如果在代码中迭代一组值,比如在基准测试中,那么使用压缩的元素(即1/0)可能会更好。对于Patric这样的一维实现,更改二维阵列可能会获得一些性能
这个答案是不正确的;迭代速度对于所有元素都是完全相同的,您可以查看生成的优化代码,以便自己验证!。这里观察到的差异是由两件事造成的:1大约90%的总时间用于创建数组,这意味着要重复地增长它,这必须为压缩元素做更多与GC相关的工作。2当最后迭代时,if语句在加载对象时必须检查该对象是否为真ish值,而对于SMI数组,它只需执行`!=0`比较,速度更快。@jmrk 1数组创建不包括在基准测试的定时区域内,因此这与此无关。2数组元素类型影响元素访问的性能,从而影响迭代。事实证明,这与条件语句中的真正ish求值无关。事实上,在当前版本的Chrome中,布尔条件比数值条件稍微快一些。非常感谢您的解释,我已经做了一些其他测试,最后我将把2d数组转换成1d数组,我将使用Uint8Array并存储1/0。这将使性能提高一点。有人建议我使用位集,所以我也在研究它。@Specy就是我建议的。这并不是为了让你的程序更快,而是为了消耗更少的内存。非常感谢你的解释,我已经做了一些其他的测试,最后我将把2d数组变成1d数组,我将使用Uint8Array并存储1/0。这将使性能提高一点。有人建议我使用位集,所以我也在研究它。@Specy就是我建议的。这并不是让你的程序更快,而是消耗更少的内存。