我最近对元编程感兴趣。我们可以采用通用计算(在这种情况下为均值),并为它动态创建一个高效的,明确的函数。在这里,我将在mean2中创建一个函数,该函数将显式计算均值(无循环)。通常,这些显式功能运行速度更快。但是我正在经历一些有趣的行为。在我的时间安排中,对于在4e7循环中运行的大小为50的数组,显式函数会按预期轻松胜出:
符号:2479.273ms |文字:60.572ms
但是将初始数组大小从50缩小到55,性能急剧下降。
符号:2445.357ms |文字:3221.829ms
这可能是什么原因?
const A = new Float64Array(50).map(Math.random)
const mean1 = function (A) {
let sum = 0
for (let i = 0; i < A.length; i++)
sum += A[i]
return sum / A.length
}
const mean2 = (function (A) {
return new Function('A', `
return (${new Array(A.length).fill(null).map(function (_, i) {
return `A[${i}]`
}).join('+')}) / ${A.length}
`)
})(A)
console.time('symbolic')
for (let i = 0; i < 4e7; i++)
mean1(A)
console.timeEnd('symbolic')
console.time('literal')
for (let i = 0; i < 4e7; i++)
mean2(A)
console.timeEnd('literal')
答案 0 :(得分:0)
结果将在第二个示例中进行缓存,与“元编程”方法(或更具体地说,缺少循环)无关。
要用JavaScript直接证明并非易事;使用编译语言,通常只需要查看程序集,即可看到编译器已尝试以某种方式变得更聪明并优化了计时器内部的循环。 JS运行时显然更加不透明和复杂,输出不那么容易访问。相反,您可以通过代理来测试它,方法是观察不同但等效的表单的行为,看看是什么触发了前期优化。
在测试时,对“ mean2”函数的以下一些修改避免了nodejs(v8)中的缓存优化:
在返回之前分配给范围变量:
const mean2 = (function (A) {
return new Function('A', `
let sum = (${new Array(A.length).fill(null).map(function (_, i) {
return `A[${i}]`
}).join('+')}) / ${A.length};
return sum;
`)
})(A)
使用A.length
引用代替文字:
const mean2 = (function (A) {
return new Function('A', `
return (${new Array(A.length).fill(null).map(function (_, i) {
return `A[${i}]`
}).join('+')}) / A.length
`)
})(A)
尽管您不必担心这些字幕...而是改变测试方法,确保不会丢掉示例以产生相同的结果,否则就有可能冒险测试编译器忽略无用指令而不是实际性能的能力。 / p>
在这种情况下,请在每个样本之前使用随机值重新初始化数组,并且要绝对确保-根据每个样本的结果创建一些持久变量。另外,请勿在计时器中包括这些多余的步骤,否则会影响您的采样时间。