Intel, AMD及VIA CPU的微架構(25)
8.11. 暫存器的部分訪問
Core2與Nehalem使用3種不同的方式來解決暫存器部分寫。這3種不同的方式分別用於通用暫存器,標記暫存器及XMM暫存器。
通用暫存器的部分訪問
一個通用暫存器的不同部分可以儲存在不同的臨時暫存器中,以消除假的依賴性。例如:
; Example 8.6. Partial registers
mov al, [esi]
inc ah
這裡,第二條指令無需等待第一條指令結束,因為AL與AH可以使用不同的臨時暫存器。當μop被回收時,AL與AH寫回永久EAX暫存器各自的部分。在寫入暫存器的一部分,緊跟著從整個暫存器讀時,出現一個問題:
; Example 8.7. Partial register problem
mov al, 1
mov ebx, eax
通過插入額外的μop來合併暫存器的不同部分,解決這個問題。我假定這個額外的μop在ROB-讀階段產生。在上面的例子中,ROB-讀將產生一個額外的μop,在MOV EBX, EAX指令前,將AL與EAX的餘下部分合併為一個臨時暫存器。在ROB-讀階段,這需要2-3個額外時鐘週期,但少於沒有這個機制的處理器上5-6時鐘週期的部分暫存器暫停懲罰。
寫回高8位暫存器AH,BH,CH,DH產生兩個額外μop,而寫回暫存器低8位或16位部分產生一個額外μop。例子參考第87頁。
標記暫存器的部分訪問暫停
不幸的,在Core2與Nehalem上不能產生額外的μop來防止標記暫存器上的暫停。因此,在一條修改了部分標記暫存器的指令後,讀標記暫存器會有一個大約7個時鐘週期的暫停。例子參考第89頁。
XMM暫存器的部分訪問
在重排緩衝中,XMM暫存器不會被分解。因此,在寫入部分XMM暫存器時沒有部分訪問暫停,無需額外的μop。但這個寫對暫存器之前的值有一個假的依賴。例如:
; Example 8.8. Partial access to XMM register
mulss xmm0, xmm1
movss [mem1], xmm0
movss xmm0, xmm2 ; has false dependence on previous value
addss xmm0, xmm3
帶有暫存器運算元的MOVSS與MOVSD指令只寫目標暫存器的一部分。在例子8.8中,MOVSS XMM0, XMM2指令對前面的MULSS指令有一個假的依賴,因為暫存器的低32位不能與暫存器未使用的高半部分開。這妨礙了亂序執行。在例子8.8中的假依賴可以通過MOVAPS XMM0, XMM2替換MOVSS XMM0, XMM2來消除。不要使用帶有兩個暫存器運算元的MOVSS與MOVSD指令,除非保留暫存器餘下部分不變是必要的。
8.12. 寫轉發暫停
在特定條件下,處理器可以把一個記憶體寫轉發給後續一個對相同地址的讀。在大多數非對齊或部分記憶體引用的情形中,與之前的處理器一樣,這個寫轉發會失敗(參考第75頁),但特定的特殊情形允許部分記憶體運算元的寫轉發。一個失敗的寫轉發將推遲後續讀大約10個時鐘週期。
如果一個記憶體寫後跟一個對相同地址的讀,在讀運算元有相同大小且自然對齊時,寫轉發可以工作:
; Example 8.9. Successful store-to-load forwarding
mov dword ptr [esi], eax ; esi aligned by 4
mov ebx, dword ptr [esi] ; No stall
如果運算元小於16位元組,且在45nm Core2上沒有跨越64位元組邊界,在65nm Core2上沒有跨越8位元組邊界,寫轉發可以作用在非對齊記憶體運算元上。在Nehalem上,寫轉發作用在所有情形的非對齊記憶體運算元上。
; Example 8.10. Failed store forward because of misalignment on Core2
mov dword ptr [esi-2], eax ; esi divisible by 64
mov ebx, dword ptr [esi-2] ; Stall because 64 B boundary crossed
如果讀比前面寫有更大的運算元,寫轉發將不能工作:
; Example 8.11. Failed store forwarding when read bigger than write
mov dword ptr [esi], eax ; Write lower 4 bytes
mov dword ptr [esi+4], edx ; Write upper 4 bytes
movq mm0, qword ptr [esi] ; Read 8 bytes. Stall
如果讀比寫有更小的運算元,在相同的地址開始,且寫運算元在45nm Core2上沒有跨越64位元組邊界,在65nm Core2上沒有跨越8位元組邊界,寫轉發是可能的。Nehalem沒有邊界跨越的限制。
; Example 8.12. Store forwarding to smaller read
mov dword ptr [esi], eax ; Write 4 bytes
mov bx, word ptr [esi] ; Successful store forwarding
mov cx, word ptr [esi+2] ; Stall because not same start address
有幾個特殊的情形,向在不同地址開始的更小讀進行寫轉發是可能的。這些特殊情形是:
- 一個8位元組寫可以後跟都是它一半的4位元組讀,如果讀在65nm Core2上沒有跨越8位元組邊界,在45nm Core2上沒有跨越64位元組邊界。Nehalem沒有邊界跨越的限制。
- 一個16位元組寫可以後跟都是它一半的8位元組讀,以及/或者都是它四分之一的4位元組讀,如果寫對齊在16位元組邊界。Nehalem沒有邊界跨越及對齊的限制。
; Example 8.13. Store forwarding in special case
movapd xmmword ptr [esi], xmm0 ; Write 16 bytes
fld qword ptr [esi] ; Read lower half. Success
fld qword ptr [esi+8] ; Read upper half. Success
mov eax, dword ptr [esi+12] ; Read last quarter. Success
mov ebx, dword ptr [esi+2] ; Not a quarter operand. Fail
檢測寫轉發是否可能的機制不區分在快取中有相同組值的不同的記憶體。在地址間隔4kb倍數時,會導致暫停或失敗的假的寫轉發:
; Example 8.14. Bogus store forwarding stall
mov word ptr [esi], ax
mov ebx, dword ptr [esi+800h] ; No stall
mov ecx, dword ptr [esi+1000h] ; Bogus stall
在例子8.4中,在寫ax後讀ecx時有一個暫停,因為記憶體地址有相同的組值(距離是0x1000的倍數),並且如果地址相同,一個小的寫之後的大的讀將給出一個暫停。
8.13. 快取與記憶體訪問
快取 |
Core 2 |
Nehalem |
1級程式碼 |
32 kB,8路,每行64位元組,時延3,每核 |
32 kB,8路,每行64位元組,時延4,每核 |
1級資料 |
32 kB,8路,每行64位元組,時延3,每核 |
32 kB,8路,每行64位元組,時延4,每核 |
2級 |
2、4或6 MB,16或24路,每行64位元組,時延15,共享 |
256 kB,8路,每行64位元組,時延11,每核 |
3級 |
無 |
8 MB,16路,每行64位元組,時延38,共享 |
表.8.3. Core2與Nehalem的快取大小
除了最後一級快取,每個核有一個快取。在可以執行兩個執行緒的核上,所有的快取都線上程間共享。很可能將來會有更多版本有不同的最後一級快取大小。在1級與2級快取間有一個256位元資料通道。
重排記憶體訪問的能力據說被提升了,因此可以在前面預期地址不同的寫之前,在地址確切清楚前,推測執行記憶體讀。
對1級與2級快取,資料預取器能夠以不同的步長自動預取兩個資料流。
快取庫的衝突
資料快取中的每個64位元組行被分為16位元組一個的4個庫。如果兩個記憶體地址有相同的庫號,即Core2上兩個地址第4與5位元位相同,在同一個時鐘週期進行記憶體讀與記憶體寫是不可能的。例如:
; Example 8.15. Core2 cache bank conflict
mov eax, [esi] ; Use bank 0, assuming esi is divisible by 40H
mov [esi+100H], ebx ; Use bank 0. Cache bank conflict
mov [esi+110H], ebx ; Use bank 1. No cache bank conflict
Nehalem沒有這些衝突,但在具有相同組與偏移的記憶體地址之間,即距離是4kB的倍數,Core2與Nehalem都有假的依賴。
非對齊記憶體訪問
在跨越快取行邊界(64位元組)時,Core2對非對齊記憶體訪問有懲罰。這個懲罰對非對齊讀大約是12個時鐘週期,對非對齊寫大約是10個時鐘週期。Nehalem對非對齊記憶體訪問幾乎沒有懲罰。
8.14. 打破依賴鏈
將一個暫存器置零的常見方法是XOR EAX, EAX或SUB EBX, EBX。Core2與Nehalem處理器知道特定的指令與暫存器之前的值無關,如果源與目標暫存器是同一個。
這適用於所有以下的指令:XOR,SUB,PXOR,XORPS,XORPD,及PSUBxxx與PCMPxxx的各種變體,除了PCMPEQQ。
以下指令在源與目標相同時,不視為無關:SBB,CMP,PANDN,ANDNPS,ANDNPD。
浮點減法與比較指令,在源與目標相同時,不是真正無關的,因為NAN等的可能性。
這些指令對打破不必要的依賴是有用的,但僅在知道這個無關性的處理器上。
8.15. Nehalem裡的多執行緒
執行緒同步原語,即LOCK XCHG指令,比之前的處理器要快得多。
Nehalem可以在四個核中的每個執行兩個執行緒。這意味著每個執行緒僅得到一半的資源。資源在同一個核上執行的兩個執行緒間以下面的方式共享:
快取:所有的快取資源線上程間完全共享。一個執行緒用得越多,另一個執行緒用得越少。
分支目標緩衝與分支歷史模式表:線上程間完全共享。
指令獲取與解碼:指令獲取器與解碼器在兩個執行緒間平均共享,每個執行緒隔一個時鐘週期獲得這些資源。
迴圈緩衝:每個執行緒一個。
暫存器重新命名與暫存器讀埠:這些被平均共享,每個執行緒隔一個時鐘週期獲得資源。一個執行緒裡的暫存器讀暫停不影響另一個執行緒中的暫存器讀。
重排緩衝與保留站:完全共享。
執行埠與執行單元:這些完全共享。一個執行緒可以使用一個執行埠,與此同時另一個執行緒使用另一個埠。
讀與寫緩衝:這些完全共享。
永久暫存器檔案:每個執行緒一個。
顯然,如果存在任何是效能限制因素的共享資源,每個核執行兩個執行緒沒有好處。在許多情形中,執行資源超出一個執行緒所需。在大量時間花在快取不命中及分支誤預測的情形裡,每個核執行兩個執行緒,優勢特別明顯。不過,如果任何共享資源是瓶頸,每個核執行兩個執行緒就沒有優勢。相反,每個執行緒可能執行得單個執行緒一半的速度還要慢,因為快取與分支目標緩衝的逐出以及其他資源衝突。在CPU裡沒有辦法給一個執行緒高於另一個的優先順序。
8.16. Core2與Nehalem中的瓶頸
指令獲取與預解碼
在Core2與Nehalem的設計中,流水線的所有部分都得到改進,因此整體吞吐率顯著提升。改進最少的部分是指令獲取與預解碼。這部分不是總能跟得上執行單元的速度。因此,指令獲取與預解碼很可能是CPU密集程式碼中的瓶頸。
避免長的指令,以優化指令獲取與預解碼是重要的。最優的平均指令長度是大約3個位元組,這不可能得到。
在適合迴圈緩衝的迴圈中,指令獲取與預解碼不是瓶頸。因此,如果最裡層迴圈足夠小,能放入迴圈緩衝,或者可以被分解為多個更小的、可以放入迴圈緩衝的迴圈,程式的效能可以得到提升。
指令解碼
每時鐘週期,解碼器可以處理4條指令,或者在巨集操作融合的情形下5條。4個解碼器中僅第一個可以處理產生多個μop的指令。因此,解碼器的最小輸出是每時鐘週期2個μop,在所有指令都產生2個μop,因此僅能使用第一個解碼器時。應該按4-1-1-1模式排列指令來優化解碼器吞吐率。
幸好,因為改進的μop融合、棧引擎以及128位寬的匯流排與執行單元,大多數在之前的設計中產生多個μop的指令,在Core2與Nehalem上僅產生一個μop。在一個所有指令僅產生一個μop的指令流中,解碼器每時鐘週期將產生4個μop。這與流水線餘下部分的吞吐率相配。因此,僅在某些指令產生兩個μop指令時,解碼器吞吐率才是關鍵的。
長度改變字首在解碼器中導致長的時延。應該不計代價避免這些字首,除了在能放入迴圈緩衝的小迴圈裡。在32位及64位模式中,避免帶有16位立即數的指令。
暫存器讀暫停
在許多情形下,永久暫存器檔案的暫存器讀埠數是不足夠的。因此,暫存器讀暫停很可能是一個瓶頸。
在程式碼中避免2或3個以上經常讀、但很少寫的暫存器,棧指標,棧框指標,this指標,及儲存在暫存器中的迴圈不變表示式,是暫存器讀暫停可能的貢獻者。迴圈計數器以及其他在迴圈中修改的暫存器也可能助長暫存器讀暫停,如果迴圈每迭代需要超過5個時鐘週期。
執行埠與執行單元
執行埠與執行單元的容量相當高。許多μop可以在2到3個執行埠間選擇,每個單元每時鐘週期可以處理一整個128位向量操作。因此,相比之前的設計,執行埠的吞吐率成為瓶頸的可能性減小了。
如果程式碼產生許多去往同一個執行埠的μop,執行埠會是一個瓶頸。在包含許多記憶體訪問的程式碼中,記憶體操作會是瓶頸,因為僅有一個記憶體讀埠與一個記憶體寫埠。
大多數執行單元有流水線化為每時鐘週期一個μop的吞吐率。最重要的例外是除法與平方根。
執行時延與依賴鏈
在Core2與Nehalem上執行時延通常是低的。大多數整數ALU操作僅有一個時鐘週期的時延,即使128位向量操作。整數單元與浮點單元間的資料移動有1-2時鐘週期的額外時延。僅在長依賴鏈中,執行時延是重要的。長依賴鏈應該被避免。
部分暫存器訪問
在寫入暫存器的部分後,讀整個暫存器是有懲罰的。使用MOVZX或MOVSX把8位或16位記憶體運算元讀入32位暫存器,而不是使用暫存器更小的部分。
回收
在我所有的實驗裡,都沒觀察到μop的回收成為一個瓶頸。
分支預測
分支預測演算法是良好的,特別對迴圈。間接跳轉可以被預測。不過,Core2上的分支歷史模式表太小,使得分支誤預測相當常見。
用於具有迴圈行為分支的特殊分支目標緩衝僅有128項,對有許多關鍵迴圈的程式,它可能是一個瓶頸。
記憶體訪問
快取頻寬、記憶體頻寬及資料預取要顯著好於之前的處理器。
當然,對記憶體密集應用,記憶體頻寬仍然可能是一個瓶頸。
文獻
"Intel 64 and IA-32 Architectures Optimization Reference Manual". Intel 2009.
Jeff Casazza: "First the Tick, Now the Tock: Intel® Microarchitecture (Nehalem)". White Paper. Intel 2009.
Ofri Wechsler: "Inside Intel Core Microarchitecture: Setting New Standards for Energy-Efficient Performance". White Paper. Intel 2006.
David Kanter: "Inside Nehalem: Intel's Future Processor and System". www.realworldtech.com, 2008.