簡體   English   中英

Skylake 在一個周期內可以執行多少個 1 字節的 NOP

[英]How many 1-byte NOPs can Skylake execute at one cycle

我將分支目標與 NOP 對齊,有時 CPU 會執行這些 NOP,最多 15 個 NOP。 Skylake 在一個周期內可以執行多少個 1 字節的 NOP? 其他兼容英特爾的處理器呢,比如 AMD? 我不僅對 Skylake 感興趣,還對其他微架構感興趣。 執行 15 個 NOP 的序列可能需要多少個周期? 我想知道添加這些 NOP 的額外代碼大小和額外執行時間是否值得。 每當我編寫align指令時,這不是我添加這些 NOP 而是自動添加匯編程序。

更新:我已經管理匯編器自動插入多字節NOP

添加這些 NOP 的不是我,而是一個匯編程序。 它非常愚蠢並且不支持用於對齊的選項 (BASM) - 只有一個選項 - 邊界大小。

我不知道“BASM”是什么,我在網上找不到任何對它的引用(除了this ,它顯然不是 x86),但如果它不支持多字節 NOP,你真的需要一個不同的匯編程序。 這只是很基本的東西,一直在英特爾和AMD架構手冊多年 Gnu 匯編器可以為 ALIGN 指令執行此操作,Microsoft 的 MASM 也可以執行此操作。 開源NASMYASM匯編器也支持這一點,並且它們中的任何一個都可以輕松集成到任何現有的構建系統中。

多字節 NOP 是指以下內容,您可以在 AMD 和 Intel 處理器手冊中找到:

Length   |  Mnemonic                                 |  Opcode Bytes
---------|-------------------------------------------|-------------------------------------
1 byte   |  NOP                                      |  90
2 bytes  |  66 NOP                                   |  66 90
3 bytes  |  NOP DWORD [EAX]                          |  0F 1F 00
4 bytes  |  NOP DWORD [EAX + 00H]                    |  0F 1F 40 00
5 bytes  |  NOP DWORD [EAX + EAX*1 + 00H]            |  0F 1F 44 00 00
6 bytes  |  66 NOP DWORD [EAX + EAX*1 + 00H]         |  66 0F 1F 44 00 00
7 bytes  |  NOP DWORD [EAX + 00000000H]              |  0F 1F 80 00 00 00 00
8 bytes  |  NOP DWORD [EAX + EAX*1 + 00000000H]      |  0F 1F 84 00 00 00 00 00
9 bytes  |  66 NOP DWORD [EAX + EAX*1 + 00000000H]   |  66 0F 1F 84 00 00 00 00 00

兩家制造商提供的序列建議在 9 個字節后略有不同,但如此長的 NOP ……並不是很常見。 並且可能無關緊要,因為帶有過多前綴的超長 NOP 指令無論如何都會降低性能。 這些一直工作到 Pentium Pro,因此它們今天得到普遍支持。

Agner Fog 關於多字節 NOP 是這樣說的:

多字節 NOP 指令具有操作碼0F 1F + 一個虛擬內存操作數。 多字節 NOP 指令的長度可以通過向虛擬內存操作數添加 1 或 4 個字節的位移和一個 SIB 字節以及添加一個或多個66H前綴來調整。 過多的前綴會導致較舊的微處理器出現延遲,但在大多數處理器上至少可以接受兩個前綴。 最多 10 個字節的任何長度的 NOP 都可以用這種方式構造,前綴不超過兩個。 如果處理器可以無懲罰地處理多個前綴,那么長度可以達到 15 個字節。

所有冗余/多余的前綴都被簡單地忽略。 當然,優點是許多較新的處理器對多字節 NOP 的解碼率較低,從而提高了效率。 它們將比一系列 1 字節 NOP ( 0x90 ) 指令更快。

也許比多字節 NOP 更好的對齊方式是使用更長形式的指令,您已經在代碼中使用了這些指令。 這些更長的編碼不需要更長的時間來執行(它們只影響解碼帶寬),因此它們比 NOP 更快/更便宜。 這方面的例子是:

  • 使用 mod-reg-r/m 字節形式的指令,如INCDECPUSHPOP等,而不是短版本
  • 使用更長的等效指令,例如ADD而不是INCLEA而不是MOV
  • 編碼更長形式的立即數操作數(例如,32 位立即數而不是符號擴展的 8 位立即數)
  • 添加 SIB 字節和/或不必要的前綴(例如,長模式下的操作數大小、段和 REX)

Agner Fog 的手冊也詳細討論並給出了這些技術的示例。

我不知道有任何匯編器會自動為您進行這些轉換/優化(匯編器會選擇最短的版本,原因很明顯),但它們通常有一個嚴格模式,您可以在其中強制使用特定的編碼,或者您可以手動發出指令字節。 無論如何,您只能在對性能高度敏感的代碼中執行此操作,在這些代碼中工作實際上會得到回報,因此大大限制了所需工作的范圍。

我想知道添加這些 NOP 的額外代碼大小和額外執行時間是否值得付出代價。

一般來說,沒有。 雖然數據對齊非常重要並且基本上是免費的(盡管二進制文件的大小),但代碼對齊不太重要。 在緊密循環的情況下,它可能會產生顯着差異,但這僅對代碼中的熱點很重要,您的分析器已經識別出這些熱點,然后您可以在必要時執行操作以手動對齊代碼。 否則,我不會擔心。

對齊函數是有意義的,因為它們之間的填充字節永遠不會被執行(而不是在此處使用 NOP,您經常會看到INT 3或無效指令,例如UD2 ),但我不會四處對齊您的所有函數內的分支目標只是理所當然。 僅在已知的關鍵內部循環中執行此操作。

和以往一樣,Agner Fog 談到了這一點,並且比我說得更好:

大多數微處理器以對齊的 16 字節或 32 字節塊獲取代碼。 如果一個重要的子程序入口或跳轉標簽恰好在一個 16 字節塊的末尾附近,那么微處理器在獲取該代碼塊時只會得到幾個有用的代碼字節。 在解碼標簽之后的第一條指令之前,它可能還必須獲取接下來的 16 個字節。 這可以通過將重要的子程序條目和循環條目對齊 16 來避免。對齊 8 將確保在第一次取指令時可以加載至少 8 字節的代碼,如果指令很小,這可能就足夠了。 如果子例程是關鍵熱點的一部分並且前面的代碼不太可能在同一上下文中執行,我們可以根據緩存行大小(通常為 64 字節)對齊子例程條目。

代碼對齊的一個缺點是一些緩存空間在對齊的代碼條目之前丟失為空白空間。

在大多數情況下,代碼對齊的影響很小。 所以我的建議是只在最關鍵的情況下對齊代碼,比如關鍵的子程序和關鍵的最內層循環。

對齊子例程入口就像在子例程入口之前放置盡可能多的NOP一樣簡單,以使地址可以根據需要被 8、16、32 或 64 整除。 匯編程序使用ALIGN指令完成此操作。 插入的NOP不會降低性能,因為它們永遠不會被執行。

對齊循環入口的問題更大,因為前面的代碼也被執行了。 可能需要多達 15 個NOP將循環條目對齊 16 個。這些NOP將在進入循環之前執行,這將花費處理器時間。 使用更長的指令比使用大量單字節NOP更有效。 最好的現代匯編程序會這樣做,並使用MOV EAX,EAXLEA EBX,[EBX+00000000H]類的指令來填充ALIGN nn語句之前的空間。 LEA指令特別靈活。 通過不同地添加一個 SIB 字節、一個段前綴和一個或四個零字節的偏移量,可以給像LEA EBX,[EBX]這樣的指令提供從 2 到 8 的任意長度。 不要在 32 位模式下使用兩字節偏移量,因為這會減慢解碼速度。 並且不要使用多個前綴,因為這會降低舊英特爾處理器上的解碼速度。

使用MOV RAX,RAXLEA RBX,[RBX+0]等偽 NOP 作為填充符的缺點是它對寄存器有錯誤的依賴,並且它使用了執行資源。 最好使用可以調整到所需長度的多字節 NOP 指令。 多字節 NOP 指令可用於所有支持條件移動指令的處理器,即 Intel PPro、P2、AMD Athlon、K7 和更高版本。

對齊循環條目的另一種方法是以比所需更長的方式對前面的指令進行編碼。 在大多數情況下,這不會增加執行時間,但可能會增加指令提取時間。

他還繼續展示了通過移動前面的子程序條目來對齊內循環的另一種方法的示例。 這有點尷尬,即使在最好的組裝商中也需要一些手動調整,但這可能是最優化的機制。 同樣,這僅在熱路徑上的關鍵內部循環中很重要,您可能已經在那里進行了挖掘和微優化。

有趣的是,我對我正在優化的代碼進行了多次基准測試,並沒有發現對齊循環分支目標有什么好處。 例如,我正在編寫一個優化的strlen函數(Gnu 庫有一個,但微軟沒有),並嘗試在 8 字節、16 字節和 32 字節邊界上對齊主內循環的目標。 這些都沒有太大區別,尤其是與我在重寫代碼時取得的其他顯着性能進步相比時更是如此。

請注意,如果您沒有針對特定處理器進行優化,那么您可能會瘋狂地尋找最佳的“通用”代碼。 當談到對齊對速度的影響時, 情況可能會有很大差異 糟糕的對齊策略通常比根本沒有對齊策略更糟糕。

2 的冪邊界總是一個好主意,但這很容易實現,無需任何額外的努力。 再次強調,不要立即取消對齊,因為它可能很重要,但同樣的道理,不要執着於嘗試對齊每個分支目標。

在最初的 Core 2(Penryn 和 Nehalem)微架構上,對齊曾經是一個更大的問題,其中大量的解碼瓶頸意味着,盡管有 4 寬的問題寬度,但您很難保持其執行單元忙碌。 隨着在 Sandy Bridge 中引入 µop 緩存(奔騰 4 的少數幾個不錯的特性之一,最終被重新引入到 P6 擴展系列中),前端吞吐量得到了相當大的提高,而且這變得不那么重要了。問題。

坦率地說,編譯器也不太擅長進行這些類型的優化。 GCC 的-O2開關意味着-falign-functions-falign-jumps-falign-loops-falign-labels開關,默認首選項在 8 字節邊界上對齊。 這是一種非常生硬的方法,而且里程數也各不相同。 正如我在上面鏈接的那樣,關於禁用這種對齊方式和使用緊湊代碼是否實際上可以提高性能的報告各不相同。 此外,您將看到編譯器所做的最好的事情是插入多字節 NOP。 我還沒有看到使用更長形式的指令或為了對齊而徹底重新排列代碼的程序。 所以我們還有很長的路要走,這是一個非常難解決的問題。 有些人正在研究它,但這只是表明問題確實是多么棘手: “指令流中的微小變化,例如插入單個 NOP 指令,可能會導致顯着的性能增量,其效果是暴露編譯器和性能優化工作以感知不需要的隨機性。” (請注意,雖然有趣,但這篇論文來自 Core 2 的早期,正如我之前提到的,它比大多數人遭受了更多的未對齊懲罰。我不確定你是否會看到今天的微體系結構有同樣的巨大改進,但是我不能肯定,因為我還沒有進行測試。也許 Google 會雇用我,我可以再發表一篇論文?)

Skylake 在一個周期內可以執行多少個 1 字節的 NOP? 其他兼容英特爾的處理器呢,比如 AMD? 我不僅對 Skylake 感興趣,還對其他微架構感興趣。 執行 15 個 NOP 的序列可能需要多少個周期?

可以通過查看 Agner Fog 的指令表並搜索NOP來回答此類問題。 我不會費心將他的所有數據提取到這個答案中。

不過,一般來說,只要知道 NOP 不是免費的。 盡管它們不需要執行單元/端口,但它們仍然必須像任何其他指令一樣通過管道運行,因此它們最終會受到處理器問題(和/或退役)寬度的瓶頸。 這通常意味着您每個時鍾可以執行 3 到 5 個 NOP。

NOP 仍然占用 µop 緩存中的空間,這意味着降低了代碼密度和緩存效率。

在許多方面,您可以將NOP視為XOR reg, regMOV ,由於寄存器重命名而在前端被省略。

另請參閱 Cody 對我遺漏的許多好東西的回答,因為他已經涵蓋了它。


永遠不要使用多個 1 字節的 NOP 所有的組裝商都有辦法獲得長時間的 NOP; 見下文。

15 個 NOP 需要 3.75c 才能以通常的每個時鍾 4 個發出,但如果那時代碼在長依賴鏈上遇到瓶頸,則可能根本不會減慢您的代碼的速度。 他們確實一直占據着 ROB 的空間,直到退休。 他們唯一不做的就是使用執行端口。 關鍵是,CPU 性能不是附加的。 你不能只是說“這需要 5 個周期,這需要 3 個,所以它們一起需要 8 個”。 亂序執行的要點是與周圍的代碼重疊。

許多 1 字節短 NOP 對 SnB 系列的更糟糕的影響是,它們往往會溢出每個對齊的 32B x86 代碼塊 3 行的 uop 緩存限制。 這意味着整個 32B 塊必須始終從解碼器運行,而不是從 uop 緩存或循環緩沖區運行。 (循環緩沖區僅適用於所有 uop 都在 uop 緩存中的循環)。

您應該在一行中最多只有 2 個實際執行的 NOP,然后僅當您需要填充超過 10B 或 15B 或其他東西時。 (某些 CPU 在解碼具有很多前綴的指令時表現非常糟糕,因此對於實際執行的 NOP,最好不要將前綴重復到 15B(最大 x86 指令長度)。


YASM 默認進行長時間的 NOP。 對於 NASM,請使用默認情況下未啟用smartalign標准宏包 它迫使您選擇 NOP 策略。

%use smartalign
ALIGNMODE p6, 32     ;  p6 NOP strategy, and jump over the NOPs only if they're 32B or larger.

IDK 如果 32 是最佳的。 另外,請注意最長的 NOP 可能會使用很多前綴,並且在 Silvermont 或 AMD 上解碼速度很慢 查看 NASM 手冊以了解其他模式。

GNU 匯編器的.p2align指令為您提供了一些條件行為.p2align 4,,10將對齊到 16 (1<<4),但.p2align 4,,10是跳過 10 個字節或更少。 (空的 2nd arg 表示填充符是 NOP,而 2 的冪對齊名稱是因為純.align在某些平台上是 2 的冪,而在其他平台上是字節計數)。 gcc 經常在循環頂部之前發出這個:

  .p2align 4,,10 
  .p2align 3
.L7:

所以你總是得到 8 字節對齊(無條件.p2align 3 ),但也可能是 16 字節,除非這會浪費超過 10B。 首先放置較大的對齊對於避免獲得例如 1 字節 NOP 然后是 8 字節 NOP 而不是單個 9 字節 NOP 很重要。

可能可以使用 NASM 宏來實現此功能。


缺少匯編程序沒有的功能(AFAIK)

  • 通過使用更長的編碼(例如 imm32 代替 imm8 或不需要的 REX 前綴)填充前面的指令的指令,以在沒有 NOP 的情況下實現所需的對齊。
  • 基於后續指令長度的智能條件內容,例如如果在達到下一個 16B 或 32B 邊界之前可以解碼 4 條指令,則不填充。

解碼瓶頸的對齊通常不再很重要,這是一件好事,因為調整它通常涉及手動組裝/反匯編/編輯周期,並且如果前面的代碼發生更改,則必須再次查看。


尤其是如果您可以對有限的一組 CPU 進行調優,請進行測試,如果您沒有發現性能優勢,請不要進行填充。 在很多情況下,尤其是對於具有 uop 緩存和/或循環緩沖區的 CPU,在函數內甚至循環內不對齊分支目標是可以的。


由於不同對齊而導致的一些性能變化是它使不同的分支在分支預測緩存中互為別名。 即使 uop 緩存完美運行並且從 uop 緩存中獲取大部分為空的行沒有前端瓶頸,這種次要的微妙效果仍然存在。

另請參閱x86-64 程序集的性能優化 - 對齊和分支預測

Skylake 一般可以在一個周期內執行四個單字節 nops 至少在 Sandy Bridge(以下簡稱 SnB)微架構中也是如此。

Skylake 和其他回到 SnB 的公司,通常也能夠在一個周期內執行四個長於 1 字節的nop ,除非它們長到遇到前端限制。


現有的答案要完整得多,並解釋了為什么您可能不想使用此類單字節nop指令,因此我不會添加更多內容,但我認為有一個答案可以清楚地回答標題問題,這很好。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM