2010-05-23 79 views
1

我有一個內聯彙編程序循環,它使用MMX指令從int32數據數組中累積添加元素。尤其是,它使用MMX寄存器可容納16個int32s並行計算16個不同累積和的事實。使用MMX內在函數和Microsoft C++的堆棧使用情況

我現在想將這段代碼轉換爲MMX內在函數,但是我恐怕會遭受性能損失,因爲不能明確地構造編譯器以使用8個MMX寄存器來容納16個獨立的總和。

有人可以對此進行評論,也許提出一個解決方案如何將下面的代碼段轉換爲使用intrinsics?

==聯彙編器(僅在循環內的部分)==

paddd mm0, [esi+edx+8*0] ; add first & second pair of int32 elements 
paddd mm1, [esi+edx+8*1] ; add third & fourth pair of int32 elements ... 
paddd mm2, [esi+edx+8*2] 
paddd mm3, [esi+edx+8*3] 
paddd mm4, [esi+edx+8*4] 
paddd mm5, [esi+edx+8*5] 
paddd mm6, [esi+edx+8*6] 
paddd mm7, [esi+edx+8*7] ; add 15th & 16th pair of int32 elements 
  • ESI指向數據陣列
  • EDX提供數據陣列中的偏移爲當前循環的開始迭代
  • 數據陣列被安排成使得16個獨立和的元素被交錯。

回答

2

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) 

那麼你能做什麼?

  1. 描述基於7-sum和8-sum內在的程序。選擇執行更快的那個。

  2. 描述一次只添加一個MMX寄存器的版本。它應該仍然能夠利用現代處理器fetch 64 to 128 bytes into the cache at a time這一事實。 8-sum版本比1-sum版本更快並不明顯。 1-sum版本獲取完全相同數量的內存,並執行完全相同數量的MMX添加。您將需要相應交錯輸入。

  3. 如果您的目標硬件允許,請考慮使用SSE instructions。這些可以一次添加4個32位值。自1999年Pentium III以來,SSE在intel CPU中可用。