当编译一个循环时,turbofan似乎在大多数情况下都会剥离第一个循环迭代。例如,一个像:
function fill256(int32Array) {
var i = 255;
do {
int32Array[i] = 0;
} while(--i >= 0);
}
这样的循环会被优化成这样的机器代码
# rdx is int32Array
0x13f38bcef7a 5a 488b4a2f REX.W movq rcx,[rdx+0x2f]
0x13f38bcef7e 5e 488b7a3f REX.W movq rdi,[rdx+0x3f]
0x13f38bcef82 62 4c8b4237 REX.W movq r8,[rdx+0x37]
# peeled iteration
0x13f38bcef86 66 4881f9ff000000 REX.W cmpq rcx,0xff
0x13f38bcef8d 6d 0f8614010000 jna 0x13f38bcf0a7 <+0x187>
0x13f38bcef93 73 4e8d0c07 REX.W leaq r9,[rdi+r8*1]
0x13f38bcef97 77 41c781fc03000000000000 movl [r9+0x3fc],0x0 # dword store
0x13f38bcefa2 82 41b9fe000000 movl r9,0xfe
0x13f38bcefa8 88 e906000000 jmp 0x13f38bcefb3 <+0x93>
0x13f38bcefad 8d 0f1f00 nop
# loop proper
0x13f38bcefb0 90 4d8bcb REX.W movq r9,r11
# first iteration entry point:
0x13f38bcefb3 93 493b65e0 REX.W cmpq rsp,[r13-0x20] (external value (StackGuard::address_of_jslimit()))
0x13f38bcefb7 97 0f868b000000 jna 0x13f38bcf048 <+0x128>
0x13f38bcefbd 9d 458d59ff leal r11,[r9-0x1]
0x13f38bcefc1 a1 4d63e1 REX.W movsxlq r12,r9
0x13f38bcefc4 a4 4c3be1 REX.W cmpq r12,rcx
0x13f38bcefc7 a7 0f83e6000000 jnc 0x13f38bcf0b3 <+0x193>
0x13f38bcefcd ad 4e8d0c07 REX.W leaq r9,[rdi+r8*1]
0x13f38bcefd1 b1 43c704a100000000 movl [r9+r12*4],0x0 # dword store
0x13f38bcefd9 b9 4183fb00 cmpl r11,0x0
0x13f38bcefdd bd 7dd1 jge 0x13f38bcefb0 <+0x90>
这并不是特定的循环结构,但似乎是对所有小体循环的优化。循环中的 V8源码的评论只是说这是一个优化而已 但除了膨胀代码大小之外,它实际上能达到什么目的呢?
我知道剥离如果引入新的不变量会有好处。
好像剥皮第一次迭代是turbofan中提升循环语句的要求。
编译器技术负责人的这个介绍 似乎表明,剥皮是一种更安全地吊装代码的新方式。幻灯片17:
环路剥离 没有更多的非优化循环,因为积极的举起
而事实上,打败剥皮步骤对性能的影响很大。这个。
// var buf = new Int32Array(10000);
for (var i = 0; i < 10; ++i) {
if (i === 0) continue;
for (var j = 0; j < 1000; ++j) {
if (j === 0) continue;
buf[i*j] = i ^ j;
}
}
比这个慢了三倍。
for (var i = 0; i < 10; ++i)
for (var j = 0; j < 1000; ++j)
buf[i*j] = i ^ j;
因为对类型和范围的检查 buf
留在内环中。