SSE42 и STTNI - PcmpEstrM в два раза медленнее, чем PcmpIstrM, правда?

Я экспериментирую с инструкциями SSE42 и STTNI и получил странный результат - PcmpEstrM (работает с явными строками длины) работает в два раза медленнее, чем PcmpIstrM (строки неявной длины),

  • В моем i7 3610QM разница составляет 2366,2 мс против 1202,3 мс - 97%.
  • В разнице i5 3470 разница не настолько велика, но по-прежнему значительна = 3206,2 мс против 2623,2 мс - 22%.

Оба являются "Ivy Bridge" - странно, что у них так разные "различия" (по крайней мере, я не вижу технических отличий в их спецификациях - http://www.cpu-world.com/Compare_CPUs/Intel_AW8063801013511,Intel_CM8063701093302/).

Справочное руководство по оптимизации архитектуры Intel 64 и IA-32 упоминает о той же пропускной способности = 11 и latency = 3 для PcmpEstrM и PcmpIstrM. Поэтому я ожидаю аналогичную производительность для обоих.

Q: Разница в том, что я получил практически спроектированный/ожидаемый результат, или я неправильно использую эту инструкцию?

Ниже приведен мой сценарий тестирования (VS 2012). Логика довольно проста - сканируйте 16 МБ текста, чтобы найти подходящий символ. Поскольку ни одна из стогов сена и ниток не содержит нулевых терминаторов, я ожидаю, что и E, и я будут иметь схожие характеристики.

PS: Я пробовал опубликовать этот вопрос на форуме intel dev, но они идентифицируют его как спам: (

#include "stdafx.h"
#include <windows.h>
#define BEGIN_TIMER(NAME)                       \
    {                                           \
        LARGE_INTEGER   __freq;                 \
        LARGE_INTEGER   __t0;                   \
        LARGE_INTEGER   __t1;                   \
        double          __tms;                  \
        const char*     __tname = NAME;         \
        char            __tbuf[0xff];           \
        \
        QueryPerformanceFrequency(&__freq);     \
        QueryPerformanceCounter(&__t0);         
#define END_TIMER()                             \
        QueryPerformanceCounter(&__t1);         \
        __tms = (__t1.QuadPart - __t0.QuadPart) * 1000.0 / __freq.QuadPart; \
        sprintf_s(__tbuf, sizeof(__tbuf), "%-32s = %6.1f ms\n", __tname, __tms ); \
        OutputDebugStringA(__tbuf);             \
        printf(__tbuf);                         \
    }
// 4.1.3 Aggregation Operation
#define SSE42_AGGOP_BITBASE         2
#define SSE42_AGGOP_EQUAL_ANY       (00b << SSE42_AGGOP_BITBASE)
#define SSE42_AGGOP_RANGES          (01b << SSE42_AGGOP_BITBASE)
#define SSE42_AGGOP_EQUAL_EACH      (10b << SSE42_AGGOP_BITBASE)
#define SSE42_AGGOP_EQUAL_ORDERED   (11b << SSE42_AGGOP_BITBASE)
int _tmain(int argc, _TCHAR* argv[])
{
    int cIterations = 1000000;
    int cCycles = 1000;
    int cchData = 16 * cIterations;
    char* testdata = new char[cchData + 16];

    memset(testdata, '*', cchData);
    testdata[cchData - 1] = '+';
    testdata[cchData] = '\0';
    BEGIN_TIMER("PcmpIstrI") {
        for( int i = 0; i < cCycles; i++ ) {
            __asm {
                    push        ecx
                    push        edx
                    push        ebx
                    mov         edi, testdata
                    mov         ebx, cIterations
                    mov         al, '+'
                    mov         ah, al
                    movd        xmm1, eax               // fill low word with pattern
                    pshuflw     xmm1, xmm1, 0           // fill low dqword with pattern
                    movlhps     xmm1, xmm1              // ... and copy it hi dqword
                loop_pcmpistri:
                    PcmpIstrM   xmm1, [edi], SSE42_AGGOP_EQUAL_EACH
                    add         edi, 16
                    sub         ebx, 1
                    jnz         loop_pcmpistri
                    pop         ebx
                    pop         edx
                    pop         ecx
            }
        }
    } END_TIMER();
    BEGIN_TIMER("PcmpEstrI") {
        for( int i = 0; i < cCycles; i++ ) {
            __asm {
                    push        ecx
                    push        edx
                    push        ebx
                    mov         edi, testdata
                    mov         ebx, cIterations
                    mov         al, '+'
                    mov         ah, al
                    movd        xmm1, eax               // fill low word with pattern
                    pshuflw     xmm1, xmm1, 0           // fill low dqword with pattern
                    movlhps     xmm1, xmm1              // ... and copy it hi dqword
                    mov         eax, 15
                    mov         edx, 15
                loop_pcmpestri:
                    PcmpEstrM   xmm1, [edi], SSE42_AGGOP_EQUAL_EACH
                    add         edi, 16
                    sub         ebx, 1
                    jnz         loop_pcmpestri
                    pop         ebx
                    pop         edx
                    pop         ecx
            }
        }
    } END_TIMER();
    return 0;
}

Ответ 1

В соответствии с таблицами инструкций Agner fog, pcmpestrm принимает 8 μops, тогда как pcmpistrm занимает 3 μops на большинстве архитектур. Это должно объяснить разницу в производительности, которую вы наблюдаете. Подумайте о переписывании кода, чтобы вы могли использовать pcmpistrm вместо pcmpestrm, если это возможно.