麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 編程 > ASM > 正文

匯編優化提示

2020-02-02 19:01:20
字體:
來源:轉載
供稿:網友
你需記住的最重要的事情就是代碼花費的時間!不同的方法可以加速你的代碼或者不能,所以你要多多嘗試。因而計算代碼花費的時間來看看你嘗試的每個方法是否可以提速是件很重要的事情。

;=========================初級=========================

<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
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

主站蜘蛛池模板: 一区小视频 | 色柚视频网站ww色 | 香蕉久久久 | 麻豆视频在线观看 | caoporn国产一区二区 | 免费中文视频 | 色婷婷久久久亚洲一区二区三区 | 欧美a黄 | 爱福利视频网 | 午夜久久久精品一区二区三区 | 最新一区二区三区 | 欧美一级三级在线观看 | 黄网站免费观看视频 | 国产资源在线免费观看 | 国产成人自拍小视频 | 亚洲网站免费观看 | 久久探花 | 在线免费小视频 | 欧美精品一区二区久久 | 99国内精品 | 亚洲一区二区三区四区精品 | 午夜视频你懂的 | av电影在线观看网站 | 国产91精品久久久久久久 | 成人福利视频在线 | 久久精品亚洲欧美日韩精品中文字幕 | 久久草在线视频国产 | 久久精品视频一区 | 极品大长腿啪啪高潮露脸 | 欧美精品 | 黄色视频a级毛片 | 欧美色视| 欧美乱码精品一区 | 成人免费看视频 | 日韩美女电影 | 手机在线看片国产 | 麻豆视频在线观看免费网站 | 亚洲精品一二三区 | 亚洲99| 国产一区二区视频精品 | 成人h精品动漫一区二区三区 |