VS2010 выполняет достойную работу по оптимизации эквивалентного кода с использованием встроенных функций. В большинстве случаев он компилирует внутреннее:
sum = _mm_add_pi32(sum, *(__m64 *) &intArray[i + offset]);
в нечто вроде:
movq mm0, mmword ptr [eax+8*offset]
paddd mm1, mm0
Это не так кратко, как ваш padd mm1, [esi+edx+8*offset]
, но, возможно, довольно близко. Время выполнения, скорее всего, зависит от выборки из памяти.
Проблема в том, что VS, похоже, любит добавлять регистры MMX только в другие регистры MMX. Приведенная схема работает только для первых 7 сумм. Восьмая сумма требует, чтобы какой-то регистр был временно сохранен в памяти.
Вот полная программа и соответствующая скомпилированная сборка (сборка выпуска):
#include <stdio.h>
#include <stdlib.h>
#include <xmmintrin.h>
void addWithInterleavedIntrinsics(int *interleaved, int count)
{
// sum up the numbers
__m64 sum0 = _mm_setzero_si64(), sum1 = _mm_setzero_si64(),
sum2 = _mm_setzero_si64(), sum3 = _mm_setzero_si64(),
sum4 = _mm_setzero_si64(), sum5 = _mm_setzero_si64(),
sum6 = _mm_setzero_si64(), sum7 = _mm_setzero_si64();
for (int i = 0; i < 16 * count; i += 16) {
sum0 = _mm_add_pi32(sum0, *(__m64 *) &interleaved[i]);
sum1 = _mm_add_pi32(sum1, *(__m64 *) &interleaved[i + 2]);
sum2 = _mm_add_pi32(sum2, *(__m64 *) &interleaved[i + 4]);
sum3 = _mm_add_pi32(sum3, *(__m64 *) &interleaved[i + 6]);
sum4 = _mm_add_pi32(sum4, *(__m64 *) &interleaved[i + 8]);
sum5 = _mm_add_pi32(sum5, *(__m64 *) &interleaved[i + 10]);
sum6 = _mm_add_pi32(sum6, *(__m64 *) &interleaved[i + 12]);
sum7 = _mm_add_pi32(sum7, *(__m64 *) &interleaved[i + 14]);
}
// reset the MMX/floating-point state
_mm_empty();
// write out the sums; we have to do something with the sums so that
// the optimizer doesn't just decide to avoid computing them.
printf("%.8x %.8x\n", ((int *) &sum0)[0], ((int *) &sum0)[1]);
printf("%.8x %.8x\n", ((int *) &sum1)[0], ((int *) &sum1)[1]);
printf("%.8x %.8x\n", ((int *) &sum2)[0], ((int *) &sum2)[1]);
printf("%.8x %.8x\n", ((int *) &sum3)[0], ((int *) &sum3)[1]);
printf("%.8x %.8x\n", ((int *) &sum4)[0], ((int *) &sum4)[1]);
printf("%.8x %.8x\n", ((int *) &sum5)[0], ((int *) &sum5)[1]);
printf("%.8x %.8x\n", ((int *) &sum6)[0], ((int *) &sum6)[1]);
printf("%.8x %.8x\n", ((int *) &sum7)[0], ((int *) &sum7)[1]);
}
void main()
{
int count = 10000;
int *interleaved = new int[16 * count];
// create some random numbers to add up
// (note that on VS2010, RAND_MAX is just 32767)
for (int i = 0; i < 16 * count; ++i) {
interleaved[i] = rand();
}
addWithInterleavedIntrinsics(interleaved, count);
}
Вот сгенерированный ассемблерный код для внутренней части цикла суммы (без пролога и эпилога). Обратите внимание, что большинство сумм эффективно хранятся в мм1-мм6. Сравните это с mm0, который используется для добавления числа к каждой сумме, и с mm7, который служит для двух последних сумм. Версия этой программы с 7 суммами, кажется, не имеет проблемы mm7.
012D1070 movq mm7,mmword ptr [esp+18h]
012D1075 movq mm0,mmword ptr [eax-10h]
012D1079 paddd mm1,mm0
012D107C movq mm0,mmword ptr [eax-8]
012D1080 paddd mm2,mm0
012D1083 movq mm0,mmword ptr [eax]
012D1086 paddd mm3,mm0
012D1089 movq mm0,mmword ptr [eax+8]
012D108D paddd mm4,mm0
012D1090 movq mm0,mmword ptr [eax+10h]
012D1094 paddd mm5,mm0
012D1097 movq mm0,mmword ptr [eax+18h]
012D109B paddd mm6,mm0
012D109E movq mm0,mmword ptr [eax+20h]
012D10A2 paddd mm7,mm0
012D10A5 movq mmword ptr [esp+18h],mm7
012D10AA movq mm0,mmword ptr [esp+10h]
012D10AF movq mm7,mmword ptr [eax+28h]
012D10B3 add eax,40h
012D10B6 dec ecx
012D10B7 paddd mm0,mm7
012D10BA movq mmword ptr [esp+10h],mm0
012D10BF jne main+70h (12D1070h)
Так что вы можете сделать?
Профиль 7-сумных и 8-сумных внутренних программ. Выберите тот, который выполняется быстрее.
Профилируйте версию, которая добавляет только один регистр MMX за раз. Тем не менее, он должен использовать тот факт, что современные процессоры извлекают 64–128 байтов в кэш за раз . Не очевидно, что версия с 8 суммами будет быстрее, чем версия с 1 суммой. Версия с 1 суммой извлекает точно такой же объем памяти и делает то же самое количество добавлений MMX. Вам нужно будет соответственно чередовать входы.
Если ваше целевое оборудование позволяет, рассмотрите возможность использования инструкций SSE . Они могут добавлять 4 32-битных значения за раз. SSE доступен в процессорах Intel начиная с Pentium III в 1999 году.