当存储器带宽限制时,SSE和AVX的性能

时间:2013-09-09 04:04:08

标签: performance caching sse avx

在下面的代码中,我更改了“dataLen”并获得了不同的效率。

dataLen = 400 SSE时间:758000 us AVX时间:483000 us SSE> AVX

dataLen = 2400上证时间:4212000 us AVX时间:2636000 us SSE> AVX

dataLen = 2864上网时间:6115000 us AVX时间:6146000 us SSE~ = AVX

dataLen = 3200 SSE时间:8049000 us AVX时间:9297000 us SSE< AVX

dataLen = 4000 SSE时间:10170000us AVX时间:11690000us SSE< AVX

SSE和AVX代码可以简化为:buf3 [i] + = buf1 [1] * buf2 [i];

#include "testfun.h"
#include <iostream>
#include <chrono>
#include <malloc.h>
#include "immintrin.h"
using namespace std::chrono;

void testfun()
{
int dataLen = 4000; 
int N = 10000000;
float *buf1 = reinterpret_cast<float*>(_aligned_malloc(sizeof(float)*dataLen, 32));
float *buf2 = reinterpret_cast<float*>(_aligned_malloc(sizeof(float)*dataLen, 32));
float *buf3 = reinterpret_cast<float*>(_aligned_malloc(sizeof(float)*dataLen, 32));
for(int i=0; i<dataLen; i++)
{
    buf1[i] = 1;
    buf2[i] = 1;
    buf3[i] = 0;
}
//=========================SSE CODE=====================================
system_clock::time_point SSEStart = system_clock::now();
__m128 p1, p2, p3;

for(int j=0; j<N; j++)
for(int i=0; i<dataLen; i=i+4)
{
    p1 = _mm_load_ps(&buf1[i]);
    p2 = _mm_load_ps(&buf2[i]);
    p3 = _mm_load_ps(&buf3[i]);
    p3 = _mm_add_ps(_mm_mul_ps(p1, p2), p3);
    _mm_store_ps(&buf3[i], p3);
}

microseconds SSEtimeUsed = duration_cast<milliseconds>(system_clock::now() - SSEStart);
std::cout << "SSE time used: " << SSEtimeUsed.count() << " us, " <<std::endl;

//=========================AVX CODE=====================================
for(int i=0; i<dataLen; i++) buf3[i] = 0;

system_clock::time_point AVXstart = system_clock::now();
__m256  pp1, pp2, pp3; 

for(int j=0; j<N; j++)
for(int i=0; i<dataLen; i=i+8)
{       
    pp1 = _mm256_load_ps(&buf1[i]);
    pp2 = _mm256_load_ps(&buf2[i]);
    pp3 = _mm256_load_ps(&buf3[i]);
    pp3 = _mm256_add_ps(_mm256_mul_ps(pp1, pp2), pp3);
    _mm256_store_ps(&buf3[i], pp3);

}

microseconds AVXtimeUsed = duration_cast<milliseconds>(system_clock::now() - AVXstart);
std::cout << "AVX time used: " << AVXtimeUsed.count() << " us, " <<std::endl;

_aligned_free(buf1);
_aligned_free(buf2);
}

我的cpu是Intel Xeon E3-1225 v2,它有一个L1缓存32KB * 4(4核),运行此代码时它只使用1个核心,因此使用的L1缓存为32KB。

buf1 buf2和buf3小到足以位于L1缓存和L2缓存(L2缓存1MB).SSE和AVX都是带宽限制,但随着dataLen的增加,为什么AVX需要比SSE更多的时间?

2 个答案:

答案 0 :(得分:3)

这是一个有趣的观察。我能够重现你的结果。我通过展开循环来修改你的SSE代码速度(参见下面的代码)。现在对于SSE dataLen=2864显然更快,对于更小的值,它几乎和AVX一样快。对于更大的值,它仍然更快。这是由于SSE代码中的承载循环依赖性(即展开循环会增加指令级并行性(ILP))。我没有尝试进一步展开。展开AVX代码没有帮助。

我对你的问题没有明确的答案。我的预感是它与ILP以及诸如Sandy Bridge之类的AVX处理器只能同时加载两个128位字(SSE宽度)而不是两个256位字这一事实有关。因此在SSE代码中,它可以同时执行一个SSE加法,一个SSE乘法,两个SSE加载和一个SSE存储。对于AVX,它可以同时执行一次AVX加载(通过端口2和3上的两个128位加载),一个AVX乘法,一个AVX加法和一个128位存储(AVX宽度的一半)。换句话说,虽然使用AVX,乘法和加法的工作量是SSE的两倍,但加载和存储仍然是128位宽。与SSE相比,这可能会导致与AVX相比ILP更低,有时代码主要由加载和存储支配?

有关端口和ILP的详细信息,请参阅此Haswell, Sandy Bridge, Nehalem ports compared

__m128 p1, p2, p3, p1_v2, p2_v2, p3_v2;
for(int j=0; j<N; j++)
    for(int i=0; i<dataLen; i+=8)
    {
        p1 = _mm_load_ps(&buf1[i]);
        p1_v2 = _mm_load_ps(&buf1[i+4]);
        p2 = _mm_load_ps(&buf2[i]);
        p2_v2 = _mm_load_ps(&buf2[i+4]);
        p3 = _mm_load_ps(&buf3[i]);
        p3_v2 = _mm_load_ps(&buf3[i+4]);
        p3 = _mm_add_ps(_mm_mul_ps(p1, p2), p3);
        p3_v2 = _mm_add_ps(_mm_mul_ps(p1_v2, p2_v2), p3_v2);
        _mm_store_ps(&buf3[i], p3);
        _mm_store_ps(&buf3[i+4], p3_v2);
    }

答案 1 :(得分:1)

我认为这是Sandy Bdrige架构缓存系统的缺陷。我可以在Ivy Brdige CPU上重现相同的结果,但不能在Haswell CPU上重现相同的结果,但是在使用L3时会遇到同样的问题。我认为这对AVX来说是一个很大的缺陷。英特尔应该在下一步或下一个架构上解决这个问题。

N = 1000000
datalen = 2000
SSE time used: 280000 us,
AVX time used: 156000 us,

N = 1000000
datalen = 4000 <- it's still fast on Haswell using L2
SSE time used: 811000 us,
AVX time used: 702000 us,

N = 1000000
datalen = 6000
SSE time used: 1216000 us,
AVX time used: 1076000 us,

N = 1000000
datalen = 8000
SSE time used: 1622000 us,
AVX time used: 1466000 us,

N = 100000  <- reduced
datalen = 20000 <- fit in L2 : 256K / 23 = 21845.3
SSE time used: 405000 us,
AVX time used: 374000 us,

N = 100000  
datalen = 40000 <- need L3
SSE time used: 1185000 us,
AVX time used: 1263000 us,

N = 100000  
datalen = 80000
SSE time used: 2340000 us,
AVX time used: 2527000 us,