node.js 和 chrome 中的循环优化问题?
Loop optimization issues in node.js and chrome?
偶然看到一句话,避免在每次迭代时读取数组的长度属性可以节省执行时间。
不过我觉得无所谓,做了个实验
然后遇到了问题
这是代码。
// Function to get the execution time.
function testFunction (func) {
console.time(func.name)
for (let i = 0; i < 100; i++) {
func()
}
console.timeEnd(func.name)
}
// Init the array to iterate.
let arr = []
for (let i = 0; i < 10000000; i++) {
arr.push(0)
}
function loopWithSavedLength () {
let len = arr.length
for (let i = 0; i < len; i++) {
arr[i] = i
}
}
function loopWithoutSavedLength() {
for (let i = 0; i < arr.length; i++) {
arr[i] = i
}
}
testFunction(loopWithoutSavedLength)
testFunction(loopWithSavedLength)
而且输出很奇怪:
loopWithoutSavedLength: 889.633ms
loopWithSavedLength: 1023.269ms
我在Node.js 9.8.0(with v8 6.2.414.46-node.21)下试了很多次,loopWithoutSavedLength的执行时间总是比loopWithSavedLength的短。
我在 chrome 66.0.3359.181(使用 v8 6.6.346.32)控制台中执行相同的脚本,它们几乎相同。
loopWithoutSavedLength: 1475.060302734375ms
loopWithSavedLength: 1493.14892578125ms
然后我认为可能是赋值和测试空循环有问题。
这是新代码。
function assignmentLoopWithSavedLength () {
let len = arr.length
for (let i = 0; i < len; i++) {
arr[i] = i
}
}
function assignmentLoopWithoutSavedLength () {
for (let i = 0; i < arr.length; i++) {
arr[i] = i
}
}
function emptyLoopWithSavedLength () {
let len = arr.length
for (let i = 0; i < len; i++) {}
}
function emptyLoopWithoutSavedLength () {
for (let i = 0; i < arr.length; i++) {}
}
testFunction(emptyLoopWithSavedLength)
testFunction(emptyLoopWithoutSavedLength)
testFunction(assignmentLoopWithSavedLength)
testFunction(assignmentLoopWithoutSavedLength)
node.js中的结果:
emptyLoopWithSavedLength: 580.978ms
emptyLoopWithoutSavedLength: 584.923ms
assignmentLoopWithSavedLength: 1046.899ms
assignmentLoopWithoutSavedLength: 901.542ms
chrome 控制台中的结果:
emptyLoopWithSavedLength: 584.126953125ms
emptyLoopWithoutSavedLength: 892.776123046875ms
assignmentLoopWithSavedLength: 1455.418212890625ms
assignmentLoopWithoutSavedLength: 1449.7529296875ms
后来我意识到存储在 arr 中的值可能会影响结果并且确实如此。
新代码在这里:
let arr = []
function initArr () {
arr = []
for (let i = 0; i < 10000000; i++) {
arr.push(0)
}
}
function testFunction (func) {
initArr()
console.time(func.name)
for (let i = 0; i < 100; i++) {
func()
}
console.timeEnd(func.name)
}
node.js中的结果:
emptyLoopWithSavedLength: 560.739ms
emptyLoopWithoutSavedLength: 1134.274ms
assignmentLoopWithSavedLength: 1841.544ms
assignmentLoopWithoutSavedLength: 1609.649ms
chrome 控制台中的结果:
emptyLoopWithSavedLength: 592.8720703125ms
emptyLoopWithoutSavedLength: 910.886962890625ms
assignmentLoopWithSavedLength: 1457.467041015625ms
assignmentLoopWithoutSavedLength: 1488.855224609375ms
现在有新问题出现。
结论:
1. 为什么在node.js中,无论数组元素是否初始化为0,loopWithoutSavedLength时间总是小于loopWithSavedLength?
2. 为什么创建新数组并将其元素初始化为 0 会产生不同的结果?
这里是 V8 开发人员。
问题1:loopWithoutSavedLength更快,因为访问元素需要执行边界检查,无论如何都需要加载长度。如果循环的条件已经包含相同的检查,则可以消除它。如果您保存长度,编译器将更难消除额外的检查。所以实际上,手动保存长度意味着重复工作。也就是说,差异通常太小而不重要(甚至可以衡量)。有关详细信息,请参阅 https://mrale.ph/blog/2014/12/24/array-length-caching.html 上的精彩文章。
问题2:我不确定。元素的值无关紧要;而在 Chrome 他们没有。 Node.js 中的 V8 与 Chrome 中的行为相同并且应该执行相同的操作;但也许您测试过的两个 V8 版本之间存在差异。我对你在 Node.js 中获得的 emptyLoopWithoutSavedLength
结果感到困惑,它显然毫无理由地从 584 变为 1134 -- 也许你系统中的其他原因导致了暂时的减速?你能重现这个结果吗?
说到重现:当我重复 运行 这些测试时,我看到重复 运行 相同测试的性能差异约为 10%(例如我得到的结果 assignmentLoopWithSavedLength
在 10 运行 的过程中介于 1075 和 1222 之间)。这并不罕见;现代计算机是复杂的机器,具有许多层和许多性能变化源。这只是意味着,当您执行两个测试中的单个 运行 时,您会看到 1400 和 1450 毫秒,这可能没有任何意义——下次它们可能会交换结果。如果你看到 1455 和 1449,差异几乎可以肯定是噪声。
偶然看到一句话,避免在每次迭代时读取数组的长度属性可以节省执行时间。
不过我觉得无所谓,做了个实验
然后遇到了问题
这是代码。
// Function to get the execution time.
function testFunction (func) {
console.time(func.name)
for (let i = 0; i < 100; i++) {
func()
}
console.timeEnd(func.name)
}
// Init the array to iterate.
let arr = []
for (let i = 0; i < 10000000; i++) {
arr.push(0)
}
function loopWithSavedLength () {
let len = arr.length
for (let i = 0; i < len; i++) {
arr[i] = i
}
}
function loopWithoutSavedLength() {
for (let i = 0; i < arr.length; i++) {
arr[i] = i
}
}
testFunction(loopWithoutSavedLength)
testFunction(loopWithSavedLength)
而且输出很奇怪:
loopWithoutSavedLength: 889.633ms
loopWithSavedLength: 1023.269ms
我在Node.js 9.8.0(with v8 6.2.414.46-node.21)下试了很多次,loopWithoutSavedLength的执行时间总是比loopWithSavedLength的短。
我在 chrome 66.0.3359.181(使用 v8 6.6.346.32)控制台中执行相同的脚本,它们几乎相同。
loopWithoutSavedLength: 1475.060302734375ms
loopWithSavedLength: 1493.14892578125ms
然后我认为可能是赋值和测试空循环有问题。
这是新代码。
function assignmentLoopWithSavedLength () {
let len = arr.length
for (let i = 0; i < len; i++) {
arr[i] = i
}
}
function assignmentLoopWithoutSavedLength () {
for (let i = 0; i < arr.length; i++) {
arr[i] = i
}
}
function emptyLoopWithSavedLength () {
let len = arr.length
for (let i = 0; i < len; i++) {}
}
function emptyLoopWithoutSavedLength () {
for (let i = 0; i < arr.length; i++) {}
}
testFunction(emptyLoopWithSavedLength)
testFunction(emptyLoopWithoutSavedLength)
testFunction(assignmentLoopWithSavedLength)
testFunction(assignmentLoopWithoutSavedLength)
node.js中的结果:
emptyLoopWithSavedLength: 580.978ms
emptyLoopWithoutSavedLength: 584.923ms
assignmentLoopWithSavedLength: 1046.899ms
assignmentLoopWithoutSavedLength: 901.542ms
chrome 控制台中的结果:
emptyLoopWithSavedLength: 584.126953125ms
emptyLoopWithoutSavedLength: 892.776123046875ms
assignmentLoopWithSavedLength: 1455.418212890625ms
assignmentLoopWithoutSavedLength: 1449.7529296875ms
后来我意识到存储在 arr 中的值可能会影响结果并且确实如此。
新代码在这里:
let arr = []
function initArr () {
arr = []
for (let i = 0; i < 10000000; i++) {
arr.push(0)
}
}
function testFunction (func) {
initArr()
console.time(func.name)
for (let i = 0; i < 100; i++) {
func()
}
console.timeEnd(func.name)
}
node.js中的结果:
emptyLoopWithSavedLength: 560.739ms
emptyLoopWithoutSavedLength: 1134.274ms
assignmentLoopWithSavedLength: 1841.544ms
assignmentLoopWithoutSavedLength: 1609.649ms
chrome 控制台中的结果:
emptyLoopWithSavedLength: 592.8720703125ms
emptyLoopWithoutSavedLength: 910.886962890625ms
assignmentLoopWithSavedLength: 1457.467041015625ms
assignmentLoopWithoutSavedLength: 1488.855224609375ms
现在有新问题出现。
结论:
1. 为什么在node.js中,无论数组元素是否初始化为0,loopWithoutSavedLength时间总是小于loopWithSavedLength?
2. 为什么创建新数组并将其元素初始化为 0 会产生不同的结果?
这里是 V8 开发人员。
问题1:loopWithoutSavedLength更快,因为访问元素需要执行边界检查,无论如何都需要加载长度。如果循环的条件已经包含相同的检查,则可以消除它。如果您保存长度,编译器将更难消除额外的检查。所以实际上,手动保存长度意味着重复工作。也就是说,差异通常太小而不重要(甚至可以衡量)。有关详细信息,请参阅 https://mrale.ph/blog/2014/12/24/array-length-caching.html 上的精彩文章。
问题2:我不确定。元素的值无关紧要;而在 Chrome 他们没有。 Node.js 中的 V8 与 Chrome 中的行为相同并且应该执行相同的操作;但也许您测试过的两个 V8 版本之间存在差异。我对你在 Node.js 中获得的 emptyLoopWithoutSavedLength
结果感到困惑,它显然毫无理由地从 584 变为 1134 -- 也许你系统中的其他原因导致了暂时的减速?你能重现这个结果吗?
说到重现:当我重复 运行 这些测试时,我看到重复 运行 相同测试的性能差异约为 10%(例如我得到的结果 assignmentLoopWithSavedLength
在 10 运行 的过程中介于 1075 和 1222 之间)。这并不罕见;现代计算机是复杂的机器,具有许多层和许多性能变化源。这只是意味着,当您执行两个测试中的单个 运行 时,您会看到 1400 和 1450 毫秒,这可能没有任何意义——下次它们可能会交换结果。如果你看到 1455 和 1449,差异几乎可以肯定是噪声。