3

我正在尝试比较两行pixels。

Apixel被定义为struct包含 4 个float值 (RGBA) 的 a。

我不使用的原因memcmp是因为我需要返回第一个不同像素的位置,但这memcmp是不行的。

我的第一个实现使用SSE内在函数,并且比以下速度慢约 30% memcmp

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.

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;
}

从我读到的其他问题来看,MS 的实现memcmp也是使用SSE. 我的问题是 MS 实现还有哪些我没有的技巧?即使进行逐字节比较,它如何仍然更快?

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

我正在编译/o2所有优化标志。

4

3 回答 3

3

您可能想检查这个memcmp SSE 实现,特别是__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]);
....
于 2013-02-10T11:02:39.090 回答
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; 
}

大致是这样的。

于 2013-02-10T11:25:28.527 回答
0

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

您只需在调试模式下进入 memcpy 并切换到反汇编视图。由于 memcpy 是一个简单的小函数,您将轻松找出所有实现技巧。

于 2013-02-10T10:12:57.250 回答