为什么序列化指令固有的管道不友好?

时间:2017-03-04 23:10:22

标签: c++ assembly optimization serialization x86

为什么序列化指令固有地管道不友好?

在另一个答案[Deoptimizing a program for the pipeline in Intel Sandybridge-family CPUs]上声明了这一点:

  

每次迭代的时间独立,甚至比RDTSC更重。例如CPUID / RDTSC或进行系统调用的时间函数。序列化指令本质上是管道不友好的。

我认为应该是相反的。序列化指令非常适合管道。例如,

sum = 5 * sum;
sum = 5 * sum;
sum = 5 * sum;
sum = 5 * sum;
sum = 5 * sum;
sum = 5 * sum;
sum = 5 * sum;

g++ main.cpp -S汇总

addl    %edx, %eax
movl    %eax, -4(%rbp)
movl    -4(%rbp), %edx
movl    %edx, %eax
sall    $2, %eax
addl    %edx, %eax
movl    %eax, -4(%rbp)
movl    -4(%rbp), %edx
movl    %edx, %eax
sall    $2, %eax
addl    %edx, %eax
movl    %eax, -4(%rbp)
movl    -4(%rbp), %edx
movl    %edx, %eax
sall    $2, %eax
addl    %edx, %eax
movl    %eax, -4(%rbp)
movl    -4(%rbp), %edx
movl    %edx, %eax
sall    $2, %eax
addl    %edx, %eax
movl    %eax, -4(%rbp)
movl    -4(%rbp), %edx
movl    %edx, %eax
sall    $2, %eax
addl    %edx, %eax
movl    %eax, -4(%rbp)
movl    -4(%rbp), %edx
movl    %edx, %eax
sall    $2, %eax
addl    %edx, %eax

管道更好,而不是:

for( int i = 0; i < 7; i++ )
{
    sum = 5 * sum;
}

sum = sum + 5;

g++ main.cpp -S汇总

    movl    $0, -4(%rbp)
    movl    $0, -8(%rbp)
.L3:
    cmpl    $6, -8(%rbp)
    jg  .L2
    movl    -4(%rbp), %edx
    movl    %edx, %eax
    sall    $2, %eax
    addl    %edx, %eax
    movl    %eax, -4(%rbp)
    addl    $1, -8(%rbp)
    jmp .L3
.L2:
    addl    $5, -4(%rbp)
    movl    $0, %eax
    addq    $48, %rsp
    popq    %rbp

因为每次循环都是:

  1. 需要执行if( i < 7 )
  2. 添加分支预测,对于上述循环,我们可以假设第一次预测失败
  3. 指令sum = sum + 5将被丢弃。
  4. 下次管道将执行sum = 5 * sum
  5. 直到条件if( i < 7 )失败,
  6. 然后sum = 5 * sum将被丢弃
  7. 最终会处理sum = sum + 5

2 个答案:

答案 0 :(得分:4)

您将“序列化”与“序列化”混淆了。序列化指令是保证数据排序的指令,即此指令之前的所有内容都发生在此指令之后的所有内容之前。

这对于超标量和流水线处理器来说是个坏消息,这些处理器通常不能做出这种保证,并且必须对它进行特殊的指导,例如:通过刷新管道或等待所有执行单元完成。

顺便说一下,这有点像你想要的基准测试,因为它迫使管道进入可预测状态,所有执行单元都准备好执行你的代码;在基准测试之前没有过时的写入会导致任何性能偏差。

答案 1 :(得分:0)

我认为他将序列化视为依赖。

sum = 5 * sum;
sum = 5 * sum;
sum = 5 * sum;
sum = 5 * sum;
sum = 5 * sum;
sum = 5 * sum;
sum = 5 * sum;

会比并行版慢:

sum1 = 5 * sum1;
sum2 = 5 * sum2;
sum1 = 5 * sum1;
sum2 = 5 * sum2;
sum1 = 5 * sum1;
sum2 = 5 * sum2;
sum1 = 5 * sum1;
sum = sum2*sum1;

因为有多个管道,每个管道可以在飞行中的多个指令上工作,所以可能有sum1 sum2 ... sum8同时发出多个累加器。

如果串行器指令足够长,它会在N个周期后使管道准备好进行测量,因为新指令无法在不完成最后一个指令的情况下启动(对于串行器指令)。