Krz*_*nde 64 javascript performance v8
我一直在尝试进行实验,看看函数中的局部变量是否存储在堆栈中.
所以我写了一点性能测试
function test(fn, times){
var i = times;
var t = Date.now()
while(i--){
fn()
}
return Date.now() - t;
}
ene
function straight(){
var a = 1
var b = 2
var c = 3
var d = 4
var e = 5
a = a * 5
b = Math.pow(b, 10)
c = Math.pow(c, 11)
d = Math.pow(d, 12)
e = Math.pow(e, 25)
}
function inversed(){
var a = 1
var b = 2
var c = 3
var d = 4
var e = 5
e = Math.pow(e, 25)
d = Math.pow(d, 12)
c = Math.pow(c, 11)
b = Math.pow(b, 10)
a = a * 5
}
Run Code Online (Sandbox Code Playgroud)
我希望能够更快地完成逆功能工作.取而代之的是一个惊人的结果.
直到我测试它运行的一个功能比测试第二个功能快10倍.
例:
> test(straight, 10000000)
30
> test(straight, 10000000)
32
> test(inversed, 10000000)
390
> test(straight, 10000000)
392
> test(inversed, 10000000)
390
Run Code Online (Sandbox Code Playgroud)
以其他顺序测试时的行为相同.
> test(inversed, 10000000)
25
> test(straight, 10000000)
392
> test(inversed, 10000000)
394
Run Code Online (Sandbox Code Playgroud)
我已经在Chrome浏览器和Node.js中对它进行了测试,我完全不知道为什么会这样.效果持续到我刷新当前页面或重新启动Node REPL.
什么可能是这种重要(约12倍)性能的来源?
PS.由于它似乎仅适用于某些环境,请编写您用来测试它的环境.
我的是:
操作系统:Ubuntu 14.04
节点v0.10.37
Chrome 43.0.2357.134(官方版本)(64位)
/编辑
在Firefox 39上,无论顺序如何,每次测试需要约5500毫秒.它似乎只发生在特定的引擎上.
/ Edit2
将函数内联到测试函数使其始终同时运行.
是否可能有一个优化内联函数参数,如果它始终是相同的函数?
Vya*_*rov 102
一旦你test使用两个不同的函数fn()调用它内部的callsite就变成了变形,而V8无法内联它.
o.m(...)V8中的函数调用(与方法调用相反)伴随着一个元素内联缓存,而不是真正的多态内联缓存.
由于V8无法在fn()调用点内联,因此无法对代码应用各种优化.如果你在IRHydra中查看你的代码(我为了你的方便而上传了编译工件),你会发现第一个优化版本test(当它专门用于它时fn = straight)有一个完全空的主循环.
V8刚刚内联straight并删除了您希望通过Dead Code Elimination优化进行基准测试的所有代码.在V8的旧版本而不是DCE V8上只需通过LICM将代码提升出循环 - 因为代码完全是循环不变的.
什么时候straight没有内联V8不能应用这些优化 - 因此性能差异.V8的新版本将仍然适用DCE到straight和inversed自己把他们变成空函数
所以性能差异不是那么大(大约2-3倍).较旧的V8对于DCE来说不够具有侵略性 - 并且在内联和非内联案例中表现出更大的差异,因为内联案例的峰值性能仅仅是积极的循环不变代码运动(LICM)的结果.
在相关的说明中,这表明为什么基准测试永远不应该这样写 - 因为他们的结果没有任何用处,因为你最终测量一个空循环.
如果您对多态性及其在V8中的含义感兴趣,请查看我的帖子"单态性是什么"("并非所有高速缓存都相同"部分讨论与函数调用相关的高速缓存).我还建议阅读我关于微基准测试危险的一些讨论,例如最近的GOTO芝加哥2015年的"Benchmarking JS"演讲(视频) - 它可以帮助你避免常见的陷阱.
Lua*_*aan 17
你误解了堆栈.
虽然"真正的"堆栈确实只有Push和Pop操作,但这并不适用于用于执行的堆栈类型.除了Push和之外Pop,您还可以随机访问任何变量,只要您拥有其地址即可.这意味着本地的顺序无关紧要,即使编译器没有为您重新排序.在伪装配中,你似乎认为如此
var x = 1;
var y = 2;
x = x + 1;
y = y + 1;
Run Code Online (Sandbox Code Playgroud)
翻译成类似的东西
push 1 ; x
push 2 ; y
; get y and save it
pop tmp
; get x and put it in the accumulator
pop a
; add 1 to the accumulator
add a, 1
; store the accumulator back in x
push a
; restore y
push tmp
; ... and add 1 to y
Run Code Online (Sandbox Code Playgroud)
事实上,真正的代码更像是这样的:
push 1 ; x
push 2 ; y
add [bp], 1
add [bp+4], 1
Run Code Online (Sandbox Code Playgroud)
如果线程堆栈确实是一个真正的,严格的堆栈,这是不可能的,真的.在这种情况下,操作和当地人的顺序比现在要重要得多.相反,通过允许随机访问堆栈上的值,您可以为编译器和CPU节省大量工作.
为了回答你的实际问题,我怀疑这些功能实际上都没有做任何事情.你只是修改本地,你的函数没有返回任何东西 - 编译器完全删除函数体,甚至函数调用都是完全合法的.如果确实如此,那么无论你观察到什么性能差异,可能只是一个测量工件,或与调用函数/迭代的固有成本相关的东西.