2014-01-05 11 views
7

Я экспериментировал с 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. Поэтому я ожидаю аналогичную производительность для обоих.

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

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

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

#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; 
} 
+0

Вы пытались реализовать это с репрессивными строками? На IvyBridge им следовало бы улучшить d – Leeor

+0

Не могу сказать прямо сейчас, но прежний опыт (и я считаю, что это уже было проверено на IvyBridge) показывает, что производительность REP близка к циклу «for», а в случае сравнения строк алгоритмически хуже, потому что для вызова REP вы нужно указать счетчик, что требует вычисления длины строки, что требует времени и фактически не требуется в большинстве случаев ... –

+1

В таблицах Agner указано, что 'pcmpestrm' равно 8 мкп, тогда как' pcmpistrm' составляет всего 3 μops с регистром-операндом. Это может объяснить разницу. – fuz

ответ

2

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

+0

Все, что больше 4 микропроцессоров, является микрокодированным, так что это качественная разница в интерфейсе. https://stackoverflow.com/questions/26907523/branch-alignment-for-loops-involving-micro-coded-instructions-on-intel-snb-famil. Кроме того, Agner перечисляет пропускную способность как 4 против 3 (близких к номерам Intel), но влияние на пропускную способность внешнего кода окружающего кода - это то, что показатели задержки/пропускной способности Intel не помогают вам разобраться. –

+0

См. Раздел «Анализ эффективности» [этот ответ] (https://stackoverflow.com/questions/45113527/why-does-mulss-take-only-3-cycles-on-haswell-different-from-agners- инструкция/45114487 # 45114487) для краткого изложения того, как порты задержки/front-end/execution-ports являются тремя основными «измерениями» для характеристики последовательности инструкций (без ветвей или промахов в кэше). –

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

  • Нет связанных вопросов^_^