首页 > 解决方案 > 为什么涡扇会剥小圈?

问题描述

在编译循环时,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 源代码注释只是说这是一种优化,但它实际上除了增加代码大小之外还有什么作用呢?

我知道如果引入新的不变量,剥离可能是有益的。

标签: assemblyoptimizationx86-64v8micro-optimization

解决方案


似乎剥离第一次迭代是 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保留在内部循环中。


推荐阅读