相关疑难解决方法(0)

如何实现每个周期4个FLOP的理论最大值?

如何在现代x86-64 Intel CPU上实现每个周期4个浮点运算(双精度)的理论峰值性能?

据我所知,SSE 需要三个周期,addmul大多数现代Intel CPU需要五个周期才能完成(参见例如Agner Fog的"指令表").由于流水线操作,add如果算法具有至少三个独立的求和,则每个周期可以获得一个吞吐量.因为打包addpd和标量addsd版本都是如此,并且SSE寄存器可以包含两个,double每个周期的吞吐量可以高达两个触发器.

此外,似乎(虽然我没有看到任何适当的文档)add并且mul可以并行执行,给出每个周期四个触发器的理论最大吞吐量.

但是,我无法使用简单的C/C++程序复制该性能.我最好的尝试导致大约2.7个翻牌/周期.如果有人可以贡献一个简单的C/C++或汇编程序,它可以表现出非常高兴的峰值性能.

我的尝试:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/time.h>

double stoptime(void) {
   struct timeval t;
   gettimeofday(&t,NULL);
   return (double) t.tv_sec + t.tv_usec/1000000.0;
}

double addmul(double add, double mul, int ops){
   // Need to initialise differently otherwise compiler might optimise away
   double sum1=0.1, sum2=-0.1, sum3=0.2, sum4=-0.2, sum5=0.0;
   double mul1=1.0, mul2= 1.1, mul3=1.2, mul4= 1.3, …
Run Code Online (Sandbox Code Playgroud)

c c++ architecture optimization assembly

618
推荐指数
4
解决办法
7万
查看次数

C代码循环性能[续]

这个问题在我的问题上继续(根据神秘的建议):

C代码循环性能


继续我的问题,当我使用压缩指令而不是标量指令时,使用内在函数的代码看起来非常相似:

for(int i=0; i<size; i+=16) {
    y1 = _mm_load_ps(output[i]);
    …
    y4 = _mm_load_ps(output[i+12]);

    for(k=0; k<ksize; k++){
        for(l=0; l<ksize; l++){
            w  = _mm_set_ps1(weight[i+k+l]);

            x1 = _mm_load_ps(input[i+k+l]);
            y1 = _mm_add_ps(y1,_mm_mul_ps(w,x1));
            …
            x4 = _mm_load_ps(input[i+k+l+12]);
            y4 = _mm_add_ps(y4,_mm_mul_ps(w,x4));
        }
    }
    _mm_store_ps(&output[i],y1);
    …
    _mm_store_ps(&output[i+12],y4);
    }
Run Code Online (Sandbox Code Playgroud)

这个内核的测量性能是每个周期大约5.6个FP操作,虽然我预计它将是标量版本性能的4倍,即每个周期4.1,6 = 6,4 FP操作.

考虑到权重因素的移动(感谢指出这一点),时间表如下:

时间表

看起来调度没有改变,尽管在操作之后有一条额外的指令movss将标量权重值移动到XMM寄存器然后用于shufps在整个向量中复制这个标量值.似乎权重向量已经准备好用于mulps考虑从加载到浮点域的切换延迟,因此这不应该产生任何额外的延迟.

此内核中使用的movaps(对齐,打包的移动)addpsmulps指令(使用汇编代码检查)与其标量版本具有相同的延迟和吞吐量,因此这不会产生任何额外的延迟.

有没有人知道每8个周期的额外周期花费在哪里,假设这个内核可以获得的最大性能是每个周期6.4个FP运算并且每个周期运行5.6个FP运算?


顺便说一下,这是实际装配的样子:

…
Block x: 
  movapsx  (%rax,%rcx,4), %xmm0
  movapsx  0x10(%rax,%rcx,4), %xmm1
  movapsx  0x20(%rax,%rcx,4), %xmm2
  movapsx  0x30(%rax,%rcx,4), %xmm3
  movssl …
Run Code Online (Sandbox Code Playgroud)

c performance assembly intel instructions

83
推荐指数
1
解决办法
4798
查看次数

C代码循环性能

我的应用程序中有一个乘法添加内核,我想提高它的性能.

我使用英特尔酷睿i7-960(3.2 GHz时钟)并已使用SSE内在函数手动实现内核,如下所示:

 for(int i=0; i<iterations; i+=4) {
    y1 = _mm_set_ss(output[i]);
    y2 = _mm_set_ss(output[i+1]);
    y3 = _mm_set_ss(output[i+2]);
    y4 = _mm_set_ss(output[i+3]);

    for(k=0; k<ksize; k++){
        for(l=0; l<ksize; l++){
            w  = _mm_set_ss(weight[i+k+l]);

            x1 = _mm_set_ss(input[i+k+l]);
            y1 = _mm_add_ss(y1,_mm_mul_ss(w,x1));
            …
            x4 = _mm_set_ss(input[i+k+l+3]);
            y4 = _mm_add_ss(y4,_mm_mul_ss(w,x4));
        }
    }
    _mm_store_ss(&output[i],y1);
    _mm_store_ss(&output[i+1],y2);
    _mm_store_ss(&output[i+2],y3);
    _mm_store_ss(&output[i+3],y4);
 }
Run Code Online (Sandbox Code Playgroud)

我知道我可以使用压缩的fp向量来提高性能,我已经成功完成了,但我想知道为什么单个标量代码无法满足处理器的峰值性能.

我的机器上的这个内核的性能是每个周期大约1.6个FP操作,而每个周期最大的是2个FP操作(因为FP add + FP mul可以并行执行).

如果我对研究生成的汇编代码是正确的,理想的时间表将如下所示,其中mov指令需要3个周期,从依赖指令的加载域到FP域的切换延迟需要2个周期,FP乘以4个循环,FP添加需要3个循环.(注意,乘法 - > add的依赖性不会导致任何切换延迟,因为操作属于同一个域).

时间表

根据测量的性能(最大理论性能的约80%),每8个周期有大约3个指令的开销.

我想要:

  • 摆脱这种开销,或
  • 解释它来自哪里

当然,存在缓存未命中和数据错位的问题,这可能会增加移动指令的延迟,但是还有其他因素可以在这里发挥作用吗?像寄存器读取档位或什么?

我希望我的问题很明确,在此先感谢您的回复!


更新:内循环的程序集如下所示:

...
Block 21: 
  movssl  (%rsi,%rdi,4), %xmm4 
  movssl  (%rcx,%rdi,4), %xmm0 
  movssl  0x4(%rcx,%rdi,4), %xmm1 …
Run Code Online (Sandbox Code Playgroud)

c performance assembly intel instructions

40
推荐指数
1
解决办法
4039
查看次数

在 C++ 中使用 SSE 将两个 32 位整数向量相乘的最快方法

我有两个无符号向量,大小均为 4

vector<unsigned> v1 = {2, 4, 6, 8}
vector<unsigned> v2 = {1, 10, 11, 13}
Run Code Online (Sandbox Code Playgroud)

现在我想将这两个向量相乘并得到一个新向量

vector<unsigned> v_result = {2*1, 4*10, 6*11, 8*13}
Run Code Online (Sandbox Code Playgroud)

SSE操作要使用什么?是跨平台还是只在某些特定平台上?

添加:如果我的目标是加法而不是乘法,我可以超级快地完成此操作:

__m128i a = _mm_set_epi32(1,2,3,4);
__m128i b = _mm_set_epi32(1,2,3,4);
__m128i c;
c = _mm_add_epi32(a,b);
Run Code Online (Sandbox Code Playgroud)

c++ x86 sse simd intrinsics

5
推荐指数
1
解决办法
3681
查看次数