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);
}
下面是總和環的內部部分(不帶其序言和結尾)所生成的彙編代碼。請注意大多數金額如何有效保存在mm1-mm6中。與用於使數字添加到每個總和的mm0和用於最後兩個和的mm7相反。這個程序的七和版本似乎沒有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寄存器的版本。它應該仍然能夠利用現代處理器fetch 64 to 128 bytes into the cache at a time這一事實。 8-sum版本比1-sum版本更快並不明顯。 1-sum版本獲取完全相同數量的內存,並執行完全相同數量的MMX添加。您將需要相應交錯輸入。
如果您的目標硬件允許,請考慮使用SSE instructions。這些可以一次添加4個32位值。自1999年Pentium III以來,SSE在intel CPU中可用。