首页 文章

优化快速乘法但缓慢添加:FMA和doubledouble

提问于
浏览
9

当我第一次使用Haswell处理器时,我尝试使用FMA来确定Mandelbrot集 . 主要算法是这样的:

intn = 0;
for(int32_t i=0; i<maxiter; i++) {
    floatn x2 = square(x), y2 = square(y); //square(x) = x*x
    floatn r2 = x2 + y2;
    booln mask = r2<cut; //booln is in the float domain non integer domain
    if(!horizontal_or(mask)) break; //_mm256_testz_pd(mask)
    n -= mask
    floatn t = x*y; mul2(t); //mul2(t): t*=2
    x = x2 - y2 + cx;
    y = t + cy;
}

这确定 n 像素是否在Mandelbrot集中 . 因此,对于双浮点,它运行超过4个像素( floatn = __m256dintn = __m256i ) . 这需要4个SIMD浮点乘法和4个SIMD浮点加法 .

然后我修改了这个就像这样使用FMA

intn n = 0; 
for(int32_t i=0; i<maxiter; i++) {
    floatn r2 = mul_add(x,x,y*y);
    booln mask = r2<cut;
    if(!horizontal_or(mask)) break;
    add_mask(n,mask);
    floatn t = x*y;
    x = mul_sub(x,x, mul_sub(y,y,cx));
    y = mul_add(2.0f,t,cy);
}

其中mul_add调用 _mm256_fmad_pd ,mul_sub调用 _mm256_fmsub_pd . 该方法使用4个FMA SIMD操作和两个SIMD乘法,这是没有FMA的两个算术运算 . 此外,FMA和乘法可以使用两个端口,只添加一个 .

为了减少我的测试偏差,我放大了一个完全在Mandelbrot集中的区域,所以所有的值都是 maxiter . 在这种情况下 the method using FMA is about 27% faster. 这肯定是一个改进,但从SSE到AVX的性能翻了一倍,所以我希望FMA可能有另外两个因素 .

但后来我发现了有关FMA的答案.2904328_答案

融合乘加指令的重要方面是中间结果的(虚拟)无限精度 . 这有助于提高性能,但不是因为两个操作在一条指令中编码 - 它有助于提高性能,因为中间结果的几乎无限精度有时很重要,并且通过普通乘法和加法来恢复非常昂贵精确度正是程序员追求的目标 .

然后给出一个double * double到double-double乘法的例子

high = a * b; /* double-precision approximation of the real product */
low = fma(a, b, -high); /* remainder of the real product */

由此,我得出结论,我正在非优化地实施FMA,因此我决定实施SIMD双倍 . 我根据论文Extended-Precision Floating-Point Numbers for GPU Computation实施了双倍 . 这篇论文用于双浮动,所以我修改它为双倍 . 此外,不是在SIMD寄存器中打包一个双倍值,而是将4个双倍值打包到一个AVX高位寄存器和一个AVX低位寄存器中 .

对于Mandelbrot集合我真正需要的是双倍乘法和加法 . 在那篇论文中,这些是 df64_adddf64_mult 函数 . 下图显示software FMA(左)和硬件FMA(右)的 df64_mult 函数的程序集 . 这清楚地表明硬件FMA是双倍乘法的重大改进 .

fma software vs hardware

那么硬件FMA如何在双倍Mandelbrot集合计算中执行? The answer is that's only about 15% faster than with software FMA. That's much less than I hoped for. 双倍Mandelbrot计算需要4次双重加法和4次双重乘法( x*xy*yx*y2*(x*y) ) . 但是,2*(x*y) multiplication is trivial for double-double所以这个乘法可以在成本中忽略 . 因此,我认为使用硬件FMA的改进如此之小的原因是计算主要是慢速双倍加法(见下面的装配) .

过去,乘法比加法慢(并且程序员使用了几个技巧来避免乘法)但是对于Haswell来说,它似乎是另一种方式 . 不仅是因为FMA,还因为乘法可以使用两个端口但只添加一个 .

所以我的问题(最后)是:

  • 与乘法相比,当加法缓慢时,如何优化?

  • 有没有一种代数方法来改变我的算法以使用更多的乘法和更少的加法?我知道有相反的方法,例如 (x+y)*(x+y) - (x*x+y*y) = 2*x*y 使用另外两个加法来减少一次乘法 .

  • 有没有办法简单地使用df64_add函数(例如使用FMA)?

如果有人想知道双重方法比双重方法慢十倍 . 这并不是那么糟糕,我认为好像有一个硬件四精度类型,它可能至少是double的两倍慢,所以我的软件方法比我预期的硬件慢五倍(如果它存在的话) .

df64_add 大会

vmovapd 8(%rsp), %ymm0
movq    %rdi, %rax
vmovapd 72(%rsp), %ymm1
vmovapd 40(%rsp), %ymm3
vaddpd  %ymm1, %ymm0, %ymm4
vmovapd 104(%rsp), %ymm5
vsubpd  %ymm0, %ymm4, %ymm2
vsubpd  %ymm2, %ymm1, %ymm1
vsubpd  %ymm2, %ymm4, %ymm2
vsubpd  %ymm2, %ymm0, %ymm0
vaddpd  %ymm1, %ymm0, %ymm2
vaddpd  %ymm5, %ymm3, %ymm1
vsubpd  %ymm3, %ymm1, %ymm6
vsubpd  %ymm6, %ymm5, %ymm5
vsubpd  %ymm6, %ymm1, %ymm6
vaddpd  %ymm1, %ymm2, %ymm1
vsubpd  %ymm6, %ymm3, %ymm3
vaddpd  %ymm1, %ymm4, %ymm2
vaddpd  %ymm5, %ymm3, %ymm3
vsubpd  %ymm4, %ymm2, %ymm4
vsubpd  %ymm4, %ymm1, %ymm1
vaddpd  %ymm3, %ymm1, %ymm0
vaddpd  %ymm0, %ymm2, %ymm1
vsubpd  %ymm2, %ymm1, %ymm2
vmovapd %ymm1, (%rdi)
vsubpd  %ymm2, %ymm0, %ymm0
vmovapd %ymm0, 32(%rdi)
vzeroupper
ret

3 回答

  • 5

    为了回答我的第三个问题,我找到了一个更快的双倍加法解决方案 . 我在论文Implementation of float-float operators on graphics hardware中找到了另一种定义 .

    Theorem 5 (Add22 theorem) Let be ah+al and bh+bl the float-float arguments of the following
    algorithm:
    Add22 (ah ,al ,bh ,bl)
    1 r = ah ⊕ bh
    2 if | ah | ≥ | bh | then
    3     s = ((( ah ⊖ r ) ⊕ bh ) ⊕ b l ) ⊕ a l
    4 e l s e
    5     s = ((( bh ⊖ r ) ⊕ ah ) ⊕ a l ) ⊕ b l
    6 ( rh , r l ) = add12 ( r , s )
    7 return (rh , r l)
    

    这是我实现这个(伪代码)的方式:

    static inline doubledoublen add22(doubledoublen const &a, doubledouble const &b) {
        doublen aa,ab,ah,bh,al,bl;
        booln mask;
        aa = abs(a.hi);                //_mm256_and_pd
        ab = abs(b.hi); 
        mask = aa >= ab;               //_mm256_cmple_pd
        // z = select(cut,x,y) is a SIMD version of z = cut ? x : y;
        ah = select(mask,a.hi,b.hi);   //_mm256_blendv_pd
        bh = select(mask,b.hi,a.hi);
        al = select(mask,a.lo,b.lo);
        bl = select(mask,b.lo,a.lo);
    
        doublen r, s;
        r = ah + bh;
        s = (((ah - r) + bh) + bl ) + al;
        return two_sum(r,s);
    }
    

    Add22的这个定义使用11个加法而不是20个,但它需要一些额外的代码来确定 |ah| >= |bh| . Here is a discussion on how to implement SIMD minmag and maxmag functions . 幸运的是,大多数附加代码不使用端口1.现在只有12条指令端口1而不是20 .

    以下是新Add22的吞吐量分析表单IACA

    Throughput Analysis Report
    --------------------------
    Block Throughput: 12.05 Cycles       Throughput Bottleneck: Port1
    
    Port Binding In Cycles Per Iteration:
    ---------------------------------------------------------------------------------------
    |  Port  |  0   -  DV  |  1   |  2   -  D   |  3   -  D   |  4   |  5   |  6   |  7   |
    ---------------------------------------------------------------------------------------
    | Cycles | 0.0    0.0  | 12.0 | 2.5    2.5  | 2.5    2.5  | 2.0  | 10.0 | 0.0  | 2.0  |
    ---------------------------------------------------------------------------------------
    
    
    | Num Of |                    Ports pressure in cycles                     |    |
    |  Uops  |  0  - DV  |  1  |  2  -  D  |  3  -  D  |  4  |  5  |  6  |  7  |    |
    ---------------------------------------------------------------------------------
    |   1    |           |     | 0.5   0.5 | 0.5   0.5 |     |     |     |     |    | vmovapd ymm3, ymmword ptr [rip]
    |   1    |           |     | 0.5   0.5 | 0.5   0.5 |     |     |     |     |    | vmovapd ymm0, ymmword ptr [rdx]
    |   1    |           |     | 0.5   0.5 | 0.5   0.5 |     |     |     |     |    | vmovapd ymm4, ymmword ptr [rsi]
    |   1    |           |     |           |           |     | 1.0 |     |     |    | vandpd ymm2, ymm4, ymm3
    |   1    |           |     |           |           |     | 1.0 |     |     |    | vandpd ymm3, ymm0, ymm3
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vcmppd ymm2, ymm3, ymm2, 0x2
    |   1    |           |     | 0.5   0.5 | 0.5   0.5 |     |     |     |     |    | vmovapd ymm3, ymmword ptr [rsi+0x20]
    |   2    |           |     |           |           |     | 2.0 |     |     |    | vblendvpd ymm1, ymm0, ymm4, ymm2
    |   2    |           |     |           |           |     | 2.0 |     |     |    | vblendvpd ymm4, ymm4, ymm0, ymm2
    |   1    |           |     | 0.5   0.5 | 0.5   0.5 |     |     |     |     |    | vmovapd ymm0, ymmword ptr [rdx+0x20]
    |   2    |           |     |           |           |     | 2.0 |     |     |    | vblendvpd ymm5, ymm0, ymm3, ymm2
    |   2    |           |     |           |           |     | 2.0 |     |     |    | vblendvpd ymm0, ymm3, ymm0, ymm2
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm3, ymm1, ymm4
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm2, ymm1, ymm3
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm1, ymm2, ymm4
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm1, ymm1, ymm0
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm0, ymm1, ymm5
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm2, ymm3, ymm0
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm1, ymm2, ymm3
    |   2^   |           |     |           |           | 1.0 |     |     | 1.0 |    | vmovapd ymmword ptr [rdi], ymm2
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm0, ymm0, ymm1
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm1, ymm2, ymm1
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm3, ymm3, ymm1
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm0, ymm3, ymm0
    |   2^   |           |     |           |           | 1.0 |     |     | 1.0 |    | vmovapd ymmword ptr [rdi+0x20], ymm0
    

    这是旧的吞吐量分析

    Throughput Analysis Report
    --------------------------
    Block Throughput: 20.00 Cycles       Throughput Bottleneck: Port1
    
    Port Binding In Cycles Per Iteration:
    ---------------------------------------------------------------------------------------
    |  Port  |  0   -  DV  |  1   |  2   -  D   |  3   -  D   |  4   |  5   |  6   |  7   |
    ---------------------------------------------------------------------------------------
    | Cycles | 0.0    0.0  | 20.0 | 2.0    2.0  | 2.0    2.0  | 2.0  | 0.0  | 0.0  | 2.0  |
    ---------------------------------------------------------------------------------------
    
    | Num Of |                    Ports pressure in cycles                     |    |
    |  Uops  |  0  - DV  |  1  |  2  -  D  |  3  -  D  |  4  |  5  |  6  |  7  |    |
    ---------------------------------------------------------------------------------
    |   1    |           |     | 1.0   1.0 |           |     |     |     |     |    | vmovapd ymm0, ymmword ptr [rsi]
    |   1    |           |     |           | 1.0   1.0 |     |     |     |     |    | vmovapd ymm1, ymmword ptr [rdx]
    |   1    |           |     | 1.0   1.0 |           |     |     |     |     |    | vmovapd ymm3, ymmword ptr [rsi+0x20]
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm4, ymm0, ymm1
    |   1    |           |     |           | 1.0   1.0 |     |     |     |     |    | vmovapd ymm5, ymmword ptr [rdx+0x20]
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm2, ymm4, ymm0
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm1, ymm1, ymm2
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm2, ymm4, ymm2
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm0, ymm0, ymm2
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm2, ymm0, ymm1
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm1, ymm3, ymm5
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm6, ymm1, ymm3
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm5, ymm5, ymm6
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm6, ymm1, ymm6
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm1, ymm2, ymm1
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm3, ymm3, ymm6
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm2, ymm4, ymm1
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm3, ymm3, ymm5
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm4, ymm2, ymm4
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm1, ymm1, ymm4
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm0, ymm1, ymm3
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vaddpd ymm1, ymm2, ymm0
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm2, ymm1, ymm2
    |   2^   |           |     |           |           | 1.0 |     |     | 1.0 |    | vmovapd ymmword ptr [rdi], ymm1
    |   1    |           | 1.0 |           |           |     |     |     |     | CP | vsubpd ymm0, ymm0, ymm2
    |   2^   |           |     |           |           | 1.0 |     |     | 1.0 |    | vmovapd ymmword ptr [rdi+0x20], ymm0
    

    如果除了FMA之外还有三个操作数单舍入模式指令,则更好的解决方案 . 在我看来应该有单一的舍入模式指令

    a + b + c
    a * b + c //FMA - this is the only one in x86 so far
    a * b * c
    
  • 1

    为了加快算法速度,我使用了基于2 fma,1 mul和2 add的简化版本 . 我以这种方式处理8次迭代 . 然后计算转义半径并在必要时回滚最后8次迭代 .

    使用x86内部函数编写的以下关键循环X = X ^ 2 C很好地由编译器展开,并且在展开之后您将发现2 FMA操作彼此之间并非严重依赖 .

    //  IACA_START;
    for (j = 0; j < 8; j++) {
        Xrm = _mm256_mul_ps(Xre, Xim);
        Xtt = _mm256_fmsub_ps(Xim, Xim, Cre);
        Xrm = _mm256_add_ps(Xrm, Xrm);
        Xim = _mm256_add_ps(Cim, Xrm);
        Xre = _mm256_fmsub_ps(Xre, Xre, Xtt);
    }       // for
    //  IACA_END;
    

    然后我计算逃逸半径(| X | <threshold),它只花费其他fma和另一个乘法,每8次迭代 .

    cmp = _mm256_mul_ps(Xre, Xre);
    cmp = _mm256_fmadd_ps(Xim, Xim, cmp);
    cmp = _mm256_cmp_ps(cmp, vec_threshold, _CMP_LE_OS);
    if (_mm256_testc_si256((__m256i) cmp, vec_one)) {
        i += 8;
        continue;
    }
    

    你提到“加速很慢”,这不完全正确,但你说得对,乘法吞吐量随着时间的推移在最近的架构上变得越来越高 .

    乘法延迟和依赖关系是关键 . FMA的吞吐量为1个周期,延迟为5个周期 . 独立FMA指令的执行可以重叠 .

    基于乘法结果的加法得到完整的延迟命中 .

    因此,您必须通过执行“代码拼接”并在同一循环中计算2个点来打破这些直接依赖关系,并在检查IACA之前只交错代码将会发生什么 . 下面的代码有两组变量(后缀为0和1代表X0 = X0 ^ 2 C0,X1 = X1 ^ 2 C1)并开始填充FMA孔

    for (j = 0; j < 8; j++) {
        Xrm0 = _mm256_mul_ps(Xre0, Xim0);
        Xrm1 = _mm256_mul_ps(Xre1, Xim1);
        Xtt0 = _mm256_fmsub_ps(Xim0, Xim0, Cre);
        Xtt1 = _mm256_fmsub_ps(Xim1, Xim1, Cre);
        Xrm0 = _mm256_add_ps(Xrm0, Xrm0);
        Xrm1 = _mm256_add_ps(Xrm1, Xrm1);
        Xim0 = _mm256_add_ps(Cim0, Xrm0);
        Xim1 = _mm256_add_ps(Cim1, Xrm1);
        Xre0 = _mm256_fmsub_ps(Xre0, Xre0, Xtt0);
        Xre1 = _mm256_fmsub_ps(Xre1, Xre1, Xtt1);
    }       // for
    

    总结一下,

    • 你可以在你的关键循环中 halve the number of instructions

    • 你可以 add more independent instructions 并获得高吞吐量与乘法的低延迟以及融合乘法和加法的优势 .

  • 1

    你提到以下代码:

    vsubpd  %ymm0, %ymm4, %ymm2
    vsubpd  %ymm2, %ymm1, %ymm1  <-- immediate dependency ymm2
    vsubpd  %ymm2, %ymm4, %ymm2
    vsubpd  %ymm2, %ymm0, %ymm0  <-- immediate dependency ymm2
    vaddpd  %ymm1, %ymm0, %ymm2  <-- immediate dependency ymm0
    vaddpd  %ymm5, %ymm3, %ymm1
    vsubpd  %ymm3, %ymm1, %ymm6  <-- immediate dependency ymm1
    vsubpd  %ymm6, %ymm5, %ymm5  <-- immediate dependency ymm6
    vsubpd  %ymm6, %ymm1, %ymm6  <-- dependency ymm1, ymm6
    vaddpd  %ymm1, %ymm2, %ymm1
    vsubpd  %ymm6, %ymm3, %ymm3  <-- dependency ymm6
    vaddpd  %ymm1, %ymm4, %ymm2 
    vaddpd  %ymm5, %ymm3, %ymm3  <-- dependency ymm3
    vsubpd  %ymm4, %ymm2, %ymm4 
    vsubpd  %ymm4, %ymm1, %ymm1  <-- immediate dependency ymm4
    vaddpd  %ymm3, %ymm1, %ymm0  <-- immediate dependency ymm1, ymm3
    vaddpd  %ymm0, %ymm2, %ymm1  <-- immediate dependency ymm0
    vsubpd  %ymm2, %ymm1, %ymm2  <-- immediate dependency ymm1
    

    如果仔细检查,这些主要是依赖操作,并且不符合关于延迟/吞吐效率的基本规则 . 大多数指令取决于前一个指令的结果,或之前的2个指令 . 该序列包含30个循环的关键路径(关于“3个循环延迟”/“1个循环吞吐量”约9或10个指令) .

    您的IACA在关键路径中报告“CP”=>指令,评估的成本为20个周期的吞吐量 . 您应该获得延迟报告,因为如果您对执行速度感兴趣,那么它就是重要的 .

    为了消除这个关键路径的成本,如果编译器不能这样做,你必须交错大约20个类似的指令(例如,因为你的双重代码是在没有-flto优化的情况下编译的单独库中,并且在函数入口和出口处到处都是vzeroupper ,vectorizer只适用于内联代码) .

    一种可能性是并行运行2次计算(参见前一篇文章中的代码拼接以改进流水线操作)

    如果我认为你的双重代码看起来像这个“标准”实现

    // (r,e) = x + y
    #define two_sum(x, y, r, e) 
        do { double t; r = x + y; t = r - x; e = (x - (r - t)) + (y - t); } while (0)
    #define two_difference(x, y, r, e) \
        do { double t; r = x - y; t = r - x; e = (x - (r - t)) - (y + t); } while (0)
    .....
    

    然后你必须考虑以下代码,其中指令以非常精细的方式交错 .

    // (r1, e1) = x1 + y1, (r2, e2) x2 + y2
    #define two_sum(x1, y1, x2, y2, r1, e1, r2, e2) 
        do { double t1, t2 \
        r1 = x1 + y1; r2 = x2 + y2; \
        t1 = r1 - x1; t2 = r2 - x2; \
        e1 = (x1 - (r1 - t1)) + (y1 - t1); e2 = (x2 - (r2 - t2)) + (y2 - t2);  \
    } while (0)
    ....
    

    然后,这将创建类似下面的代码(关于延迟报告中的相同关键路径,以及大约35条指令) . 有关运行时的详细信息,乱序执行应该在没有停顿的情况下飞越 .

    vsubsd  %xmm2, %xmm0, %xmm8
    vsubsd  %xmm3, %xmm1, %xmm1
    vaddsd  %xmm4, %xmm4, %xmm4
    vaddsd  %xmm5, %xmm5, %xmm5
    vsubsd  %xmm0, %xmm8, %xmm9
    vsubsd  %xmm9, %xmm8, %xmm10
    vaddsd  %xmm2, %xmm9, %xmm2
    vsubsd  %xmm10, %xmm0, %xmm0
    vsubsd  %xmm2, %xmm0, %xmm11
    vaddsd  %xmm14, %xmm4, %xmm2
    vaddsd  %xmm11, %xmm1, %xmm12
    vsubsd  %xmm4, %xmm2, %xmm0
    vaddsd  %xmm12, %xmm8, %xmm13
    vsubsd  %xmm0, %xmm2, %xmm11
    vsubsd  %xmm0, %xmm14, %xmm1
    vaddsd  %xmm6, %xmm13, %xmm3
    vsubsd  %xmm8, %xmm13, %xmm8
    vsubsd  %xmm11, %xmm4, %xmm4
    vsubsd  %xmm13, %xmm3, %xmm15
    vsubsd  %xmm8, %xmm12, %xmm12
    vaddsd  %xmm1, %xmm4, %xmm14
    vsubsd  %xmm15, %xmm3, %xmm9
    vsubsd  %xmm15, %xmm6, %xmm6
    vaddsd  %xmm7, %xmm12, %xmm7
    vsubsd  %xmm9, %xmm13, %xmm10
    vaddsd  16(%rsp), %xmm5, %xmm9
    vaddsd  %xmm6, %xmm10, %xmm15
    vaddsd  %xmm14, %xmm9, %xmm10
    vaddsd  %xmm15, %xmm7, %xmm13
    vaddsd  %xmm10, %xmm2, %xmm15
    vaddsd  %xmm13, %xmm3, %xmm6
    vsubsd  %xmm2, %xmm15, %xmm2
    vsubsd  %xmm3, %xmm6, %xmm3
    vsubsd  %xmm2, %xmm10, %xmm11
    vsubsd  %xmm3, %xmm13, %xmm0
    

    摘要:

    • inline 您的双重双重源代码:由于ABI约束,编译器和向量化程序无法跨函数调用进行优化,并且由于担心别名而无法跨内存访问进行优化 .

    • stitch 代码可以 balancer 吞吐量和延迟并最大化CPU端口使用(并且还可以最大化每个周期的指令),只要编译器不会将过多的寄存器溢出到内存中 .

    您可以使用perf实用程序(包linux-tools-generic和linux-cloud-tools-generic)跟踪优化影响,以获取执行的指令数和每个周期的指令数 .

相关问题