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 个总和。第 8 项要求将一些寄存器临时保存到内存中。
这是一个完整的程序及其对应的编译程序集(发布版本):
#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);
}
这是 sum 循环内部生成的汇编代码(没有它的序言和结语)。请注意大多数和如何有效地保存在 mm1-mm6 中。与 mm0 相比,mm0 用于将要添加到每个总和中的数字,而与 mm7 相比,它服务于最后两个总和。该程序的 7-sum 版本似乎没有 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-sum 和 8-sum 基于内在函数的程序。选择执行速度更快的那个。
分析一次只添加一个 MMX 寄存器的版本。它应该仍然能够利用现代处理器一次将 64 到 128 个字节提取到缓存中这一事实。8-sum 版本是否会比 1-sum 版本快并不明显。1-sum 版本获取完全相同的内存量,并执行完全相同数量的 MMX 加法。不过,您将需要相应地交错输入。
如果您的目标硬件允许,请考虑使用SSE 指令。这些可以一次添加 4 个 32 位值。自 1999 年 Pentium III 以来,SSE 就在英特尔 CPU 中可用。