3

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

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

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

dataLen = 2864 SSE 时间: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,它有一个 32KB*4(4 核)的 L1 缓存,运行此代码时它只使用 1 个核,所以使用的 L1 缓存为 32KB。

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

4

2 回答 2

3

这是一个有趣的观察。我能够重现您的结果。通过展开循环,我设法提高了您的 SSE 代码速度(请参见下面的代码)。现在,SSEdataLen=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 端口比较

__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);
    }
于 2013-09-09T11:37:34.857 回答
1

我认为这是 Sandy Bdrige 架构的缓存系统的缺陷。我可以在 Ivy Brdige CPU 上重现相同的结果,但不能在 Haswell CPU 上重现,但 haswell 在访问 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,
于 2013-09-10T02:35:43.740 回答