SIMD代码与标量代码

时间:2010-12-09 10:16:16

标签: c optimization sse simd sse2

以下循环执行数百次 elma and elmc are both unsigned long (64-bit) arrays, so is res1 and res2.

unsigned long simdstore[2];  
__m128i *p, simda, simdb, simdc;  
p = (__m128i *) simdstore;  

for (i = 0; i < _polylen; i++)  
{  

    u1 = (elma[i] >> l) & 15;  
    u2 = (elmc[i] >> l) & 15;  
    for (k = 0; k < 20; k++)  
    {     

    1.  //res1[i + k] ^= _mulpre1[u1][k];  
    2.  //res2[i + k] ^= _mulpre2[u2][k];               
    3.        _mm_prefetch ((const void *) &_mulpre2[u2][k], _MM_HINT_T0);
    4.        _mm_prefetch ((const void *) &_mulpre1[u1][k], _MM_HINT_T0);
    5.        simda = _mm_set_epi64x (_mulpre2[u2][k], _mulpre1[u1][k]);
    6.        _mm_prefetch ((const void *) &res2[i + k], _MM_HINT_T0); 
    7.        _mm_prefetch ((const void *) &res1[i + k], _MM_HINT_T0); 
    8.        simdb = _mm_set_epi64x (res2[i + k], res1[i + k]);  
    9.        simdc = _mm_xor_si128 (simda, simdb);  
    10.        _mm_store_si128 (p, simdc);  
    11.        res1[i + k] = simdstore[0];  
    12.        res2[i + k] = simdstore[1];                      
    }     
}  

unsigned long simdstore[2]; __m128i *p, simda, simdb, simdc; p = (__m128i *) simdstore; for (i = 0; i < _polylen; i++) { u1 = (elma[i] >> l) & 15; u2 = (elmc[i] >> l) & 15; for (k = 0; k < 20; k++) { 1. //res1[i + k] ^= _mulpre1[u1][k]; 2. //res2[i + k] ^= _mulpre2[u2][k]; 3. _mm_prefetch ((const void *) &_mulpre2[u2][k], _MM_HINT_T0); 4. _mm_prefetch ((const void *) &_mulpre1[u1][k], _MM_HINT_T0); 5. simda = _mm_set_epi64x (_mulpre2[u2][k], _mulpre1[u1][k]); 6. _mm_prefetch ((const void *) &res2[i + k], _MM_HINT_T0); 7. _mm_prefetch ((const void *) &res1[i + k], _MM_HINT_T0); 8. simdb = _mm_set_epi64x (res2[i + k], res1[i + k]); 9. simdc = _mm_xor_si128 (simda, simdb); 10. _mm_store_si128 (p, simdc); 11. res1[i + k] = simdstore[0]; 12. res2[i + k] = simdstore[1]; } }

在for循环中,标量版本的代码(注释)运行速度比simd代码快两倍。下面提到了上述行的cachegrind输出(指令读取)。

第1行:668,460,000 2 2 第2行:668,460,000 1 1 3号线:​​89,985,000 1 1 第4行:89,985,000 1 1 5号线:617,040,000 2 2 第6行:44,992,500 0 0
7号线:44,992,500 0 0
8号线:539,910,000 1 1 第9行:128,550,000 0 0
第10行:。 。 。
11号线:205,680,000 0 0
第12行:205,680,000 0 0

从上图中可以看出,评论(标量代码)所需的指令数量明显少于simd代码。

如何更快地制作此代码?

2 个答案:

答案 0 :(得分:3)

取出_mm_prefetch内在函数 - 他们在这种背景下没有取得任何成果,甚至可能会损害性能。如果(a)您有足够的带宽并且(b)您可以在实际需要数据之前几百个时钟周期发出预取提示,则预取仅有好处。我认为(a)和(b)都不属于你的情况。

答案 1 :(得分:1)

你的性能问题是:

_mm_set_epi64x(_mulpre2 [u2] [k],_ mulpre1 [u1] [k]);

mm_set(a,b,c,d)类内在函数非常慢。 只有单个参数集内在函数(也就是广播)很快。

我看了他们在汇编代码中做了什么。

它们基本上在堆栈上创建一个数组,使用正常的内存移动(mov DWORD)将两个整数从它们当前所在的多维数组移动到堆栈数组。然后从堆栈数组中使用XMM内存移动(mov XMWORD)。

标量版本直接从内存转到寄存器。更快!

您看到开销来自于XMM寄存器一次只能与128位进行通信这一事实,因此您的程序在加载之前首先在另一个内存区域中对128位进行排序。

如果有办法将64位值直接移入或移出普通寄存器到XMM寄存器,我仍然在寻找它。

为了提高使用SSE / XMM寄存器的速度,您的数据可能需要在内存中按顺序排列。如果每次无序加载可以执行多个XMM操作,则将无序数据加载到XMM寄存器中是值得的。在这里你做一个XOR操作。