SSE 加载/存储内存事务

Posted

技术标签:

【中文标题】SSE 加载/存储内存事务【英文标题】:SSE load/store memory transactions 【发布时间】:2013-07-12 22:50:38 【问题描述】:

在使用 SSE 内部函数时,内存-寄存器交互有两种方式:

中间指针:

void f_sse(float *input, float *output, unsigned int n)

   _m128 *input_sse = reinterpret_cast<__m128*>(input);//Input intermediate pointer
   _m128 *output_sse = reinterpret_cast<__m128*>(output);//Output intermediate pointer
   _m128 s = _mm_set1_ps(0.1f);
   auto loop_size = n/4; 
   for(auto i=0; i<loop_size; ++i)
      output_sse[i] = _mm_add_ps(input_sse[i], s);

显式获取/存储:

void f_sse(float *input, float *output, unsigned int n)

   _m128 input_sse, output_sse, result;
   _m128 s = _mm_set1_ps(0.1f); 
   for(auto i=0; i<n; i+=4)
   
      input_sse  = _mm_load_ps(input+i);
      result     = _mm_add_ps(input_sse, s);
      _mm_store_ps(output+i, result);
   

上述方法之间有什么区别,哪种方法在性能方面更好?输入和输出指针由_mm_malloc()对齐。

【问题讨论】:

如果第一个例子的赋值操作使用非对齐指令,它会变慢。 _mm_store_ps 是对齐存储不是吗?第一个示例类似于按元素复制。你能显示反汇编程序的输出吗? 【参考方案1】:

在优化级别 O3 用 g++ 编译,内部循环的汇编代码(使用objdump -d)是

20:   0f 28 04 07             movaps (%rdi,%rax,1),%xmm0
24:   0f 58 c1                addps  %xmm1,%xmm0
27:   0f 29 04 06             movaps %xmm0,(%rsi,%rax,1)
2b:   48 83 c0 10             add    $0x10,%rax
2f:   48 39 d0                cmp    %rdx,%rax
32:   75 ec                   jne    20 <_Z5f_ssePfS_j+0x20>

10:   0f 28 04 07             movaps (%rdi,%rax,1),%xmm0
14:   83 c1 04                add    $0x4,%ecx
17:   0f 58 c1                addps  %xmm1,%xmm0
1a:   0f 29 04 06             movaps %xmm0,(%rsi,%rax,1)
1e:   48 83 c0 10             add    $0x10,%rax
22:   39 ca                   cmp    %ecx,%edx
24:   77 ea                   ja     10 <_Z5f_ssePfS_j+0x10>

它们非常相似。在第一个 g++ 中,设法只使用一个计数器(只有一个 add 指令)。所以我想它更好。

【讨论】:

【参考方案2】:

我用 g++ -O2 编译了你的两个示例,我发现的主要区别是 edx (n) 中的值使用不同,这导致代码略有不同。

第一个函数:

0000000000000000 <_Z6f_sse2PfS_j>:
   0:   c1 ea 02                shr    $0x2,%edx      # loop_size = n / 4. 
   3:   85 d2                   test   %edx,%edx
   5:   74 2d                   je     34 <_Z6f_sse2PfS_j+0x34>
   7:   83 ea 01                sub    $0x1,%edx
   a:   0f 28 0d 00 00 00 00    movaps 0x0(%rip),%xmm1        # 11 <_Z6f_sse2PfS_j+0x11>
  11:   48 83 c2 01             add    $0x1,%rdx
  15:   31 c0                   xor    %eax,%eax
  17:   48 c1 e2 04             shl    $0x4,%rdx             // Adjust for loop size vs. index. 
  1b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
  20:   0f 28 04 07             movaps (%rdi,%rax,1),%xmm0
  24:   0f 58 c1                addps  %xmm1,%xmm0
  27:   0f 29 04 06             movaps %xmm0,(%rsi,%rax,1)
  2b:   48 83 c0 10             add    $0x10,%rax
  2f:   48 39 d0                cmp    %rdx,%rax
  32:   75 ec                   jne    20 <_Z6f_sse2PfS_j+0x20>
  34:   f3 c3                   repz retq 

第二个功能:

0000000000000000 <_Z5f_ssePfS_j>:
   0:   85 d2                   test   %edx,%edx
   2:   74 22                   je     26 <_Z5f_ssePfS_j+0x26>
   4:   0f 28 0d 00 00 00 00    movaps 0x0(%rip),%xmm1        # b <_Z5f_ssePfS_j+0xb>
   b:   31 c0                   xor    %eax,%eax
   d:   31 c9                   xor    %ecx,%ecx
   f:   90                      nop
  10:   0f 28 04 07             movaps (%rdi,%rax,1),%xmm0
  14:   83 c1 04                add    $0x4,%ecx
  17:   0f 58 c1                addps  %xmm1,%xmm0
  1a:   0f 29 04 06             movaps %xmm0,(%rsi,%rax,1)
  1e:   48 83 c0 10             add    $0x10,%rax
  22:   39 ca                   cmp    %ecx,%edx
  24:   77 ea                   ja     10 <_Z5f_ssePfS_j+0x10>
  26:   f3 c3                   repz retq 

我还查看了生成的代码,并想出了这个:

void f_sse2(float *input, float *output, unsigned int n)

    __m128 *end = reinterpret_cast<__m128*>(&input[n]);
   __m128 *input_sse = reinterpret_cast<__m128*>(input);//Input intermediate pointer
   __m128 *output_sse = reinterpret_cast<__m128*>(output);//Output intermediate pointer
   __m128 s = _mm_set1_ps(0.1f);
   while(input_sse < end)
      *output_sse++ = _mm_add_ps(*input_sse++, s);

生成此代码:

0000000000000000 <_Z6f_sse2PfS_j>:
   0:   89 d2                   mov    %edx,%edx
   2:   48 8d 04 97             lea    (%rdi,%rdx,4),%rax
   6:   48 39 c7                cmp    %rax,%rdi
   9:   73 23                   jae    2e <_Z6f_sse2PfS_j+0x2e>
   b:   0f 28 0d 00 00 00 00    movaps 0x0(%rip),%xmm1        # 12 <_Z6f_sse2PfS_j+0x12>
  12:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
  18:   0f 28 07                movaps (%rdi),%xmm0
  1b:   48 83 c7 10             add    $0x10,%rdi
  1f:   0f 58 c1                addps  %xmm1,%xmm0
  22:   0f 29 06                movaps %xmm0,(%rsi)
  25:   48 83 c6 10             add    $0x10,%rsi
  29:   48 39 f8                cmp    %rdi,%rax
  2c:   77 ea                   ja     18 <_Z6f_sse2PfS_j+0x18>
  2e:   f3 c3                   repz retq 

我认为这可能更有效,但可能不值得改变它。但它给了我 15 分钟的时间。

【讨论】:

以上是关于SSE 加载/存储内存事务的主要内容,如果未能解决你的问题,请参考以下文章

学习 GCC ASM:SSE 到 NEON:加载和存储

C++ SSE:存储到数组后的未定义行为

SSE 内存访问

如何使用 Cloud Formation 模板在 S3 存储桶上设置 SSE-S3 或 SSE-KMS 加密?

Spring中的SSE(服务器端事件)——存储SseEmitters

将文件从 AWS s3 (SSE) 存储桶复制到谷歌云