为什么这比memcmp慢

时间:2013-02-10 10:01:52

标签: c++ visual-c++ sse memcmp

我想比较两行pixel s。

pixel定义为struct,其中包含4个float值(RGBA)。

我没有使用memcmp的原因是因为我需要返回第一个不同像素的位置,memcmp不会这样做。

我的第一个实现使用SSE内在函数,比memcmp慢约30%:

inline int PixelMemCmp(const Pixel* a, const Pixel* b, int count)
{
    for (int i = 0; i < count; i++)
    {
        __m128 x = _mm_load_ps((float*)(a + i));
        __m128 y = _mm_load_ps((float*)(b + i));
        __m128 cmp = _mm_cmpeq_ps(x, y);
        if (_mm_movemask_ps(cmp) != 15) return i;
    }
    return -1;
}

然后我发现将值视为整数而不是浮点数加速了一些事情,现在只比memcmp慢了约20%。

inline int PixelMemCmp(const Pixel* a, const Pixel* b, int count)
{
    for (int i = 0; i < count; i++)
    {
        __m128i x = _mm_load_si128((__m128i*)(a + i));
        __m128i y = _mm_load_si128((__m128i*)(b + i));
        __m128i cmp = _mm_cmpeq_epi32(x, y);
        if (_mm_movemask_epi8(cmp) != 0xffff) return i; 
    }
    return -1;
}

根据我在其他问题上的阅读,memcmp的MS实施也是使用SSE实现的。我的问题是,MS实现的其他技巧是什么呢?我不这样做?即使它进行逐字节比较,它仍然如何更快?

对齐是一个问题吗?如果pixel包含4个浮点数,那么是否已经在16字节边界上分配了一个像素数组?

我正在使用/o2和所有优化标记进行编译。

3 个答案:

答案 0 :(得分:3)

你可能想要检查这个memcmp SSE implementation,特别是__sse_memcmp函数,它从一些健全性检查开始,然后检查指针是否对齐:

aligned_a = ( (unsigned long)a & (sizeof(__m128i)-1) );
aligned_b = ( (unsigned long)b & (sizeof(__m128i)-1) );

如果它们对齐,它会逐字节地比较指针,直到对齐地址的开始:

 while( len && ( (unsigned long) a & ( sizeof(__m128i)-1) ) )
{
   if(*a++ != *b++) return -1;
   --len;
}

然后将剩余内存与SSE指令进行比较,类似于您的代码:

 if(!len) return 0;
while( len && !(len & 7 ) )
{
__m128i x = _mm_load_si128( (__m128i*)&a[i]);
__m128i y = _mm_load_si128( (__m128i*)&b[i]);
....

答案 1 :(得分:3)

我已经用SSE(和MMX / 3DNow!)编写了strcmp / memcmp优化,第一步是确保数组尽可能对齐 - 你可能会发现你必须做第一次和/或最后一次字节“一次一个”。

如果您可以在数据到达循环之前对齐[如果您的代码进行了分配],那么这是理想的。

第二部分是展开循环,所以你不会得到这么多“如果循环不在最后,跳回循环的开头” - 假设循环很长。

您可能会发现在执行“我们现在离开”条件之前预加载输入的下一个数据也有帮助。

编辑:最后一段可能需要一个例子。此代码假设展开的循环至少为两个:

 __m128i x = _mm_load_si128((__m128i*)(a));
 __m128i y = _mm_load_si128((__m128i*)(b));

 for(int i = 0; i < count; i+=2)
 {
    __m128i cmp = _mm_cmpeq_epi32(x, y);

    __m128i x1 = _mm_load_si128((__m128i*)(a + i + 1));
    __m128i y1 = _mm_load_si128((__m128i*)(b + i + 1));

    if (_mm_movemask_epi8(cmp) != 0xffff) return i; 
    cmp = _mm_cmpeq_epi32(x1, y1);
    __m128i x = _mm_load_si128((__m128i*)(a + i + 2));
    __m128i y = _mm_load_si128((__m128i*)(b + i + 2));
    if (_mm_movemask_epi8(cmp) != 0xffff) return i + 1; 
}

大致类似的东西。

答案 2 :(得分:0)

我无法直接帮助你,因为我正在使用Mac,但有一种简单的方法可以弄清楚会发生什么:

您只需在调试模式下进入memcpy并切换到反汇编视图。由于memcpy是一个简单的小函数,你很容易弄清楚所有的实现技巧。