SIMD代码比标量代码运行得慢

时间:2010-12-09 04:47:14

标签: c optimization sse simd sse2

elmaelmc都是unsigned long数组。 res1res2也是如此。

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++)  
    {
        //res1[i + k] ^= _mulpre1[u1][k];  
        //res2[i + k] ^= _mulpre2[u2][k];               

        simda = _mm_set_epi64x (_mulpre2[u2][k], _mulpre1[u1][k]);  
        simdb = _mm_set_epi64x (res2[i + k], res1[i + k]);  
        simdc = _mm_xor_si128 (simda, simdb);  
        _mm_store_si128 (p, simdc);  
        res1[i + k] = simdstore[0];  
        res2[i + k] = simdstore[1];                     
    }     
}  

在for循环中包含非simd和simd版本的XOR元素。第二个for循环中的前两行执行显式XOR,而其余的则执行相同操作的simd版本。

此循环从外部调用数百次,因此优化此循环将有助于缩短总计算时间。

问题是simd代码运行比标量代码慢很多倍。

编辑: 完成部分展开

__m128i *p1, *p2, *p3, *p4;  
p1 = (__m128i *) simdstore1;  
p2 = (__m128i *) simdstore2;  
p3 = (__m128i *) simdstore3;  
p4 = (__m128i *) simdstore4;  

for (i = 0; i < 20; i++)  
{
    u1 = (elma[i] >> l) & 15;  
    u2 = (elmc[i] >> l) & 15;  
    for (k = 0; k < 20; k = k + 4)  
    {
        simda1  = _mm_set_epi64x (_mulpre2[u2][k], _mulpre1[u1][k]);  
        simda2  = _mm_set_epi64x (_mulpre2[u2][k + 1], _mulpre1[u1][k + 1]);  
        simda3  = _mm_set_epi64x (_mulpre2[u2][k + 2], _mulpre1[u1][k + 2]);  
        simda4  = _mm_set_epi64x (_mulpre2[u2][k + 3], _mulpre1[u1][k + 3]);  

        simdb1  = _mm_set_epi64x (res2[i + k], res1[i + k]);  
        simdb2  = _mm_set_epi64x (res2[i + k + 1], res1[i + k + 1]);  
        simdb3  = _mm_set_epi64x (res2[i + k + 2], res1[i + k + 2]);  
        simdb4  = _mm_set_epi64x (res2[i + k + 3], res1[i + k + 3]);  

        simdc1  = _mm_xor_si128 (simda1, simdb1);  
        simdc2  = _mm_xor_si128 (simda2, simdb2);  
        simdc3  = _mm_xor_si128 (simda3, simdb3);  
        simdc4  = _mm_xor_si128 (simda4, simdb4);  

        _mm_store_si128 (p1, simdc1);  
        _mm_store_si128 (p2, simdc2);  
        _mm_store_si128 (p3, simdc3);  
        _mm_store_si128 (p4, simdc4);  

        res1[i + k]= simdstore1[0];  
        res2[i + k]= simdstore1[1]; 
        res1[i + k + 1]= simdstore2[0];  
        res2[i + k + 1]= simdstore2[1];   
        res1[i + k + 2]= simdstore3[0];  
        res2[i + k + 2]= simdstore3[1]; 
        res1[i + k + 3]= simdstore4[0];  
        res2[i + k + 3]= simdstore4[1];   
    }  
}  

但是,结果并没有太大变化;它仍然需要标量码的两倍。

4 个答案:

答案 0 :(得分:6)

免责声明:我来自PowerPC背景,所以我在这里说的可能是完全的h ..但是,当你试图立即访问你的结果时,你就停止了你的矢量管道。

最好将所有内容保存在矢量管道中。只要你从vector到int或float进行任何类型的转换,或者将结果存储到内存中,你就会停滞不前。

处理SSE或VMX时的最佳操作模式是:加载,处理,存储。将数据加载到向量寄存器中,进行所有向量处理,然后将其存储到内存中。

我建议:保留几个__m128i寄存器,多次展开循环,然后存储它。

编辑:此外,如果您展开,并且如果您将res1和res2对齐16个字节,您可以将结果直接存储在内存中,而无需通过此simdstore间接,这可能是LHS和另一个停顿。

编辑:忘了显而易见的。如果您的polylen通常很大,请不要忘记在每次迭代时都进行数据缓存预取。

答案 1 :(得分:4)

代码看起来res1和res2的方式似乎是完全独立的向量。 然而,您将它们混合在同一个寄存器中以对它们进行混合。

我会使用不同的寄存器,如下所示(The 向量必须全部对齐)。

__m128i x0, x1, x2, x3;  
for (i = 0; i < _polylen; i++)  
{  

    u1 = (elma[i] >> l) & 15;  
    u2 = (elmc[i] >> l) & 15;  
    for (k = 0; k < 20; k+=2)  
    {     
        //res1[i + k] ^= _mulpre1[u1][k];
        x0= _mm_load_si128(&_mulpre1[u1][k]);
        x1= _mm_load_si128(&res1[i + k]);
        x0= _mm_xor_si128 (x0, x1);
        _mm_store_si128 (&res1[i + k], x0);
        //res2[i + k] ^= _mulpre2[u2][k];               
        x2= _mm_load_si128(&_mulpre2[u2][k]);
        x3= _mm_load_si128(&res2[i + k]);
        x2= _mm_xor_si128 (x2, x3);
        _mm_store_si128 (&res2[i + k], x2);
   }     
}  

请注意,我只使用4个寄存器。您可以手动展开以在x86_64

中使用x86或更多的所有8个寄存器

答案 2 :(得分:4)

相对于正在执行的加载和存储的数量,您在这里进行的计算非常少,因此您看不到SIMD带来的好处。在这种情况下,您可能会发现使用标量代码更有用,特别是如果您有一个可以在64位模式下使用的x86-64 CPU。这将减少负载和存储的数量,这是目前性能的主要因素。

(注意:你可能展开循环,特别是如果你使用的是Core 2或更新版本。)

答案 3 :(得分:2)

我也不是SIMD专家,但看起来你也可以从预取数据中获益,再加上推出的EboMike。如果你将res1和res2合并为一个对齐的数组(结构取决于其他用途),也可能有帮助,那么你不需要额外的复制,你可以直接在它上面操作。