Я экспериментировал с 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;
}
Вы пытались реализовать это с репрессивными строками? На IvyBridge им следовало бы улучшить d – Leeor
Не могу сказать прямо сейчас, но прежний опыт (и я считаю, что это уже было проверено на IvyBridge) показывает, что производительность REP близка к циклу «for», а в случае сравнения строк алгоритмически хуже, потому что для вызова REP вы нужно указать счетчик, что требует вычисления длины строки, что требует времени и фактически не требуется в большинстве случаев ... –
В таблицах Agner указано, что 'pcmpestrm' равно 8 мкп, тогда как' pcmpistrm' составляет всего 3 μops с регистром-операндом. Это может объяснить разницу. – fuz