大型阵列上的SSE性能较低

时间:2016-08-17 12:10:07

标签: c performance sse simd intrinsics

我是SSE编程的新手,所以我希望有人可以帮助我。我最近使用GCC SSE内在函数实现了一个函数来计算32位整数数组的总和。我的实现代码如下。

int ssum(const int *d, unsigned int len)
{
  static const unsigned int BLOCKSIZE=4;
  unsigned int i,remainder;
  int output;
  __m128i xmm0, accumulator;
  __m128i* src;

  remainder = len%BLOCKSIZE;
  src = (__m128i*)d;
  accumulator = _mm_loadu_si128(src);

  output = 0;
  for(i=BLOCKSIZE;i<len-remainder;i+=BLOCKSIZE){
    xmm0 = _mm_loadu_si128(++src);
    accumulator = _mm_add_epi32(accumulator,xmm0);
  }

  accumulator = _mm_add_epi32(accumulator, _mm_srli_si128(accumulator, 8));
  accumulator = _mm_add_epi32(accumulator, _mm_srli_si128(accumulator, 4));
  output = _mm_cvtsi128_si32(accumulator);


  for(i=len-remainder;i<len;i++){
    output += d[i];
  }
  return output;
}

正如您所看到的,这是一个相当简单的实现,我使用扩展的xmm寄存器一次对数组4求和,然后通过将剩余的元素相加来最后清理。

然后,我将此SIMD实现的性能与简单的for循环进行了比较。这个实验的结果可以在这里找到:

SIMD vs. for-loop

正如您所看到的,与for循环相比,此实现确实显示输入大小(意味着数组的长度)的约60%加速,大约为5M元素。但是,对于较大的输入大小值,相对于for循环而言,性能会急剧下降,并且只会产生大约20%的加速。

我无法解释这种性能急剧下降的原因。我或多或少地通过内存线性地步进,因此对于两种实现,缓存未命中和页面错误的影响应该大致相同。我在这里错过了什么?有什么办法可以让这条曲线变平吗?任何想法将不胜感激。

1 个答案:

答案 0 :(得分:5)

对于大输入,数据在缓存之外,代码是内存限制的 对于小输入,数据在缓存内(即L1 / L2 / L3缓存),代码是计算限制的 我假设在性能测量之前你没有尝试刷新缓存。

高速缓冲存储器位于CPU内部,高速缓冲存储器和ALU(或SSE)单元之间的带宽非常高(高带宽 - 传输数据的时间更少)。
您的最高级别缓存(即L3)大小约为4MB到8MB(取决于您的CPU型号) 大量数据必须位于DDR SDRAM上,外部RAM(CPU外部) CPU通过内存总线连接到DDR SDRAM,带宽远低于高速缓存。

例:
假设您的外部RAM类型为Dual Channel DDR3 SDRAM 1600。 外部RAM和CPU之间的最大理论带宽约为25GB /秒。

从RAM向CPU读取100MB的数据(25GB / S)大约需要100e6 / 25e9 = 4msec。 根据我的经验,所使用的带宽约为理论带宽的一半,因此读取时间约为8毫秒。

计算时间较短:
假设循环的每次迭代需要大约2个CPU时钟(仅举例) 每次迭代处理16个字节的数据 处理100MB的总CPU时钟大约需要(100e6 / 16)* 2 = 12500000 clks 假设CPU频率为3GHz 总SSE处理时间约为12500000 / 3e9 = 4.2毫秒。

如您所见,从外部RAM读取数据所需的时间是SSE计算时间的两倍。

由于数据传输和计算是并行进行的,因此总时间最长为4.2毫秒和8毫秒(即8毫秒)。

假设不使用SSE的循环需要两倍的计算时间,因此不使用SSE计算时间约为8.4毫秒。

在上面的例子中,使用SSE的总改进是大约0.4毫秒。

注意:所选数字仅用于举例目的。

基准:
我在我的系统上做了一些基准测试 我使用的是Windows 10和Visual Studio 2010 基准测试:总计100MB的数据(总计25 * 1024 ^ 2 32位整数)。

CPU

  • 英特尔酷睿i5 3550(Ivy Bridge)。
  • CPU基频为3.3GHz。
  • 测试期间的实际核心速度:3.6GHz(启用Turbo boost)。
  • L1数据缓存大小:32KBytes。
  • L2缓存大小:256Bytes(单核L2缓存大小)。
  • L3缓存大小:6MBytes。

记忆:

  • 8GB DDR3双通道。
  • RAM频率:666MHz(相当于没有DDR的1333MHz)。
  • 内存理论最大带宽:(128 * 1333/8)/ 1024 = 20.8GBytes / Sec。
  1. 将100 MB作为大块与SSE (外部RAM中的数据)相加 处理时间:6.22毫秒
  2. 在SSE (缓存中的数据)内总计1KB 100次 处理时间:3.86毫秒
  3. 在没有SSE的情况下将100MB作为大块 (外部RAM中的数据) 处理时间:8.1毫秒
  4. 总计1KB 100次没有SSE (缓存中的数据) 处理时间:4.73毫秒
  5. 利用内存带宽:100 / 6.22 = 16GB /秒 (按时间划分数据大小)
    使用SSE(缓存中的数据)每次迭代的平均时钟:(3.6e9 * 3.86e-3)/(25/4 * 1024 ^ 2)= 2.1 clks / iteration (除了总CPU按迭代次数计算时钟)