1. 程式人生 > >Intel, AMD及VIA CPU的微架構(31)

Intel, AMD及VIA CPU的微架構(31)

12. Intel Atom流水線

Intel Atom處理器有更為簡單的設計,其中節能是最重要的目標。它幾乎沒有亂序執行能力。它有1到2個核,每個核可以執行兩個執行緒,總計同時最多4個執行緒。快取、解碼器與執行單元都在兩個執行緒間共享。它支援補充SSE3指令集。某些版本支援x64。

流水線有16級:3級用於指令獲取,3級用於指令解碼,2級用於指令分發,1級用於讀暫存器運算元,1級用於執行,2級用於異常處理與多執行緒,1級用於提交結果。讀-修改或讀-修改-寫型別指令作為一個μop處理。更復雜的指令分解為多個μop。

流水線每時鐘週期可以處理兩條指令。有兩個執行埠,每個連線到一個整數單元與一個浮點/SIMD單元。

除了我自己的測試結果,這個描述基於第二手來源的有限資訊(Scott Wasson: Intel's Atom processor unveiled. http://techreport.com/articles.x/14458/1, April 2008. Anand Lal Shimpi: Intel's Atom Architecture: The Journey Begins. http://www.anandtech.com/showdoc.aspx?i=3276&p=1, April 2008)。

12.1. 指令獲取

在執行單執行緒時,指令獲取速率平均大約是每時鐘週期8位元組。在少數情形下,比如所有指令都是8位元組且8位元組對齊,獲取速率可以高達每時鐘週期10.5位元組,但在大多數情形裡,在執行單執行緒時,獲取速率稍低於每時鐘週期8位元組。當在一個核上執行兩個執行緒時,獲取速率更低。

在平均指令長度超過4位元組時,指令獲取速率可能是瓶頸。在執行因為其他某些原因暫停時,指令獲取器可以趕上並填充指令佇列。

12.2. ​​​​​​​指令解碼

兩個指令解碼器是相同的。帶有最多3個字首的指令可在一個時鐘週期裡解碼。字首超過3個的指令(這幾乎不會出現,除非使用無用的字首進行位元組填充)有嚴重的時延。大多數指令僅產生一個μop。對長度改變字首沒有懲罰。

解碼後的指令進入一個每執行緒16項的佇列。如果一個執行緒被禁止,兩個16項佇列可以合併為一個32項佇列。

12.3. ​​​​​​​執行單元

有兩個執行單元群(cluster):一個處理所有在通用暫存器上指令的整數群,及一個處理所有在浮點暫存器與SIMD向量暫存器上指令的浮點與SIMD群。一個記憶體訪問群連線到整數單元群。在群之間移動資料是慢的。

來自解碼器的μop可以被髮布到兩個執行埠,我稱之為埠0與埠1。每個執行埠訪問部分的整數處理群與浮點/SIMD處理群。我將稱整數處理群的兩部分為ALU0與ALU1,浮點/SIMD處理群的兩部分為FP0與FP1。這樣兩個執行埠可以並行處理兩個μop流,分工如下:

可由埠0及埠1處理的指令:

  • 暫存器到暫存器移動
  • 通用暫存器或SIMD暫存器中的整數加法
  • 通用暫存器或SIMD暫存器中的布林操作

僅由埠0處理的指令:

  • 記憶體讀寫
  • 通用暫存器或SIMD暫存器中的整數偏移、混排、封裝
  • 乘法
  • 除法
  • 各種複雜指令

僅由埠1處理的指令:

  • 浮點加法
  • 跳轉與分支
  • LEA指令

4個單元ALU0、ALU1、FP0與FP1可能都有一個整數ALU,雖然不能排除僅有兩個整數ALU,分別在ALU0與FP0、ALU1與FP1間共享。在FP0裡有一個乘法單元,一個除法單元。整數乘法與整數除法去往埠0。

SIMD整數加法器與偏移單元有128位的寬度及1時鐘週期的時延。對單精度向量,浮點加法器有完整的128位能力,但對雙精度僅有64位的能力。乘法器與除法器都是64位。

浮點加法器有5時鐘週期時延,且完全流水線化,吞吐率是每時鐘週期一個單精度向量加法。乘法器是部分流水線化的,時延為4時鐘週期,吞吐率是每時鐘週期一個單精度乘法。雙精度與整數乘法有更長的時延及更低的吞吐率。從一個乘法開始的時刻到下一個乘法可以開始的時刻,從最理想情形的1時鐘週期,到不那麼理想情形的2個以上時鐘週期。雙精度向量乘法與某些整數乘法在時間上不能重疊。除法是慢的且沒有流水線化。一個單精度標量浮點除法需要30時鐘週期。雙精度需要60時鐘週期。一個64位整數除法需要207時鐘週期。

手冊4“指令表”中的指令列表顯示了哪些指令使用哪些單元。

12.4. ​​​​​​​指令成對

僅在指令被排序,使得同時可以執行兩條時,得到每時鐘週期兩條指令的最大吞吐率。在遵守以下規則時,可以同時執行兩條指令:

  • 核僅執行一個執行緒。另一個執行緒,如果有,必須是空閒或被快取不命中等暫停。
  • 兩條指令必須是連續的,中間沒有其他指令。
  • 兩條指令不需要是連續的。一個預測被採用的分支指令可與分支目標處第一條指令成對。
  • 第二條指令不讀第一條指令寫的暫存器。這個規則有一個例外:讀標記暫存器的分支指令可與之前修改標記暫存器的一條指令成對。
  • 兩條指令不寫相同的暫存器,除了標記暫存器。例如:INC EAX / MOV EAX, 0不能成對,因為都修改EAX。INC EAX / INC EBX可以成對,即使都修改了標記暫存器。
  • 兩條指令不使用相同的執行埠。第一條指令去往埠0,第二條指令去往埠1;或者第一條指令去往埠1,第二條指令去往埠0。
  • 使用兩個埠或流水線資源的指令不能與其他任何指令成對。例如,帶一個記憶體運算元的浮點加指令使用埠1下的FP1進行浮點加法,對記憶體運算元使用埠0下的記憶體單元。

從這些規則知道同時進行記憶體讀與記憶體寫是不可能,因為它們都使用埠0下的記憶體單元。但同時進行浮點加法(沒有記憶體運算元)及浮點乘法是可能的,因為它們分別使用FP1與FP0。

12.5. ​​​​​​​X87浮點指令

使用x87形式浮點暫存器的指令,由Intel Atom處理器以一個非常不幸的方式處理。一旦有兩條連續的x87指令,這兩條指令不能成對,相反因為解碼器中的問題,導致額外的1時鐘週期。這給出了每2個時鐘週期僅一條指令的吞吐率,而使用XMM暫存器的類似程式碼有每時鐘週期兩條指令的最大吞吐率。

這適用於所有x87指令(名字以F開頭),即使FNOP。例如,在我的測試中,一個100條連續FNOP的指令序列需要200個時鐘週期。如果這100個FNOP被100個NOP分散,那麼該序列僅需要100個時鐘週期。因此,避免連續的x87指令很重要。如果在兩條x87指令間沒有東西可放,那麼放入一個NOP。每兩條指令一個NOP顯然需要一半的頻寬,但這仍然好於沒有NOP時的四分之一頻寬。

FXCH指令有1時鐘週期的時延,而許多其他處理器對FXCH給出零時延。這是在Atom上執行x87形式浮點程式碼的深層缺點,因為浮點暫存器棧結構使這對使用許多FXCH指令是必要的。

因此,用使用XMM暫存器的SSE2形式的程式碼替換x87形式的浮點程式碼是有好處的。SSE2指令通常超過4位元組,而x87指令要短些。以每時鐘週期8位元組的最大指令獲取速度,我們可能使指令獲取成為一個瓶頸,但x87指令更短的長度不足以抵消上述嚴重的缺點。

12.6. ​​​​​​​指令時延

簡單的整數指令有1時鐘週期的時延。乘法、除法及浮點指令有更長的時延。

不像大多數其他處理器,當在同一個流水線裡,混用不同時延的指令時,在Atom處理器中我沒有發現任何時延。

LEA指令使用地址生成單元(AGU),而不是ALU。在依賴一個指標暫存器或索引暫存器時,因為AGU與ALU間的距離,這導致了4時鐘週期的時延。因此,在大多數情形裡,使用加法與偏移指令,比使用LEA指令更快。

在SIMD向量暫存器與通用暫存器或標記暫存器間移動資料的指令有4 ~ 5時鐘週期的時延,因為整數執行群與浮點/SIMD群有獨立的暫存器檔案。

對目標以外型別使用XMM移動、混排與布林指令,沒有懲罰。例如,可以對浮點資料使用PSHUFD,對整數資料使用MOVAPS。

12.7. ​​​​​​​記憶體訪問

每個核有3個快取:

  • 1級指令快取。32kB,8路,組相聯,每行64B。
  • 1級資料快取。24kB,6路,組相聯,每行64B。
  • 2級快取。512kB或1MB,8路,組相聯,每行64B。

每個快取在兩個執行緒間共享,但不能在核間共享。所有快取都有硬體預取器。

帶有記憶體運算元的指令,只要記憶體運算元也被快取,與類似的帶暫存器運算元的指令相比,無需額外的執行時間。但由於兩個原因,使用記憶體運算元不是完全“免費的”。首先,記憶體運算元使用埠0下的記憶體單元,因此該指令不能與另一條要求埠0的指令成對。其次,記憶體運算元可能使指令程式碼更長,特別如果它有完整的4位元組地址。當指令獲取被限制在每時鐘週期8位元組時,這會是一個瓶頸。對執行在整數執行群的指令,快取訪問是快的,但對執行在浮點/SIMD群的指令是慢的,因為記憶體單元僅連線到整數群。使用浮點或XMM暫存器的指令通常需要4 ~ 5時鐘週期來讀寫記憶體,而整數指令,由於寫轉發,僅需要1時鐘週期的實際快取時延,如下所述。依賴於一個最近修改的指標暫存器的記憶體讀有3時鐘週期的時延。

寫轉發非常高效。在某個時鐘週期寫入的記憶體運算元,可以在下一個時鐘週期讀回。不像大多數其他處理器,即使讀運算元比前面的寫運算元更長或有不同的對齊,Atom也可以進行寫轉發。我發現的僅有的寫轉發失敗是在跨越快取行邊界時。

在跨越快取行邊界時,非對齊記憶體訪問代價極高。跨64位元組邊界的非對齊記憶體讀或寫需要16個時鐘週期。效能監控計數器顯示,非對齊記憶體訪問涉及1級快取的4次訪問,而兩次訪問就足夠了。在不跨64位元組邊界時,非對齊記憶體訪問沒有代價。

12.8. ​​​​​​​分支與迴圈

跳轉與分支的吞吐率是每2時鐘週期一個跳轉。不採用的分支每時鐘週期一個。因此,迴圈的最小執行時間是2時鐘週期,如果該迴圈不包含16位元組邊界,而如果在迴圈裡跨了16位元組邊界,則是3 ~ 4時鐘週期。

分支預測使用一個12位元的全域性歷史暫存器,如第22頁所述。這給出了相當好的預測,但分支目標緩衝(BTB)僅有28項。在我的一些測試中,BTB不命中率要高於命中率。分支誤預測的代價最多是13個時鐘週期,有時稍低。如果正確預測分支被採用,但因為BTB項被逐出,預測目標失敗,那麼懲罰大約是7時鐘週期。這發生得非常頻繁,因為模式歷史表有4096項,而BTB僅有128項。

12.9. ​​​​​​​多執行緒

每個處理器核可以執行兩個執行緒。這兩個執行緒競爭相同的資源,因此兩個執行緒的速度比單獨執行時的速度要慢。

快取、解碼器、埠與執行單元在同一個核上的兩個執行緒間共享,而預取緩衝、指令佇列與暫存器檔案是分開的。

整個核的最大吞吐率仍然是每時鐘週期兩條指令,平均每個執行緒每時鐘週期一條指令。

如果這兩個執行緒需要相同的資源,例如記憶體訪問,那麼每個執行緒將在一半時間裡得到競爭資源。換而言之,如果沒有快取不命中,每個執行緒每兩個時鐘週期訪問記憶體一次。

有趣的是,我發現在執行兩個執行緒時,每個執行緒的指令獲取速度大於單執行緒的一半速度,但小於單執行緒的速度。在執行兩個執行緒時每執行緒指令獲取速度在每時鐘週期4到8位元組之間,但不會超過8。這個值嚴重依賴指令長度與對齊。這些發現顯示,可能有兩個指令獲取器,在有限的程度上,在另一個執行緒不活動時,能服務同一個執行緒。

分支目標緩衝(BTB)與模式歷史表在兩個執行緒間共享。如果兩個執行緒執行相同的程式碼(不同的資料),那麼我們可能期望這兩個執行緒在這兩個表裡共享相同的項。但是,這不會發生。這兩個表顯然由分支地址與執行緒號的某個簡單的雜湊函式索引,因此在這兩個執行緒裡相同的項不會使用相同的表索引。我的測試顯示執行在同一個核上的兩個執行緒,比單獨執行的一個執行緒,分支誤預測率稍高,而BTB不命中顯著增多。在有許多分支的最壞情形裡,每個執行緒的速度還不到單獨執行執行緒的一半。

這些資源衝突僅適用於兩個執行緒執行在同一個核上的情形。Atom處理器的某些版本有兩個每個都能執行兩個執行緒的核,同時最多執行4個執行緒。如果每個核僅執行一個執行緒,那麼不存在資源衝突。幸運的是,大多數作業系統傾向於將兩個執行緒放在兩個不同的核上。但如果有超過兩個執行緒,那麼將有一些執行緒共享同一個處理器核。

向執行在同一個核裡兩個執行緒分配不同的優先順序是不可能的。因此,一個低優先順序執行緒可能從同一個核裡執行的另一個執行緒得到資源,使高優先順序執行緒很不幸地僅以最大速度的一半執行。

在我的測試中,這發生了幾次。

12.10. ​​​​​​​Atom裡的瓶頸

在Atom處理器裡的某些執行單元相當強大。每時鐘週期,它可以處理兩個完整的128位整數向量ALU指令,雖然因為系統別處的瓶頸,這個能力很少被完全發揮。浮點加法單元也相當好,而乘法與除法比較慢。

在大多數情形裡,執行很可能被執行單元以外的因素所限制。最可能的瓶頸有:

  • 順序執行。在等待快取不命中或一條長時延指令時,處理器無事可做,除非另一個執行緒可以同時使用其資源。
  • 在大多數情形裡,指令獲取速度小於每時鐘週期8位元組。如果平均指令長度超過4位元組,這是不足夠的。
  • 記憶體訪問限制為每時鐘週期一次讀或寫。不能同時進行讀寫。
  • 浮點與SIMD指令有長的記憶體訪問時延。
  • 分支目標緩衝相當小。
  • X87形式浮點程式碼的執行比SSE形式程式碼要慢得多。
  • 僅當代碼特別對Atom優化,且指令以允許成對的方式排列時,才能實現每時鐘週期兩條指令的最大吞吐率。
  • 如果兩個執行緒同時執行在一個核上,吞吐率減半。資源很可能成為瓶頸,比如快取、記憶體埠與分支目標快取,在兩個執行緒間共享。

結論是,對CPU密集與記憶體密集應用,比如遊戲、圖形處理及浮點算術,Atom力有不逮。低的價格與功耗使它對要求不那麼高的目的有用,如辦公應用與嵌入式應用。執行4個執行緒的能力,使Atom對流量有限的伺服器應用有用。