程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 更多關於編程 >> 匯編優化提醒

匯編優化提醒

編輯:更多關於編程

匯編優化提醒。本站提示廣大學習愛好者:(匯編優化提醒)文章只能為提供參考,不一定能成為您想要的結果。以下是匯編優化提醒正文


你需記住的最主要的工作就是代碼消費的時光!分歧的辦法可以加快你的代碼或許不克不及,所以你要多多測驗考試。因此盤算代碼消費的時光來看看你測驗考試的每一個辦法能否可以提速是件很主要的工作。

;=========================低級=========================

<1>釋放一切8-CPU存放器,以使它們用於你本身的代碼中

push ebx
push esi
push edi
push ebp ;必需在轉變ESP之前完成
;裝載ESI、EDI和其他傳遞棧中值的存放器,這必需在釋放ESP之前完成。
movd mm0,esp ; 沒有入棧/出棧操作跨越此處,
xor ebx,ebx ; 所以你如何保留呢?一個變量嘛!
mov esp,5
inner_loop:
mov [eax_save],eax ; eax_save是一個全局變量弗成能是局
; 部的,由於那請求EBP來指向它。
add ebx,eax
mov eax,[eax_save] ; 存儲 eax
movd esp,mm0 ; 必需在做POPs之前完成
pop ebp
pop edi
pop esi
pop ebx
ret

<2>存放器的最年夜化應用

多半高等點的編譯器會發生異常多的到內存變量的拜訪。我平日防止那樣做,由於我努力把這些變量放在存放器中。所以要使8-CPU存放器自在應用,以備迫在眉睫~

<3>龐雜指令

防止龐雜指令(lods, stos, movs, cmps, scas, loop, xadd, enter, leave)。龐雜指令就是那些可以做很多工作的指令。例如,stosb向內存寫1字節,同時增長EDI。 這些龐雜指令阻攔晚期的Pentium疾速運轉,由於處置器的成長趨向是力使本身更像精簡指令集盤算機(RISC)。應用rep和字符串指令照舊很快----而這是獨一的破例。

<4>不要再P4上應用INC/DEC

在P4上用ADD/SUB取代INC/DEC。平日前者更快些。ADD/SUB消耗0.5 cycle而INC/DEC消耗1 cycle.

<5>輪回移位(rotating)

防止用存放器作輪回移位計數器;立刻數作計數器時,除1,不要用其他值。

<6>清除不用要的比擬指令

經由過程公道應用前提跳轉指令來清除不用要的比擬指令,然則,這些前提跳轉指令時基於標記位的。而這些標記位曾經被後面的算術指令設置了。
dec ecx
cmp ecx,0
jnz loop_again
優化為:
dec ecx
jnz loop_again

<7>lea指令的應用

lea異常酷,除在P4上它表示得慢一點。你可以在這一條指令中履行很多數學運算,而且它不會影響標記存放器。所以你可以把它放在一個正在被修正的存放器和一個觸及標記位的前提跳轉指令中央。
top_of_loop:
dec eax
lea edx,[edx*4+3] ; 乘4加3。不影響標記位
jnz top_of_loop ; 所以jnz斷定的是dec eax履行後的狀況字

<8>ADC和SBB的應用

很多編譯器都不充足應用ADC和SBB。你可以用它們很好地提速,如:把兩個64-bit的數加在一路或許兩個年夜數相加。記牢:ADC和SBB在P4上慢。當你手頭有相似任務時,你可使用addq和用MMX來完成。所以第二個優化建議是:用MMX做如許的加減法。但你的處置器必需支撐MMX。
add eax,[fred]
adc edx,[fred+4]
;上面的3條指令完成異樣的功效
movd mm0,[fred] ;獲得MM0中的32-bit的值
movd mm1,[fred+4] ;獲得MM1中的32-bit的值
paddq mm0,mm1 這是一種未優化的辦法,現實上,你可以在後面的輪回中預取MM0和MM1。我如許做是為了好懂得。

<9>ROL, ROR, RCL, RCR 和 BSWAP

應用BSWAP指令把Big Endian數據轉換成Little Endian格局是很酷的一種辦法。你也能夠應用它暫時存儲存放器的高位的的值(16-bit或8-bit)。相似地,你可使用ROL/ROR來存儲8-bit或16-bit值。這也是取得更多“存放器”的一種辦法。假如你處置的都是16-bit的值,你可以把你的8個32-bit的存放器轉換成16個16-bit的存放器。如許你就有更多存放器可使用了。RCL和RCR可以很輕易地被應用在盤算存放器中比特位(0 or 1)的若干。切記:P4上ROL, ROR, RCL, RCR 和 BSWAP速度慢。輪回移位指令年夜約比BSWAP快2倍。所以,假如你必需在P4機上應用上述指令,照樣選輪回移位指令吧。

xor edx,edx ; 設置兩個16-bit的存放器為0
mov dx,234 ; 設置低位存放器為234
bswap edx ; 高下位存放器交換
mov dx,345 ; 設置以後低位存放器為345
bswap edx ; 以後高下位交換
add dx,5 ; 以後低位存放器加5
bswap edx ; 高下位交換
add dx,7 ; 以後低位存放器加7


<10>字符串指令

很多編譯器沒有充足應用字符串指令( scas, cmps, stos, movs和 lods)。所以檢測這些指令寫成的函數能否比一些庫函數速度快長短常成心義的。例如:當我在看VC++的strlen()函數時,我真的很驚奇。在radix40代碼中,處置一個100字節的字符串,它竟跑了416 cycles !!!我以為這慢地荒謬!!!

<11>以乘代除

If you have a full 32-bit number and you need to divide, you can simply do a multiply
and take the top 32-bit half as the result. This is faster because multiplication is
faster than division. ( thanks to pdixon for the tip).
假如你有一個滿32-bit(full 32-bit)的數要做除法,你可以簡略地做乘法,然後取高32-bit部門作為成果。如許會快些,由於乘法比除法快!(感激pdixon供給了這個tip)(譯者注:因為程度無限,此tip翻譯尚待商議,並且不克不及給出一個例子。還仰仗列位協助。)

<12>被常數除

這兒有一些很好的信息----如何被常數除(在Agner Fog的pentopt.pdf中)。我(Mark Larson)寫了一個小法式:可以依據你輸出的除數主動發生匯編代碼序列。我會持續探討探討,然後貼出來。這是Agner的文檔的鏈接。Agner's Pentopt PDF (http://www.agner.org/assem/)

<13>睜開(Unrolling)

這是一個指點方針。在普通優化戰略裡,“睜開”常常被疏忽,是以我想為它加個注腳。我老是用數值總數相等的宏來樹立我的“睜開”構造。那樣的話,你可以測驗考試分歧的值來看看哪一種是最輕易的。你願望這個睜開“安身”於L1代碼緩存(或追蹤緩存,trace cache)。應用等價(equate)語句可以很輕易地測驗考試分歧的睜開值來到達最快的速度。
UNROLL_AMT equ 16 ; # 睜開輪回的次數
UNROLL_NUM_BYTES equ 4 ; # 一次輪回處置的字節數
mov ecx,1024
looper:
offset2 = 0
REPEAT UNROLL_AMT
add eax,[edi+offset2]
offset2 = offset2 + UNROLL_NUM_BYTES
add edi,UNROLL_AMT * UNROLL_NUM_BYTES ; 按16*4字節來處置
sub ecx,UNROLL_AMT ; 從輪回計數器中減去睜開輪回的次數
jnz looper

<14>MOVZX指令

應用MOVZX來防止偏心的存放器受阻。我應用MOVZX異常多。很多人起首將滿32-bit存放器(full 32-bit register)XOR一下。然則,MOVZX無需這個過剩的XOR指令而可以完成異樣的工作。並且你必需提早做XOR運算,以後能力應用存放器,而且這個操作是消費必定時光的。但有MOVZX在,你就無需擔憂啦。

<15>用MOVZX來防止SHIFT和AND指令的應用

我用匯編來對C代碼中的位運算提速。the_array是一個dword的數組。代碼功效是獲得數組中一個dword的隨意率性字節。Pass是一個值為0-3的變量。是以這個功效有以下C代碼:
unsigned char c = ((the_array[i])>>(Pass<<3)) & 0xFF;
; 我為了不Pass變量的應用,睜開了輪回4次。所以我獲得了以下更多的代碼:
unsigned char c = (the_array[i])>>0) & 0xFF;
unsigned char c = (the_array[i])>>8) & 0xFF;
unsigned char c = (the_array[i])>>16) & 0xFF;
unsigned char c = (the_array[i])>>24) & 0xFF;
在匯編中,我摒棄SHIFT和AND會產生甚麼呢?勤儉了我2個指令!更不消提P4上SHIFT指令異常慢(4 cycles!!!)的現實。所以假如能夠,盡可能防止應用SHIFT指令。我們以第三行動例,所以只需右移16位便可:
mov eax,[esi] ; esi指向the_array數組
shr eax,16
and eax,0FFh
; 那末我們如何防止SHR和AND的應用呢?我們舉例對dword中的第三個字節做MOVZX運算。
movzx eax,byte ptr [esi+2] ;unsigned char c = (the_array[i])>>16) & 0xFF;

<16>align指令

該偽指令異常主要,它可以對齊你的代碼和數據來很好地提速。對代碼,我平日按4字節界限對齊。關於數據呢,2字節數據按2字節界限對齊,4字節數據按4字節界限對齊,8字節數據按8字節界限對齊,16字節數據按16字節界限對齊。普通情形,假如不以一個16-bit界限對齊SSE或SSE2數據的話,將會發生一個異常。假如有VC++開辟包,你可以在VC++情況中對齊你的數據。VC++參加了對靜態數據和靜態內存的支撐。關於靜態數據,你可使用__declspec(align(4))來按4字節界限對齊。

<17>用於2的冪的BSR指令

你可以把BSR敕令用於變量的2的最高冪的計數。

<18>用XOR置存放器為0

這長短常陳腐的知識了,然則我仍然要重提一下。它也有一個正面利益----清除對存放器的依附性。這就是人們應用存放器之前用XOR置零成風的緣由。但我情願應用MOVZX,由於XOR很狡詐~(看看我後面對MOVZX的評論辯論)在P4上,也增長了PXOR支撐,以清除對XOR的依附性。我以為P3做了異樣的工作。

<19>應用XOR和DIV

假如你肯定你的數據在做除法時是無符號數,應用XOR EDX, EDX,然後DIV。它比CDQ和IDIV快。

<20>盡可能防止顯著的依附關系

假如你修正一個存放器,然後在緊跟的下一行中讓它和某個值停止比擬,相反地,你最好在二者之間拔出其他的存放器的修正指令。依附就是任什麼時候間你修正一個存放器,然後緊隨著對它讀或寫。(譯者注:其實就是AGI延遲; AGI: Address Generation Interlock)
inc edi
inc eax
cmp eax,1 ;這行依附於上一行,所以發生延遲
jz fred
;挪動四周的指令到這兒可以打破這類依附
inc eax
inc edi
cmp eax,1
jz fred

<21>P4機上防止應用的指令

在P4機上防止應用以下指令: adc, sbb, rotate指令, shift指令, inc, dec, lea, 和任何消耗多於 4 uops(微指令)的指令。你怎樣曉得以後運轉代碼的處置器是P4?CPUID敕令!

<22>應用查找表

在P4機上,你可以經由過程樹立查找表,來躲避長時光延遲的指令(後面已列出來)。幸而P4機的內存速度很快,所以假如樹立的查找表不在cache裡,它也其實不會很年夜地影響機能。

<23>應用指針而不是盤算下標(索引)

很多時刻,C說話中的輪回會湧現兩個非冪數的乘法。你可以很輕易地用加法來完成。上面是一個應用構造體的例子:
typedef struct fred
{
int fred;
char bif;
}freddy_type;
freddy_type charmin[80];

freddy_type構造的年夜小是5字節。假如你想在輪回中拜訪它們,編譯器會發生此種代碼----對每一個數組元素的拜訪都乘以5!!!Ewwwwwwwwwwwww(譯者注:語氣詞)!!!那末我們應當如何做呢?
for ( int t = 0; t < 80; t++)
{
charmin[t].fred = rand(); // 編譯器為了獲得偏移量,竟乘以了5,EWWWWWWWW!
charmin[t].bif = (char)(rand() % 256);
}
在匯編中,我們以偏移量0開端,這指向了數據第一個元素。然後我們每次把輪回迭代器加5來防止MUL指令的應用。
mov esi,offset charmin
mov ecx,80
fred_loop:
;...freddy_type構造中FRED和BIF元素的處置敕令
add esi,5 ;指向下一個構造的進口地址
dec ecx
jnz fred_loop
MUL指令的躲避也運用在輪回中。我曾見過人們在輪回中以乘來完成變量增長或許終止前提。相反,你應盡可能用加法。

<24>服從默許分支猜測

盡可能設計你的代碼使向後的前提跳轉常常產生,而且向前的前提跳轉簡直從不產生。這明顯與分支猜測有關。CPU中的靜態分支猜測器(static branch predictor)只應用簡略的規矩來猜想一個前提跳轉能否產生。所以應使向後跳轉的輪回分支接近停止。然後讓統一輪回的特別加入前提(exit condition)履行一個向前的跳轉(這個跳轉只在此跳轉不常常產生的這個特定的前提下加入)。

<25>清除分支

假如能夠,清除分支!這是明顯的,但我見過很多人在他們的匯編代碼中應用了太多的分支。堅持法式的簡略。應用盡量少的分支。

<26>應用CMOVcc來清除分支

我曾見到過CMOVcc指令確切比前提跳轉指令快。所以我建議應用CMOVcc而非前提跳轉指令。當在你的跳轉不輕易被分支猜測邏輯猜到的情形下,它能夠會更快。假如你碰到那種情形,設定基准點(benchmark)看看!

<27>部分變量vs全局變量

在一個進程模塊(子函數)中應用部分變量而不是全局變量。假如你應用部分變量,你會獲得更少的緩存未射中(Cache miss)。

<28>地址盤算

在你須要某地址之前盤算它。不能不說你為了獲得某一特定地址必需盤算一些使人憎惡的器械。例如地址乘20。你可以在須要該地址的代碼之前預算它。

<29>小點兒的存放器

有些時刻,應用小點兒的存放器可以晉升速度。我在radix40代碼中驗證了它。假如你應用EDX來修正上面的代碼,它跑得會慢些。
movzx edx,byte ptr [esi] ;從ascii數組取數據
test dl,ILLEGAL ;位測試
jnz skip_illegal

<30>指令長度

盡可能使你的指令年夜小堅持在8字節以下。

<31>應用存放器傳遞參數

假如能夠,測驗考試用存放器傳遞參數而不是棧。假如你有3個變量要壓棧作為參數,至多有6個內存讀操作和3個內存寫操作。你不能不把每一個變量由內存讀入CPU存放器然後把它們壓入棧。這是3個內存讀操作。然後壓向棧頂發生3個寫操作。然後為何你會壓你從不應用的參數呢?所以,又湧現了起碼3個讀棧操作。(譯者注:兩內存變量不克不及直接傳遞數據)

<32>不要向棧傳遞年夜數據

不要向棧傳遞64-bit或128-bit的數據(或許更年夜)。相反,你應當傳遞指向該數據的指針。

;=========================中級=========================

<33>加法偏向

存放器加向內存比內存加向存放器速度更快。這與指令消耗的微指令(micro-ops)的若干有關。所以,你曉得該怎樣做了。
add eax,[edi] ;假如能夠,不要如許做
add [edi],eax ;這才是首選

<34>指令選擇

盡可能拔取發生起碼微指令和起碼延遲的指令。

<35>未對齊字節數據流的雙字(dword)對齊處置

關於沒有4字節界限對齊的緩沖區,一次分化出一個dword會使機能埋沒。(譯者注:由於地址32-bit對齊時,處置速度最快)你可以經由過程處置開端的X(0-3)字節直到碰到一個4字節對齊的界限處的辦法來躲避這類情形。

<36>應用CMOVcc來復位一個無窮輪回指針

假如你想屢次傳遞一個數組,並當到達數組末尾的時刻復位到開端處,你可使用CMOVcc指令。
dec ecx ; 削減數組索引
cmovz ecx,MAX_COUNT ; 假如在開端處,復位索引為 MAX_COUNT(開頭處)。

<37>以減法來取代乘以0.5的乘法

這能夠不會對一切情形見效除real4變量乘以0.5,或許被2除(real4變量是浮點數),你只需把指數減1便可。但對0.0不見效。對real8變量,要減去00100000h。(這個tip是donkey貼出來的,donkey posted this)
.data
somefp real4 2.5
.code
sub dword ptr [somefp],00800000h ;用2除real4

<38>自修正代碼

P4優化手冊建議防止自修正代碼的應用。我已經見到過幾回自修正代碼可以跑得更快的情形。然則每次應用前你都要明白它在以後情形下會更快。(譯者注:自修正代碼即INC/DEC)

<39>MMX, SSE, SSE2指令

很多編譯器對MMX,SSE和SSE2不會發生很好的代碼。GCC和Intel編譯器對此做了更多任務,所以較其他產物好一些。然則你本身的"年夜腦+手"編譯器在這項任務中的運用依然是很年夜的勝利。

<40>應用EMMS

EMMS在Intel處置器上偏向為很慢的指令。在AMD上它比擬快。平日我不會在例行的基本法式中應用它,由於它慢。我很少在有很多MMX指令的法式中再應用許多浮點指令。反之仍然(vice versa)。所以我常常在做任何浮點運算之前等著履行EMMS。假如你有很多浮點和很少的MMX,那末應在你挪用的一切的MMX子法式(假如有的話)履行完再履行EMMS。但是,在每一個履行MMX的子法式中參加EMMS會使代碼變慢。

<41>轉換到MMX,SSE,SSE2

你的代碼能轉換到MMX, SSE, 或許SSE2嗎?假如能,你可以並行處置來極年夜的晉升速度。

<42>預取數據

這常常未被充足應用。假如你處置一個異常年夜的數組(256KB或更年夜),在P3及今後處置器上應用PREFETCXH指令可使你的代碼不管在甚麼處所都可以提速10-30%。但現實上,假如你沒有公道應用的話,代碼機能還能夠升級。在這方面,“睜開”表示優越,由於我把它睜開為用此指令預取的字節的整數倍。在P3上,此倍數為32,而在P4上,它是128。這就意味著你可以在P4機上很輕易地睜開輪回來一次處置128字節,你可以從預取和睜開中獲得利益。但其實不是在每種情形下睜開為128字節都邑有最好的提速。你可以測驗考試分歧的字節數。
UNROLL_NUM_BYTES equ 4 ; 一次輪回要處置的字節數
UNROLL_AMT equ 128/UNROLL_NUM_BYTES ;我們想睜開輪回以使它每次處置128字節
mov ecx,1024
looper:
offset2 = 0
REPEAT UNROLL_AMT
prefetchnta [edi+offset2+128] ; 在我們須要之前預取128字節到L1緩存
add eax,[edi+offset2]
offset2 = offset2 + UNROLL_NUM_BYTES
add edi,UNROLL_AMT * UNROLL_NUM_BYTES ; 我們處置16*4字節
sub ecx,UNROLL_AMT ; 調劑迭代器到下一輪回
jnz looper

<43>緩存模塊化(Cache Blocking)

不能不說,你必需對內存中的年夜數組挪用很多進程(子函數)。你最好把數組分紅塊兒並裝入緩存(Cache)來削減緩存未射中(cache miss)。例如:你正在履行3D代碼,第一個進程能夠傳遞坐標,第二個進程能夠是范圍年夜小,第三個能夠是變換方法。所以與其在全部的的年夜數組裡翻箱倒櫃似地找,你應當把數據年夜塊(chunk)分紅適於緩存(Cache)的小塊。然後挪用此3個進程。然落後行上面數據年夜塊的相似操作。

<44>TLB啟動(TLB Priming)

TLB就是旁路轉換緩沖,或稱頁表緩沖(Translation Lookaside Buffer),是虛擬內存地址到物理內存地址的轉換部件。它經由過程對頁表進口點的疾速拜訪來晉升機能。未在TLB緩存的代碼或數據會促使緩存未射中,這就下降了代碼速度。處理辦法就是:在你必需讀某頁之前預讀該頁的一個數據字節。我會在前面的tips中供給一個例子。

<45>混入代碼來清除依附

在C代碼中,C編譯器把分歧的代碼塊分離看待。當進入匯編說話級別時,你可以混入(intermix)它們來清除依附。

<46>並行處置

很多編譯器沒有充足應用CPU有2個ALU流水線的現實,而ALU是人們應用的年夜部門。在P4機上你會更自得----假如操作適合,你可以在一個指令周期履行4個ALU運算指令。假如你把義務分派,並行處置之,這也會清除依附。真是一舉兩得!在輪回中采用這個戰略。
looper:
mov eax,[esi]
xor eax,0E5h ;依附上一行
add [edi],eax ;依附上一行
add esi,4
add edi,4
dec ecx
jnz looper
;那末我們若何使它並行化而且削減依附呢?
looper:
mov eax,[esi]
mov ebx,[esi+4]
xor eax,0E5
xor ebx,0E5
add [edi],eax
add [edi+4],ebx
add esi,8
add edi,8
sub ecx,2
jnz looper

<47>防止內存拜訪

從新構建代碼來防止內存拜訪(或許其他I/O操作)。一種辦法就是在向內存寫一個值的時刻,先在一個存放器中累加它。上面給出一個例子。在這個例子裡,假定每次輪回我們從源數組向目標數組(元素為dword年夜小)持續相加3個字節的值。目標數組曾經置0。
mov ecx,AMT_TO_LOOP
looper:
movzx byte ptr eax,[esi]
add [edi],eax
movzx byte ptr eax,[esi+1]
add [edi],eax
movzx byte ptr eax,[esi+3]
add [edi],eax
add edi,4
add esi,3
dec ecx
jnz looper
;我們可以在存放器中累加成果,然後只需向內存寫一下便可。
mov ecx,AMT_TO_LOOP
looper:
xor edx,edx ;置0以存儲成果
movzx byte ptr eax,[esi]
add edx,eax
movzx byte ptr eax,[esi+1]
add edx,eax
movzx byte ptr eax,[esi+3]
add edx,eax
add esi,3
mov [edi],edx
add edi,4
dec ecx
jnz looper

<48>什麼時候轉換call為jump

假如子法式的最初一個語句是一個call,斟酌把它轉換為一個jump來削減一個call/ret。

<49>應用數組作為數據構造

(這個tip不是只針對匯編的,但匯編表示更優良)你可使用一個數組來完成數據的構造(例如樹和鏈表)。經由過程應用數組,內存會無縫銜接,代碼會因更少的緩存未射中而提速。

;=========================高等=========================

<50>防止前綴

盡可能防止應用前綴。段超出(segment overrides),分支標記(branch hints),操作數年夜小強迫轉換(operand-size override),地址年夜小強迫轉換(address-size override),封閉數據指令(LOCKs),反復前綴(REPs)等都邑發生前綴。前綴會增長指令的長度,履行時光也有所延伸。

<51>將代碼中的讀/寫操作分組

假如bus總線上有很多交互的讀敕令和寫敕令,斟酌分組。統一時光處置更多的讀和寫敕令。上面的是我們要防止的:
mov eax,[esi]
mov [edi],eax
mov eax,[esi+4]
mov [edi+4],eax
mov eax,[esi+8]
mov [edi+8],eax
mov eax,[esi+12]
mov [edi+12],eax
;將讀和寫指令分組
mov eax,[esi]
mov ebx,[esi+4]
mov ecx,[esi+8]
mov edx,[esi+12]
mov [edi],eax
mov [edi+4],ebx
mov [edi+8],ecx
mov [edi+12],edx

<52>充足應用CPU履行單位(EU,execution units)來加快代碼

選擇在分歧處置單位履行的敕令。假如你能公道地如許做的話,履行代碼的時光會等於吞吐時光(throughput time)而沒有延遲時光(latency time)。對很多指令來講,吞吐時光是較少的。

<53>交織2個輪回以同步履行

你可以睜開一個輪回2次,而不是一條接另外一條的運轉敕令,你可以同步運轉它們。這為何很有效?有2個緣由。第一,有時你要履行必需應用某個存放器的指令而且這些指令有很長的延遲。例如MUL/DIV,兩個連在一路的MUL指令會發生對EDX:EAX的依附和爭用。第二,有時一些指令自己就有很長的延遲。所以,你天然想測驗考試在在一個輪回前面放置一些來自另外一個輪回的指令來削減延遲直到它前往成果。P4機上的很多MMX, SSE和SSE2指令都采用這個戰略。這兒有一個例子輪回。
loop:
A1 ; instruction 1 loop 1
D2 ; instruction 4 loop 2
B1 ; instruction 2 loop 1
A2 ; instruction 1 loop 2
C1 ; instruction 3 loop 1
B2 ; instruction 2 loop 2
D1 ; instruction 4 loop 1
C2 ; instruction 3
loop 2

<54>應用MMX/SSE/SSE2時比擬指令設置的標記

當與MMX/SSE/SSE2打交道時,比擬指令會發生對標記位的設置。在某些情形下,當你搜刮一個文件中的形式(例如換行符)時,這會很有效。所以你可使用它來搜刮形式,而不只僅來做數學運算。你可使用MMX/SSE/SSE2中比擬指令發生的標記來掌握部門MMX或SSE存放器上的數學運算。例以下面的代碼片斷:假如有5,把9加到它MMX存放器的dword部門。
; if (fredvariable == 5)
; fredvariable += 9;
;-------------------------------
movq mm5,[two_fives] ;mm5有兩個DWORD 5在外面
movq mm6,[two_nines] ;mm6有兩個DWORD 9在外面
movq mm0,[array_in_memory] ;取值
movq mm1,mm0 ;回寫
pcmpeqd mm1,mm5 ;mm1如今在每一個DWORD地位都為FFFFFFFF
;在MM1有一個5,其他一切地位都為0
pand mm1,mm6 ;把MM6中不為5的地位置0
paddd mm0,mm1 ;只向MM0中值為5的地位加9

<55>PSHUFD和PUSHFW指令

在P4的MMX,SSE和SSE2中,挪動指令(MOV系列)速度慢。你可以在SSE和SSE2中應用"pushfd",MMX中應用"pushfw"來防止如下情況。它快2指令周期呢。但有一個正告:它是與微指令被分派加載到哪一個流水線有關的。而沒有控制更多技巧的時刻,有時應用慢點的"MOVDQA"會比替換它的"PUSHFD"快。所以你要對你的代碼一絲不苟。
pushfd xmm0,[edi],0E4h ;拷貝EDI指向地位的16字節到XMM0。0E4h會直接拷貝。
pushfw mm0,[edi],0E4h ;拷貝EDI指向地位的8字節到MM0。0E4h會直接拷貝。

<56>直接寫內存---繞開緩存(cache)

這是另外一個優化內存處置的戰略。假如你必需向很多內存空間(256KB及以上)停止寫操作,繞開緩存直接向內存寫更快!假如你的CPU是P3,你可使用"movntq"或"movntps"指令。前者履行8字節的寫操作,爾後者是16字節。16-byte寫須要16字節對齊。在P4上,你還可使用"movntdq",它也能夠用於16字節,但必需16字節對齊。這個辦法在內存填充和內存拷貝中均實用,兩者都做寫操作。這裡有一些樣本代碼。我必需本身著手並行應用8個XMM存放器來贊助清除P4機MOVDQA指令的一些延遲。但是,為了贊助懂得,我沒那末做。
mov ecx,16384 ;寫16384個16-bit值,16384*16 = 256KB
;所以我們正在拷貝一個256KB的數組
mov esi,offset src_arr ;指向必需以16-bit對齊的源數組的指針,不然會發生異常
mov edi,offset dst_arr ;指向必需以16-bit對齊的目標數組的指針,不然會發生異常
looper:
movdqa xmm0,[esi] ;任務在P3及以上
movntps [edi],xmm0 ;任務在P3及以上
add esi,16
add edi,16
dec ecx
jnz looper

<57>應用MMX/SSE/SSE2時每一個輪回處置2個事宜

在P4上,MMS/SSE/SSE2指令的延遲那末長以致於我老是每一個輪回處置2個事宜或許提早讀取一個輪回。假如你有足夠的存放器,可以多於2個事宜。一切的各類各樣的MOVE(包含MOVD)指令在P4上的速度都慢。所以2個32-bit的數字數組相加運算在P4上比P3上還慢。一個快點兒的辦法能夠就是每一個輪回(這個輪回在FRED標號之前預讀輪回初始值MM0和MM1)處置兩個事宜。你必需做的只是在數組元素個數為奇時停止特別的處置;在最初檢討一下,假如為奇數,加一個額定的dword。這兒有個並沒有提早讀取值的代碼段。我想,把它改成提早讀取值是很輕易的,所以我沒有兩個都貼出。上面的代碼可以:在P4機上防止ADC這個速度慢的指令來把兩個數組相加。
pxor mm7,mm7 ; the previous loops carry stays in here
fred:
movd mm0,[esi] ; esi points to src1
movd mm1,[edi] ; edi points to src2, also to be used as the destination
paddq mm0,mm1 ; add both values together
paddq mm0,mm7 ; add in remainder from last add
movd [edi],mm0 ; save value to memory
movq mm7,mm0
psrlq mm7,32 ; shift the carry over to bit 0
add esi,8
add edi,8
sub ecx,1
jnz fred
movd [edi],mm7 ; save carry

<58>預讀MMX或XMM存放器來躲避長時光的延遲

在須要之前預讀一個SSE2存放器會晉升速度。這是由於MOVDQA指令在P4上消費6 cycles。這確切慢。鑒於它有如斯長之延遲,我想在肯定不會發生障礙的處所提早讀取它。這裡有一個例子。

movdqa xmm1,[edi+16] ;在我們須要之前讀取入XMM1,P4上消費6 cycles,不包含從緩存取的時光。
por xmm5,xmm0 ;做OR運算,XMM0已預讀。P4上消費2 cycles。
pand xmm6,xmm0 ;做AND運算,XMM0已預讀。P4上消費2 cycles。
movdqa xmm2,[edi+32] ;在我們須要之前預讀入XMM2,P4上消費6 cycles,不包含從緩存取的時光。
por xmm5,xmm1 ;做OR運算,XMM1已預讀。P4上消費2 cycles。
pand xmm6,xmm1 ;做AND運算,XMM1已預讀。P4上消費2 cycles。

<59>在一個或多個存放器中累加一個成果來防止履行慢的指令

在一個或多個存放器中累加一個成果來防止履行慢的指令。我用這個戰略加快用SSE2寫的比擬/讀輪回。比擬慢的指令是PMOVMSKB。所以,我累加成果在一個存放器中而不是每次輪回都履行這個指令。對每一個4KB的內存讀操作,我會用PMOVMSKB,它會很年夜地提速。上面我們經由過程剖析一個應用PREFETCH和TLB啟動的例子來證實。上面的代碼有2個輪回。內層輪回被睜開來處置128字節(P4機上PREFETCH指令的預取字節數)。另外一個輪回被睜開為4KB。所以我可使用TLB啟動。假如你應用的體系沒有應用4KB頁年夜小,你不能不恰當地修正你的代碼。在具有最年夜6.4 GB/s內存帶寬的戴爾辦事器(Dell Server)體系上,我測試了這段代碼。我可以或許以5.55 GB/s做讀和比擬操作(在沒有Windows情況下。在Windows情況下會運轉地慢點)。我漏掉標號"compare_failed"的代碼有2個緣由:1)剪切/粘貼的代碼曾經夠多了;2)它沒有論證任何我要展示的技巧。"compare_failed"的代碼只是簡略地(在PCMPEQD找到掉敗地址所屬的比來的4KB內存塊後)做一個REP SCASD來找到掉敗的地址。這個例子有異常偉大的代碼量,所以我把它放在最初以避免你讀它的時刻睡著;)(譯者注:感到上面的代碼正文翻譯出來有點別扭,並且原文也不難懂得。故略。)

read_compare_pattern_sse2 proc near

mov edi,[start_addr] ;Starting Address
mov ecx,[stop_addr] ;Last addr to NOT test.
mov ebx,0FFFFFFFFh ;AND mask
movd xmm6,ebx ;AND mask
pshufd xmm6,xmm6,00000000b ;AND mask
movdqa xmm0,[edi] ;Get first 16 bytes
mov eax,[pattern] ;EAX holds pattern
pxor xmm5,xmm5 ;OR mask
movd xmm7,eax ;Copy EAX to XMM7
pshufd xmm7,xmm7,00000000b ;Blast to all DWORDS
outer_loop:
mov ebx,32 ;128 32 byte blocks
mov esi,edi ;save start of block

if DO_TLB_PRIMING
mov eax,[edi+4096] ;TLB priming
endif ;if DO_TLB_PRIMING

fred_loop:
movdqa xmm1,[edi+16] ;read 16 bytes
por xmm5,xmm0 ;OR into mask
pand xmm6,xmm0 ;AND into mask

movdqa xmm2,[edi+32] ;read 16 bytes
por xmm5,xmm1 ;OR into mask
pand xmm6,xmm1 ;AND into mask

movdqa xmm3,[edi+48] ;read 16 bytes
por xmm5,xmm2 ;OR into mask
pand xmm6,xmm2 ;AND into mask

movdqa xmm0,[edi+64] ;read 16 bytes
por xmm5,xmm3 ;OR into mask
pand xmm6,xmm3 ;AND into mask

movdqa xmm1,[edi+80] ;read 16 bytes
por xmm5,xmm0 ;OR into mask
pand xmm6,xmm0 ;AND into mask

movdqa xmm2,[edi+96] ;read 16 bytes
por xmm5,xmm1 ;OR into mask
pand xmm6,xmm1 ;AND into mask

movdqa xmm3,[edi+112] ;read 16 bytes
por xmm5,xmm2 ;OR into mask
pand xmm6,xmm2 ;AND into mask

por xmm5,xmm3 ;OR into mask
prefetchnta [edi+928] ;Prefetch 928 ahead
pand xmm6,xmm3 ;AND into mask

add edi,128 ;Go next 128byteblock
cmp edi,ecx ;At end?
jae do_compare ;No, jump

movdqa xmm0,[edi] ;read 16 bytes

sub ebx,1 ;Incr for inner loop
jnz fred_loop

do_compare:
pcmpeqd xmm5,xmm7 ;Equal?
pmovmskb eax,xmm5 ;Grab high bits in EAX
cmp eax,0FFFFh ;all set?
jne compare_failed ;No, exit failure

mov edx,0FFFFFFFFh ;AND mask
pxor xmm5,xmm5
pcmpeqd xmm6,xmm7 ;Equal?

pmovmskb eax,xmm6 ;Grab high bits in EAX
cmp eax,0FFFFh ;All Set?
jne compare_failed ;No, exit failure

movd xmm6,edx ;AND mask
pshufd xmm6,xmm6,00000000b ;AND mask

cmp edi,ecx ;We at end of range
jb outer_loop ;No, loop back up

jmp compare_passed ;Done!!! Success!!!

<60>在輪回內預取間隔和地位

你會留意到,在下面的例子中,我之前預取了928字節而不是128字節(128是P4機上的預取字節數)。為何?Intel建議在輪回開端前預取128字節(2 cache lines)。但兩種分歧取法(在輪回開端處或提早預取128字節)都邑失足。我既沒有在輪回開端時預取也沒之前預取128字節。為何?當我研討這段代碼時,我發明把PREFETCH指令放到輪回四周而且轉變它預取的偏移量可使它運轉得更快。所以失常得是,我寫代碼來測驗考試一切的輪回內預取指令的地位和開端預取的偏移量的組合情形。這段代碼寫成一個匯編文件,並且把四周的PREFETCH指令移到輪回內,同時修正開端預取的偏移量。然後一個bat文件編譯這個修正的代碼而且運轉一個基准點(benchmark)。我運轉了這個基准點幾個小時來測驗考試分歧的組合情形(我在預取間隔為32時開端,慢慢增長間隔直到間隔到達1024)。在此體系上,我寫的基於928字節而不是128字節的代碼履行地更快。而且,簡直在輪回停止處預取是最快的(在do_compare標號之前,PREFETCHNTA指令年夜約8條line)。
THE END.
txt打包下載 huibianyouhuats_jb51
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved