Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/69.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 优化简单的模具操作,将变量保留在寄存器中_C_Optimization_X86_Cpu Registers_Cpu Cache - Fatal编程技术网

C 优化简单的模具操作,将变量保留在寄存器中

C 优化简单的模具操作,将变量保留在寄存器中,c,optimization,x86,cpu-registers,cpu-cache,C,Optimization,X86,Cpu Registers,Cpu Cache,我试图使下面的代码更快地将两个变量(我们需要重用的变量)保留在寄存器中或比缓存更近的任何位置。该代码将数组中位于idx位置的三个相邻元素相加 void stencil(double * input, double * output){ unsigned int idx = 1; output[0] = input[0] + input[1]; for(; idx < SIZE - 1; idx++){ output[idx] = input[id

我试图使下面的代码更快地将两个变量(我们需要重用的变量)保留在寄存器中或比缓存更近的任何位置。该代码将数组中位于
idx
位置的三个相邻元素相加

void stencil(double * input, double * output){

    unsigned int idx = 1;
    output[0] = input[0] + input[1];

    for(; idx < SIZE - 1; idx++){
        output[idx] = input[idx-1] + input[idx] + input[idx+1];
    }

    output[idx] = input[idx-1] + input[idx];
}
使用
-march=native
标志时,如下所示:

stencil:
.LFB20:
        .cfi_startproc
        vmovsd  (%rdi), %xmm1
        vxorpd  %xmm0, %xmm0, %xmm0
        leaq    144(%rdi), %rdx
        leaq    136(%rsi), %rax
        xorl    %ecx, %ecx
        .p2align 4,,10
        .p2align 3
.L2:
        vaddsd  %xmm1, %xmm0, %xmm0
        vmovsd  -136(%rdx), %xmm4
        prefetcht0      (%rdx)
        addl    $8, %ecx
        prefetchw       (%rax)
        addq    $64, %rdx
        addq    $64, %rax
        vaddsd  %xmm1, %xmm4, %xmm1
        vaddsd  %xmm4, %xmm0, %xmm0
        vmovsd  %xmm0, -200(%rax)
        vmovsd  -192(%rdx), %xmm3
        vaddsd  %xmm3, %xmm1, %xmm1
        vaddsd  %xmm3, %xmm4, %xmm4
        vmovsd  %xmm1, -192(%rax)
        vmovsd  -184(%rdx), %xmm2
        vaddsd  %xmm2, %xmm4, %xmm4
        vaddsd  %xmm2, %xmm3, %xmm3
        vmovsd  %xmm4, -184(%rax)
        vmovsd  %xmm4, -184(%rax)
        vmovsd  -176(%rdx), %xmm0
        vaddsd  %xmm0, %xmm3, %xmm3
        vaddsd  %xmm0, %xmm2, %xmm2
        vmovsd  %xmm3, -176(%rax)
        vmovsd  -168(%rdx), %xmm1
        vaddsd  %xmm1, %xmm2, %xmm2
        vaddsd  %xmm1, %xmm0, %xmm0
        vmovsd  %xmm2, -168(%rax)
        vmovsd  -160(%rdx), %xmm2
        vaddsd  %xmm2, %xmm0, %xmm0
        vaddsd  %xmm2, %xmm1, %xmm1
        vmovsd  %xmm0, -160(%rax)
        vmovsd  -152(%rdx), %xmm0
        vaddsd  %xmm0, %xmm1, %xmm1
        vaddsd  %xmm0, %xmm2, %xmm2
        vmovsd  %xmm1, -152(%rax)
        vmovsd  -144(%rdx), %xmm1
        vaddsd  %xmm1, %xmm2, %xmm2
        vmovsd  %xmm2, -144(%rax)
        cmpl    $1399999992, %ecx
        jne     .L2
        movabsq $11199999944, %rdx
        movabsq $11199999936, %rcx
        addq    %rdi, %rdx
        addq    %rsi, %rcx
        xorl    %eax, %eax
        jmp     .L3
        .p2align 4,,7
        .p2align 3
.L4:
        vmovaps %xmm2, %xmm1
.L3:
        vaddsd  %xmm0, %xmm1, %xmm0
        vmovsd  (%rdx,%rax), %xmm2
        vaddsd  %xmm2, %xmm0, %xmm0
        vmovsd  %xmm0, (%rcx,%rax)
        addq    $8, %rax
        vmovaps %xmm1, %xmm0
        cmpq    $56, %rax
        jne     .L4
        vaddsd  %xmm2, %xmm1, %xmm1
        movabsq $11199999992, %rax
        vmovsd  %xmm1, (%rsi,%rax)
        ret

有人对如何让GCC将变量保存到寄存器中以加快代码速度有什么建议吗?或者通过任何其他方式使代码绕过缓存而有效?

使用寄存器旋转时,通常最好展开循环。除非明确要求,否则gcc不会这样做

下面是一个4级循环展开的示例

void stencil(double * input, double * output){

    double x, y, z, w, u, v ;
    x=0.0;
    y=input[0];
    int idx=0;
    for(; idx < SIZE - 5; idx+=4){
      z=input[idx+1];
      w=input[idx+2];
      u=input[idx+3];
      v=input[idx+4];

      output[idx]  =x+y+z;
      output[idx+1]=y+z+w;
      output[idx+2]=z+w+u;
      output[idx+3]=w+u+v;

      x=u;
      y=v;
    }
    z=input[idx+1];
    w=input[idx+2];
    u=input[idx+3];

    output[idx]  =x+y+z;
    output[idx+1]=y+z+w;
    output[idx+2]=z+w+u;
    output[idx+3]=w+u;
}
void模具(双*输入,双*输出){
双x,y,z,w,u,v;
x=0.0;
y=输入[0];
int-idx=0;
对于(;idx
通过idx值有一个内存读写,每两个idx值有一个寄存器拷贝

可以尝试不同的展开级别,但每次迭代总是有2个寄存器副本,4个似乎是一个很好的折衷方案

如果大小不是4的倍数,则需要开场白

void stencil(double * input, double * output){

    double x, y, z, w, u, v ;
    int idx=0;
    int remain=SIZE%4;

    x=0.0;y=input[0]
    switch (remain) {
    case 3: z=input[++idx]; output[idx-1]=x+y+z; x=y; y=z;
    case 2: z=input[++idx]; output[idx-1]=x+y+z; x=y; y=z;
    case 1: z=input[++idx]; output[idx-1]=x+y+z; x=y; y=z;
    }

    for(; idx < SIZE - 5; idx+=4){
      z=input[idx+1];
      ....
void模具(双*输入,双*输出){
双x,y,z,w,u,v;
int-idx=0;
int剩余=大小%4;
x=0.0;y=输入[0]
开关(保持){
情况3:z=输入[++idx];输出[idx-1]=x+y+z;x=y;y=z;
情况2:z=输入[++idx];输出[idx-1]=x+y+z;x=y;y=z;
情况1:z=输入[++idx];输出[idx-1]=x+y+z;x=y;y=z;
}
对于(;idx
正如预期的那样,asm相当复杂,很难说会有什么好处


您也可以尝试在原始代码上使用
-funroll循环。
编译器非常好,可能会提供更好的解决方案。

这是一个好主意,但如果编译器知道这是安全的,他们已经可以为您这样做了。使用
双*限制输出
常量双*限制输入
来保证存储到
输出[]
的编译器不会更改从
输入[]
读取的内容

但使用SIMD的自动矢量化是一个更重要的优化,每个指令产生2或4个
结果。在检查重叠后,GCC和ICC将在
-O3
处执行此操作。(但clang无法自动矢量化,只需使用标量
[v]展开即可。)addsd
避免不必要的重新加载

不幸的是,您的优化版本无法实现自动矢量化!(这是编译器的错误,即当它知道输出没有重叠时,错过了优化错误,因此从内存中重新读取源代码与否是等价的)


看起来gcc在原始版本上做得很好,带有
-O3-march=native
(特别是在为英特尔调优时,使用AVX的更宽向量是值得的。)我并行计算了3个未对齐加载和2个
vaddpd ymm的4个
结果

它在使用矢量化循环之前检查重叠。您可以使用
double*restrict output
input
来保证指针不重叠,因此不需要回退循环


L1d缓存带宽在现代CPU上非常出色;重新加载相同的数据不是什么大问题(每个时钟加载2次)。指令吞吐量更大。内存源
addsd
的成本并不比将数据保存在寄存器中的成本高多少

如果使用128位向量进行矢量化,则有必要保留[idx+1..2]
中的
向量,以便在下一次迭代中用作[idx+-1..1]
向量。GCC实际上就是这样做的

但是,当您为每条指令生成4个结果时,一次迭代的3个输入向量中没有一个对下一次迭代直接有用。不过,通过无序排列来节省一些加载端口带宽,以便从加载结果创建3个向量中的一个可能会有用。如果我使用
\uuum256d
内部函数手动矢量化,我会尝试这样做。或使用128位
向量的
浮点


不幸的是,gcc正在使用索引寻址模式,因此带有内存源的
vaddpd
指令将SnB系列前端(包括您的Broadwell Xeon E5-2698 v4)反分层为2个UOP

吞吐量分析,请参阅和

GCC的循环是8个融合域UOP,用于前端发布/重命名阶段发送到无序后端。这意味着前端最大吞吐量为每2个周期迭代1次

Skylake之前的Intel中的
[v]addpd
只能在端口1上运行,而
[v]mulpd
或FMA的吞吐量是前者的两倍。(Skylake放弃了专用的FP add单元,并以与mul和FMA相同的方式运行FP add。)因此,这也是每个迭代瓶颈2个周期

我们有3个加载+1个存储,所有这些加载都需要端口2或3中的一个(索引寻址模式存储不能使用端口7上的专用存储AGU)。因此,每个迭代瓶颈还有2个周期。但不是真的;跨越缓存线边界的未对齐加载更昂贵。实验表明Intel Skylake(可能还有Broadwell)重放被发现是缓存线拆分的加载UOP,因此它们再次运行以从第二个缓存线获取数据

我们的数据是8字节对齐的,但我们在所有8字节偏移量上均匀分布32字节负载
void stencil(double * input, double * output){

    double x, y, z, w, u, v ;
    x=0.0;
    y=input[0];
    int idx=0;
    for(; idx < SIZE - 5; idx+=4){
      z=input[idx+1];
      w=input[idx+2];
      u=input[idx+3];
      v=input[idx+4];

      output[idx]  =x+y+z;
      output[idx+1]=y+z+w;
      output[idx+2]=z+w+u;
      output[idx+3]=w+u+v;

      x=u;
      y=v;
    }
    z=input[idx+1];
    w=input[idx+2];
    u=input[idx+3];

    output[idx]  =x+y+z;
    output[idx+1]=y+z+w;
    output[idx+2]=z+w+u;
    output[idx+3]=w+u;
}
void stencil(double * input, double * output){

    double x, y, z, w, u, v ;
    int idx=0;
    int remain=SIZE%4;

    x=0.0;y=input[0]
    switch (remain) {
    case 3: z=input[++idx]; output[idx-1]=x+y+z; x=y; y=z;
    case 2: z=input[++idx]; output[idx-1]=x+y+z; x=y; y=z;
    case 1: z=input[++idx]; output[idx-1]=x+y+z; x=y; y=z;
    }

    for(; idx < SIZE - 5; idx+=4){
      z=input[idx+1];
      ....
#define SIZE 1000000

void stencil_restrict(double *restrict input, double *restrict output)
{
    int idx = 1;
    output[0] = input[0] + input[1];

    for(; idx < SIZE - 1; idx++){
        output[idx] = input[idx-1] + input[idx] + input[idx+1];
    }

    output[idx] = input[idx-1] + input[idx];
}
stencil_restrict:
    vmovsd  xmm0, QWORD PTR [rdi]
    vaddsd  xmm0, xmm0, QWORD PTR [rdi+8]
    xor     eax, eax
    vmovsd  QWORD PTR [rsi], xmm0           # first iteration

### Main loop
.L12:
    vmovupd ymm2, YMMWORD PTR [rdi+8+rax]         # idx +0 .. +3
    vaddpd  ymm0, ymm2, YMMWORD PTR [rdi+rax]     # idx -1 .. +2
    vaddpd  ymm0, ymm0, YMMWORD PTR [rdi+16+rax]  # idx +1 .. +4
    vmovupd YMMWORD PTR [rsi+8+rax], ymm0         # store idx +0 .. +3
    add     rax, 32                             # byte offset += 32
    cmp     rax, 7999968
    jne     .L12

  # cleanup of last few elements
    vmovsd  xmm1, QWORD PTR [rdi+7999976]
    vaddsd  xmm0, xmm1, QWORD PTR [rdi+7999968]
    vaddsd  xmm1, xmm1, QWORD PTR [rdi+7999984]
    vunpcklpd       xmm0, xmm0, xmm1
    vaddpd  xmm0, xmm0, XMMWORD PTR [rdi+7999984]
    vmovups XMMWORD PTR [rsi+7999976], xmm0
    vmovsd  xmm0, QWORD PTR [rdi+7999984]
    vaddsd  xmm0, xmm0, QWORD PTR [rdi+7999992]
    vmovsd  QWORD PTR [rsi+7999992], xmm0
    vzeroupper
    ret
    vmovupd ymm2, YMMWORD PTR [rdi+8+rax]         # 1 uop, no micro-fusion
    vaddpd  ymm0, ymm2, YMMWORD PTR [rdi+rax]     # 2 uops.  (micro-fused in decoders/uop cache, unlaminates)
    vaddpd  ymm0, ymm0, YMMWORD PTR [rdi+16+rax]  # 2 uops.  (ditto)
    vmovupd YMMWORD PTR [rsi+8+rax], ymm0         # 1 uop (stays micro-fused, but can't use the port 7 store AGU)
    add     rax, 32                             # 1 uop
    cmp     rax, 7999968                         # 0 uops, macro-fuses with JNE
    jne     .L12                                 # 1 uop
 # prologue to reach an alignment boundary somewhere?
.L12:
    movupd  xmm2, XMMWORD PTR [rdi+rax]
    movupd  xmm1, XMMWORD PTR [rdi+8+rax]
    addpd   xmm0, xmm2
    addpd   xmm0, xmm1
    movups  XMMWORD PTR [rsi+rax], xmm0
    add     rax, 16
    movapd  xmm0, xmm1                   # x = z
    cmp     rax, 7999992
    jne     .L12
 # prologue to reach an alignment boundary so one load can be aligned.

# r10=input and r9=input+8  or something like that
# r8=output
.L18:                                       # do {
    movupd  xmm0, XMMWORD PTR [r10+rdx]
    add     ecx, 1
    addpd   xmm0, xmm1                        # x+y
    movapd  xmm1, XMMWORD PTR [r9+rdx]      # z for this iteration, x for next
    addpd   xmm0, xmm1                        # (x+y) + z
    movups  XMMWORD PTR [r8+rdx], xmm0
    add     rdx, 16
    cmp     ecx, r11d
    jb      .L18                            # } while(i < max);