2013-02-10 2 views
3

Я пытаюсь сравнить две строки из pixel s.Почему это медленнее, чем memcmp

A pixel определяется как struct, содержащий значения 4 float (RGBA).

Причина, по которой я не использую memcmp, состоит в том, что мне нужно вернуть положение 1-го другого пикселя, который 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; 
} 

Затем я обнаружил, что обработка значений в виде целых чисел вместо поплавков ускорили вещи немного, и теперь только ~ 20% медленнее, чем 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; 
} 

Из того, что я читал на другие вопросы, реализация МС memcmp также осуществляется с использованием SSE. Мой вопрос в том, что другие трюки в реализации MS имеют его рукав, который у меня нет? Как он все еще быстрее, хотя он побайтовое сравнение?

Является ли выравнивание проблемой? Если pixel содержит 4 поплавки, не будет ли выделен массив пикселей уже на границе 16 байтов?

Я компилирую с помощью /o2 и всех флагов оптимизации.

+2

Нет, нет гарантии, что он будет выровнен по 16 байт, если вы сами не позаботитесь о нем. – interjay

+0

Да, выравнивание - проблема. Используемые вами варианты компиляции также актуальны. Вы также должны показать сгенерированный asm. Возможно, компилятору не хватает развертки цикла или какой-либо другой оптимизации вашего кода. –

ответ

3

Я написал зЬгстр/memcmp оптимизации с SSE (! И MMX/3DNow), и первым шагом, чтобы гарантировать, что массивы в соответствии, насколько это возможно - вы можете обнаружить, что вы должны сделать первое и/или последние байты «по одному».

Если вы можете выровнять данные до того, как они попадут в цикл [если ваш код выполняет выделение], тогда это идеально.

Вторая часть состоит в том, чтобы развернуть цикл, так что вы не получите так много «если цикл не в конце, прыжок назад в начало цикла» - если цикл довольно длинный.

Вы можете обнаружить, что предварительная загрузка следующих данных ввода перед выполнением условия «выходим сейчас» тоже помогает.

Редактировать: В последнем абзаце может потребоваться пример. Этот код предполагает развернутый цикл, по меньшей мере, двух:

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

Примерно так.

+1

Развертывание сделало все! Я развернулся в 4 раза и пошел с 20% медленнее, чем 'memcmp', на 20% быстрее, чем' memcmp'. По какой-то причине выравнивание, казалось, не имело никакого значения ('malloc' vs' _aligned_malloc (16) '). Не могли бы вы объяснить свой последний абзац? Я не понимаю, что ты имеешь в виду. – Rotem

+1

Если ваши входные массивы не были выровнены по 16 байт, вы получите крах, потому что вы используете выровненные версии функций загрузки (например, '_mm_load_si128()' versus '_mm_loadu_si128()'. Если вы хотите быть надежным в отношении потенциально не согласованных входов, тогда вы можете использовать нестандартные функции загрузки, но будет небольшой удар производительности, даже если массивы будут выровнены. –

0

Я не могу помочь вам непосредственно, потому что я использую Mac, но есть простой способ выяснить, что происходит:

Вы просто шаг в тетсру в режиме отладки и переключиться в режим просмотра разборке. Поскольку memcpy - простая небольшая функция, вы легко поймете все трюки реализации.

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]); 
.... 
+0

Спасибо, этот код был полезен в некотором смысле, хотя я имею дело с данными, которые могут быть предварительно выровнены, поэтому все проверки работоспособности и логика хвоста не имеют отношения к моему делу. – Rotem

+0

@Rotem, если вы можете выровнять данные, вам это действительно не нужно, проверили ли вы код с выравниванием? – iabdalkader

+0

Да, результаты были идентичны (выровнены через '_aligned_malloc'). Я не знаю, как объяснить этот факт. – Rotem

Смежные вопросы