llvm-mca - LLVM 機器碼分析器

概要

llvm-mca [選項] [輸入]

描述

llvm-mca 是一個效能分析工具,它使用 LLVM 中可用的資訊(例如排程模型)來靜態地測量特定 CPU 中機器碼的效能。

效能的測量方式是吞吐量以及處理器資源消耗。該工具目前適用於 LLVM 中具有可用排程模型的處理器後端。

此工具的主要目標不僅是預測代碼在目標上運行時的效能,還包括幫助診斷潛在的效能問題。

給定一個組合語言序列,llvm-mca 會估計每週期指令數 (IPC),以及硬體資源壓力。分析和報告風格的靈感來自英特爾的 IACA 工具。

例如,您可以使用 clang 編譯代碼,輸出組合語言,並將其直接傳送到 llvm-mca 進行分析

$ clang foo.c -O2 --target=x86_64 -S -o - | llvm-mca -mcpu=btver2

或者對於英特爾語法

$ clang foo.c -O2 --target=x86_64 -masm=intel -S -o - | llvm-mca -mcpu=btver2

(llvm-mca 通過在輸入開頭是否存在 .intel_syntax 指令來檢測英特爾語法。預設情況下,其輸出語法与其輸入的語法相匹配。)

排程模型不僅用於計算指令延遲和吞吐量,還用於了解可用的處理器資源以及如何模擬它們。

根據設計,llvm-mca 進行的分析品質不可避免地會受到 LLVM 中排程模型品質的影響。

如果您發現效能報告對於某個處理器不準確,請針對相應的後端提交錯誤報告

選項

如果 輸入 是“-”或省略,llvm-mca 將從標準輸入讀取。否則,它將從指定的文件名讀取。

如果省略了 -o 選項,則如果輸入來自標準輸入,llvm-mca 會將其輸出發送到標準輸出。如果 -o 選項指定了“-”,則輸出也會發送到標準輸出。

-help

列印命令列選項的摘要。

-o <filename>

使用 <filename> 作為輸出檔名。有關詳細資訊,請參閱上面的摘要。

-mtriple=<target triple>

指定目標三元組字串。

-march=<arch>

指定要分析其程式碼的架構。預設為主機預設目標。

-mcpu=<cpuname>

指定要分析其程式碼的處理器。預設情況下,CPU 名稱會從主機自動偵測。

-output-asm-variant=<variant id>

指定工具產生的報表的輸出組合語言變體。在 x86 上,可能的值為 [0, 1]。此旗標的值為 0(或 1)時,會為工具在分析報告中列印的程式碼啟用 AT&T(或 Intel)組合語言格式。

-print-imm-hex

在報表中列印的輸出組合語言中,優先使用十六進制格式表示數值常數。

-dispatch=<width>

為處理器指定不同的發送寬度。發送寬度預設為處理器排程模型中的「IssueWidth」欄位。如果寬度為零,則使用預設發送寬度。

-register-file-size=<size>

指定暫存器檔案的大小。指定後,此旗標會限制可供暫存器重新命名使用的實體暫存器數量。此旗標的值為零表示「不限數量的實體暫存器」。

-iterations=<number of iterations>

指定要執行的迭代次數。如果此旗標設定為 0,則工具會將迭代次數設定為預設值(即 100)。

-noalias=<bool>

如果設定,則工具會假設載入和儲存沒有別名。這是預設行為。

-lqueue=<load queue size>

指定工具模擬的載入/儲存單元中載入佇列的大小。預設情況下,工具會假設載入佇列中的項目數量不受限制。此旗標的值為零將被忽略,並且會改用預設的載入佇列大小。

-squeue=<存放 佇列 大小>

指定由工具模擬的加載/存放單元中存放佇列的大小。預設情況下,該工具假設存放佇列中的條目數量不受限制。此標誌的值為零將被忽略,並改用預設的存放佇列大小。

-timeline

啟用時間軸視圖。

-timeline-max-iterations=<迭代次數>

限制時間軸視圖中要列印的迭代次數。預設情況下,時間軸視圖最多會顯示 10 次迭代的資訊。

-timeline-max-cycles=<週期數>

限制時間軸視圖中的週期數,或使用 0 表示沒有限制。預設情況下,週期數設定為 80。

-resource-pressure

啟用資源壓力視圖。預設情況下啟用此選項。

-register-file-stats

啟用暫存器檔案使用統計資訊。

-dispatch-stats

啟用額外的發送統計資訊。此視圖會收集並分析指令發送事件,以及靜態/動態發送停頓事件。預設情況下停用此視圖。

-scheduler-stats

啟用額外的排程器統計資訊。此視圖會收集並分析指令發佈事件。預設情況下停用此視圖。

-retire-stats

啟用額外的退出控制單元統計資訊。預設情況下停用此視圖。

-instruction-info

啟用指令資訊視圖。預設情況下啟用此選項。

-show-encoding

啟用在指令資訊視圖中列印指令編碼。

-show-barriers

啟用在指令資訊視圖中列印 LoadBarrier 和 StoreBarrier 標誌。

-all-stats

列印所有硬體統計資訊。這將啟用與發送邏輯、硬體排程器、暫存器檔案和退出控制單元相關的額外統計資訊。預設情況下停用此選項。

-all-views

啟用所有視圖。

-instruction-tables

根據處理器模型可用的靜態資訊列印資源壓力資訊。這與資源壓力視圖不同,因為它不需要模擬程式碼。相反,它會依序列印每條指令的理論均勻資源壓力分佈。

-bottleneck-analysis

列印有關影響吞吐量的瓶頸的資訊。此分析可能很耗時,預設情況下會停用。瓶頸在摘要視圖中會突出顯示。目前不支援對具有順序後端的處理器進行瓶頸分析。

-json

以有效的 JSON 格式列印請求的視圖。指令和處理器資源會列印為特殊頂級 JSON 物件的成員。個別視圖透過索引引用它們。但是,目前並非所有視圖都支援。例如,瓶頸分析的報告不會以 JSON 列印。目前支援所有預設視圖。

-disable-cb

強制使用通用的 CustomBehaviour 和 InstrPostProcess 類別,而不是使用目標特定的實現。通用類別永遠不會偵測到任何自訂危害或對指令進行任何後處理修改。

-disable-im

強制使用通用的 InstrumentManager,而不是使用目標特定的實現。通用類別會建立不提供額外資訊的 Instruments,並且 InstrumentManager 永遠不會覆蓋給定指令的預設排程類別。

-skip-unsupported-instructions=<reason>

強制 llvm-mca 在存在無法解析或缺少關鍵排程資訊的指令時繼續執行。請注意,由於這些不受支援的指令會被忽略,就好像它們不是作為輸入的一部分提供的一樣,因此產生的分析會受到影響。

<reason> 的選擇控制 mca 何時會報告錯誤。<reason> 可以是 none(預設)、lack-schedparse-failureany

退出狀態

llvm-mca 在成功時返回 0。否則,會將錯誤訊息列印到標準錯誤,並且工具返回 1。

使用標記分析特定程式碼區塊

llvm-mca 允許選擇性地使用特殊的程式碼註釋來標記要分析的組譯程式碼區域。以子字串 LLVM-MCA-BEGIN 開頭的註釋標記分析區域的開頭。以子字串 LLVM-MCA-END 開頭的註釋標記區域的結束。例如

# LLVM-MCA-BEGIN
  ...
# LLVM-MCA-END

如果沒有指定使用者定義的區域,則 llvm-mca 會假設一個預設區域,其中包含輸入檔案中的每條指令。每個區域都單獨分析,最終效能報告是為每個分析區域產生的所有報告的聯合。

分析區域可以有名稱。例如

# LLVM-MCA-BEGIN A simple example
  add %eax, %eax
# LLVM-MCA-END

上面範例中的程式碼定義了一個名為「一個簡單的範例」的區域,其中包含一條指令。請注意,區域名稱不必在 LLVM-MCA-END 指令中重複。在沒有重疊區域的情況下,匿名的 LLVM-MCA-END 指令總是結束當前活動的使用者定義區域。

巢狀區域範例

# LLVM-MCA-BEGIN foo
  add %eax, %edx
# LLVM-MCA-BEGIN bar
  sub %eax, %edx
# LLVM-MCA-END bar
# LLVM-MCA-END foo

重疊區域範例

# LLVM-MCA-BEGIN foo
  add %eax, %edx
# LLVM-MCA-BEGIN bar
  sub %eax, %edx
# LLVM-MCA-END foo
  add %eax, %edx
# LLVM-MCA-END bar

請注意,多個匿名區域不能重疊。此外,重疊區域不能具有相同的名稱。

不支援從高階原始碼(如 C 或 C++)標記區域。作為一種解決方法,可以使用內嵌組譯指令

int foo(int a, int b) {
  __asm volatile("# LLVM-MCA-BEGIN foo":::"memory");
  a += 42;
  __asm volatile("# LLVM-MCA-END":::"memory");
  a *= b;
  return a;
}

但是,這會干擾迴圈向量化等優化,並且可能會影響產生的程式碼。這是因為 __asm 語句被視為具有重要副作用的真實程式碼,這限制了它們周圍程式碼的轉換方式。如果使用者想要利用內嵌組譯來發出標記,則建議始終驗證輸出組譯是否與在沒有標記的情況下產生的組譯相同。Clang 選項發出優化報告也有助於檢測遺漏的優化。

INSTRUMENT 區域

InstrumentRegion 描述了由特殊 LLVM-MCA 註釋指令保護的組譯程式碼區域。

# LLVM-MCA-<INSTRUMENT_TYPE> <data>
  ...  ## asm

其中 INSTRUMENT_TYPE 是由目標定義的類型,並且預計使用 data

以子字串 LLVM-MCA-<INSTRUMENT_TYPE> 開頭的註釋會將資料引入 llvm-mca 的範圍內,以用於其對所有後續指令的分析。

如果稍後在指令清單中找到具有相同 INSTRUMENT_TYPE 的註釋,則原始 InstrumentRegion 將自動結束,並開始一個新的 InstrumentRegion。

如果存在包含不同 INSTRUMENT_TYPE 的註釋,則兩個資料集都將保持可用狀態。與 AnalysisRegion 相反,InstrumentRegion 不需要註釋來結束區域。

LLVM-MCA- 為字首但不對應於目標的有效 INSTRUMENT_TYPE 的註釋會導致錯誤,但 BEGINEND 除外,因為它們對應於 AnalysisRegions。不以 LLVM-MCA- 開頭的註釋會被 llvm-mca 忽略。

指令(MCInst)僅在其位置在 [R.RangeStart, R.RangeEnd] 範圍內時才會新增到 InstrumentRegion R。

在 RISCV 目標上,向量指令的行為會因 LMUL 而異。可以使用採用以下形式的註釋來檢測程式碼

# LLVM-MCA-RISCV-LMUL <M1|M2|M4|M8|MF2|MF4|MF8>

RISCV InstrumentManager 將覆寫向量指令的排程類別,以使用其偽指令的排程行為,該行為取決於 LMUL。將 RISCV 檢測註釋直接放在 vset{i}vl{i} 指令之後是有意義的,儘管它們可以放在程式的任何位置。

沒有呼叫 vset{i}vl{i} 的程式範例

# LLVM-MCA-RISCV-LMUL M2
vadd.vv v2, v2, v2

呼叫 vset{i}vl{i} 的程式範例

vsetvli zero, a0, e8, m1, tu, mu
# LLVM-MCA-RISCV-LMUL M1
vadd.vv v2, v2, v2

多次呼叫 vset{i}vl{i} 的程式範例

vsetvli zero, a0, e8, m1, tu, mu
# LLVM-MCA-RISCV-LMUL M1
vadd.vv v2, v2, v2
vsetvli zero, a0, e8, m8, tu, mu
# LLVM-MCA-RISCV-LMUL M8
vadd.vv v2, v2, v2

呼叫 vsetvl 的程式範例

vsetvl rd, rs1, rs2
# LLVM-MCA-RISCV-LMUL M1
vadd.vv v12, v12, v12
vsetvl rd, rs1, rs2
# LLVM-MCA-RISCV-LMUL M4
vadd.vv v12, v12, v12

LLVM-MCA 的運作方式

llvm-mca 將組譯程式碼作為輸入。借助現有的 LLVM 目標組譯解析器,將組譯程式碼解析為 MCInst 序列。然後,Pipeline 模組會分析已解析的 MCInst 序列,以產生效能報告。

Pipeline 模組會模擬機器碼序列在迴圈迭代中的執行(預設為 100 次)。在此過程中,pipeline 會收集許多與執行相關的統計數據。在此過程結束時,pipeline 會根據收集到的統計數據生成並列印報告。

以下是由工具為兩個包含四個元素的封裝浮點向量之點積生成的效能報告範例。分析是針對目標 x86、cpu btver2 進行的。可以使用位於 test/tools/llvm-mca/X86/BtVer2/dot-product.s 的範例,透過以下命令產生以下結果

$ llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=btver2 -iterations=300 dot-product.s
Iterations:        300
Instructions:      900
Total Cycles:      610
Total uOps:        900

Dispatch Width:    2
uOps Per Cycle:    1.48
IPC:               1.48
Block RThroughput: 2.0


Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)

[1]    [2]    [3]    [4]    [5]    [6]    Instructions:
 1      2     1.00                        vmulps      %xmm0, %xmm1, %xmm2
 1      3     1.00                        vhaddps     %xmm2, %xmm2, %xmm3
 1      3     1.00                        vhaddps     %xmm3, %xmm3, %xmm4


Resources:
[0]   - JALU0
[1]   - JALU1
[2]   - JDiv
[3]   - JFPA
[4]   - JFPM
[5]   - JFPU0
[6]   - JFPU1
[7]   - JLAGU
[8]   - JMul
[9]   - JSAGU
[10]  - JSTC
[11]  - JVALU0
[12]  - JVALU1
[13]  - JVIMUL


Resource pressure per iteration:
[0]    [1]    [2]    [3]    [4]    [5]    [6]    [7]    [8]    [9]    [10]   [11]   [12]   [13]
 -      -      -     2.00   1.00   2.00   1.00    -      -      -      -      -      -      -

Resource pressure by instruction:
[0]    [1]    [2]    [3]    [4]    [5]    [6]    [7]    [8]    [9]    [10]   [11]   [12]   [13]   Instructions:
 -      -      -      -     1.00    -     1.00    -      -      -      -      -      -      -     vmulps      %xmm0, %xmm1, %xmm2
 -      -      -     1.00    -     1.00    -      -      -      -      -      -      -      -     vhaddps     %xmm2, %xmm2, %xmm3
 -      -      -     1.00    -     1.00    -      -      -      -      -      -      -      -     vhaddps     %xmm3, %xmm3, %xmm4

根據此報告,點積核心已執行 300 次,總共模擬了 900 條指令。模擬微指令 (uOps) 的總數也是 900。

該報告分為三個主要部分。第一部分收集了一些效能數據;本節的目標是快速概述效能吞吐量。重要的效能指標是**IPC**、**每週期 uOps 數**和**區塊倒數吞吐量**(區塊倒數吞吐量)。

欄位*DispatchWidth* 是每個模擬週期分派到亂序後端的微指令的最大數量。對於具有順序後端的處理器,*DispatchWidth* 是每個模擬週期發佈到後端的微指令的最大數量。

IPC 是透過將模擬指令總數除以週期總數來計算的。

欄位*區塊倒數吞吐量*是區塊吞吐量的倒數。區塊吞吐量是一個理論值,計算為在沒有迴圈進位相依性的情況下,每個模擬時鐘週期可以執行的最大區塊數(即迭代次數)。區塊吞吐量主要受分派速率和硬體資源可用性的限制。

在沒有迴圈進位數據相依性的情況下,觀察到的 IPC 趨向於一個理論上的最大值,該最大值可以透過將單次迭代的指令數除以*區塊倒數吞吐量*來計算。

欄位「每週期 uOps 數」是透過將模擬微指令總數除以週期總數來計算的。Dispatch Width 與此欄位之間的差異表示效能問題。在沒有迴圈進位數據相依性的情況下,觀察到的「每週期 uOps 數」應趨向於一個理論上的最大吞吐量,該吞吐量可以透過將單次迭代的 uOps 數除以*區塊倒數吞吐量*來計算。

欄位*每週期 uOps 數*的上限為分派寬度。這是因為分派寬度限制了分派群組的最大大小。IPC 和「每週期 uOps 數」都受到硬體平行度的限制。硬體資源的可用性會影響資源壓力分佈,並限制每個週期可以平行執行的指令數量。Dispatch Width 與理論上的最大每週期 uOp 數(透過將單次迭代的 uOps 數除以*區塊倒數吞吐量*來計算)之間的差異表示由硬體資源不足引起的效能瓶頸。一般來說,區塊倒數吞吐量越低越好。

在此範例中,每次迭代的 uOps 數/區塊倒數吞吐量 為 1.50。由於沒有迴圈進位相依性,因此當迭代次數趨於無限時,預計觀察到的*每週期 uOps 數*將接近 1.50。Dispatch Width (2.00) 與理論上的最大 uOp 吞吐量 (1.50) 之間的差異表示由硬體資源不足引起的效能瓶頸,而*資源壓力視圖*可以幫助識別有問題的資源使用情況。

報告的第二部分是「指令資訊檢視」。它顯示了序列中每條指令的延遲和倒數吞吐量。它還報告與微指令數量和指令碼屬性(即「MayLoad」、「MayStore」和「HasSideEffects」)相關的額外資訊。

欄位「RThroughput」是指令吞吐量的倒數。吞吐量計算為在沒有運算元依賴關係的情況下,每個時脈週期可以執行的相同類型指令的最大數量。在此範例中,向量浮點乘法的倒數吞吐量為 1 個週期/指令。這是因為 FP 乘法器 JFPM 僅從管線 JFPU1 中可用。

當指定了標記「-show-encoding」時,指令編碼會顯示在指令資訊檢視中。

以下是點積核心「-show-encoding」輸出的範例

Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[7]: Encoding Size

[1]    [2]    [3]    [4]    [5]    [6]    [7]    Encodings:                    Instructions:
 1      2     1.00                         4     c5 f0 59 d0                   vmulps %xmm0, %xmm1, %xmm2
 1      4     1.00                         4     c5 eb 7c da                   vhaddps        %xmm2, %xmm2, %xmm3
 1      4     1.00                         4     c5 e3 7c e3                   vhaddps        %xmm3, %xmm3, %xmm4

編碼大小」欄顯示指令的大小(以位元組為單位)。「編碼」欄顯示實際的指令編碼(十六進位制位元組序列)。

第三部分是「資源壓力檢視」。此檢視報告目標上可用的每個處理器資源單元,指令每次迭代所消耗的平均資源週期數。資訊以兩個表格的形式呈現。第一個表格報告每次迭代平均消耗的資源週期數。第二個表格將資源週期與序列中的機器指令相關聯。例如,指令「vmulps」的每次迭代始終在資源單元 [6](JFPU1 - 浮點管線 #1)上執行,每次迭代平均消耗 1 個資源週期。請注意,在 AMD Jaguar 上,向量浮點乘法只能發送到管線 JFPU1,而水平浮點加法只能發送到管線 JFPU0。

資源壓力檢視有助於識別由特定硬體資源的高使用率引起的瓶頸。通常應避免資源壓力主要集中在少數資源上的情況。理想情況下,壓力應均勻分佈在多個資源之間。

時間軸檢視

時間軸檢視會產生每個指令透過指令管線狀態轉換的詳細報告。此檢視由命令列選項「-timeline」啟用。當指令在管線的不同階段轉換時,它們的狀態會顯示在檢視報告中。這些狀態由以下字元表示

  • D:指令已分派。

  • e:指令正在執行。

  • E:指令已執行。

  • R:指令已結束。

  • =:指令已分派,正在等待執行。

  • -:指令已執行,正在等待結束。

以下是位於「test/tools/llvm-mca/X86/BtVer2/dot-product.s」中點積範例子集的時間軸檢視,並使用以下命令由「llvm-mca」處理

$ llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=btver2 -iterations=3 -timeline dot-product.s
Timeline view:
                    012345
Index     0123456789

[0,0]     DeeER.    .    .   vmulps   %xmm0, %xmm1, %xmm2
[0,1]     D==eeeER  .    .   vhaddps  %xmm2, %xmm2, %xmm3
[0,2]     .D====eeeER    .   vhaddps  %xmm3, %xmm3, %xmm4
[1,0]     .DeeE-----R    .   vmulps   %xmm0, %xmm1, %xmm2
[1,1]     . D=eeeE---R   .   vhaddps  %xmm2, %xmm2, %xmm3
[1,2]     . D====eeeER   .   vhaddps  %xmm3, %xmm3, %xmm4
[2,0]     .  DeeE-----R  .   vmulps   %xmm0, %xmm1, %xmm2
[2,1]     .  D====eeeER  .   vhaddps  %xmm2, %xmm2, %xmm3
[2,2]     .   D======eeeER   vhaddps  %xmm3, %xmm3, %xmm4


Average Wait times (based on the timeline view):
[0]: Executions
[1]: Average time spent waiting in a scheduler's queue
[2]: Average time spent waiting in a scheduler's queue while ready
[3]: Average time elapsed from WB until retire stage

      [0]    [1]    [2]    [3]
0.     3     1.0    1.0    3.3       vmulps   %xmm0, %xmm1, %xmm2
1.     3     3.3    0.7    1.0       vhaddps  %xmm2, %xmm2, %xmm3
2.     3     5.7    0.0    0.0       vhaddps  %xmm3, %xmm3, %xmm4
       3     3.3    0.5    1.4       <total>

時間軸檢視很有趣,因為它顯示了執行期間的指令狀態變化。它還讓您了解工具如何處理在目標上執行的指令,以及如何計算它們的時間資訊。

時間軸檢視由兩個表格組成。第一個表格顯示指令隨時間推移而變化的狀態(以週期為單位);第二個表格(名為「平均等待時間」)報告有用的時序統計數據,這應該有助於診斷由長時間數據依賴性和硬體資源的次優使用引起的效能瓶頸。

時間軸檢視中的指令由一對索引標識,其中第一個索引標識迭代,第二個索引是指令索引(即它在代碼序列中出現的位置)。由於此範例是使用 3 次迭代生成的:「-iterations=3」,因此迭代索引的範圍從 0-2(含)。

除了第一列和最後一列之外,其餘各列均以週期為單位。 週期從 0 開始依序編號。

從上面的範例輸出中,我們可以知道以下資訊

  • 指令 [1,0] 在週期 1 被分派。

  • 指令 [1,0] 在週期 2 開始執行。

  • 指令 [1,0] 在週期 4 到達寫回階段。

  • 指令 [1,0] 在週期 10 被捨棄。

指令 [1,0](即,迭代 #1 的 vmulps)不需要在排程器的佇列中等待運算元變為可用。 在 vmulps 被分派時,運算元已經可用,並且管線 JFPU1 已準備好服務另一條指令。 因此,該指令可以立即在 JFPU1 管線上發出。 指令只在排程器的佇列中花費了 1 個週期,就證明了這一點。

寫回階段和捨棄事件之間有 5 個週期的間隔。 這是因為指令必須按程式順序捨棄,所以 [1,0] 必須等待 [0,2] 首先被捨棄(即,它必須等待到週期 10)。

在此範例中,所有指令都在 RAW(寫入後讀取)依賴鏈中。 由 vmulps 寫入的暫存器 %xmm2 立即被第一個 vhaddps 使用,而由第一個 vhaddps 寫入的暫存器 %xmm3 被第二個 vhaddps 使用。 長資料依賴性會對 ILP(指令級平行性)產生負面影響。

在點積範例中,不同迭代的指令會引入反依賴性。 但是,這些依賴性可以在暫存器重新命名階段移除(以配置暫存器別名為代價,因此會消耗實體暫存器)。

表格「平均等待時間」有助於診斷由長延遲指令和可能限制 ILP 的長資料依賴性所導致的效能問題。 最後一行 <total> 顯示所有測量指令的整體平均值。 請注意,llvm-mca 預設會假設分派事件和發布事件之間至少有 1 個週期。

當效能受到資料依賴性和/或長延遲指令的限制時,與在排程器佇列中花費的總週期數相比,「就緒」狀態下花費的週期數預計會非常少。 兩個計數器之間的差異可以很好地指示資料依賴性對指令執行的影響程度。 當效能主要受限於硬體資源不足時,兩個計數器之間的差異很小。 但是,在佇列中花費的週期數往往會更大(即,超過 1-3 個週期),尤其是在與其他低延遲指令相比時。

瓶頸分析

命令列選項 -bottleneck-analysis 可以啟用效能瓶頸分析。

此分析的成本可能很高。 它會嘗試將後端壓力的增加(由管線資源壓力和資料依賴性引起)與動態分派停頓關聯起來。

以下是由 llvm-mca 在 btver2 上針對點積範例的 500 次迭代產生的 -bottleneck-analysis 輸出範例。

Cycles with backend pressure increase [ 48.07% ]
Throughput Bottlenecks:
  Resource Pressure       [ 47.77% ]
  - JFPA  [ 47.77% ]
  - JFPU0  [ 47.77% ]
  Data Dependencies:      [ 0.30% ]
  - Register Dependencies [ 0.30% ]
  - Memory Dependencies   [ 0.00% ]

Critical sequence based on the simulation:

              Instruction                         Dependency Information
 +----< 2.    vhaddps %xmm3, %xmm3, %xmm4
 |
 |    < loop carried >
 |
 |      0.    vmulps  %xmm0, %xmm1, %xmm2
 +----> 1.    vhaddps %xmm2, %xmm2, %xmm3         ## RESOURCE interference:  JFPA [ probability: 74% ]
 +----> 2.    vhaddps %xmm3, %xmm3, %xmm4         ## REGISTER dependency:  %xmm3
 |
 |    < loop carried >
 |
 +----> 1.    vhaddps %xmm2, %xmm2, %xmm3         ## RESOURCE interference:  JFPA [ probability: 74% ]

根據分析,輸送量受到資源壓力的限制,而不是資料依賴性的限制。 分析觀察到在 48.07% 的模擬執行過程中,後端壓力有所增加。 幾乎所有這些壓力增加事件都是由處理器資源 JFPA/JFPU0 上的爭用引起的。

關鍵序列是根據模擬得出的最昂貴的指令序列。 它會加上註釋,以提供有關關鍵暫存器依賴性和指令之間資源干擾的其他資訊。

預計關鍵序列中的指令會顯著影響效能。 根據建構,此分析的準確性很大程度上取決於模擬,並且(一如既往)取決於 llvm 中處理器模型的品質。

目前不支援對具有順序後端的處理器進行瓶頸分析。

用於進一步診斷效能問題的額外統計數據

命令列選項 -all-stats 可啟用額外的統計數據和效能計數器,用於分配邏輯、重新排序緩衝區、退出控制單元和寄存器檔案。

以下是由 llvm-mca 針對前幾節中討論的點積範例的 300 次迭代生成的 -all-stats 輸出的範例。

Dynamic Dispatch Stall Cycles:
RAT     - Register unavailable:                      0
RCU     - Retire tokens unavailable:                 0
SCHEDQ  - Scheduler full:                            272  (44.6%)
LQ      - Load queue full:                           0
SQ      - Store queue full:                          0
GROUP   - Static restrictions on the dispatch group: 0


Dispatch Logic - number of cycles where we saw N micro opcodes dispatched:
[# dispatched], [# cycles]
 0,              24  (3.9%)
 1,              272  (44.6%)
 2,              314  (51.5%)


Schedulers - number of cycles where we saw N micro opcodes issued:
[# issued], [# cycles]
 0,          7  (1.1%)
 1,          306  (50.2%)
 2,          297  (48.7%)

Scheduler's queue usage:
[1] Resource name.
[2] Average number of used buffer entries.
[3] Maximum number of used buffer entries.
[4] Total number of buffer entries.

 [1]            [2]        [3]        [4]
JALU01           0          0          20
JFPU01           17         18         18
JLSAGU           0          0          12


Retire Control Unit - number of cycles where we saw N instructions retired:
[# retired], [# cycles]
 0,           109  (17.9%)
 1,           102  (16.7%)
 2,           399  (65.4%)

Total ROB Entries:                64
Max Used ROB Entries:             35  ( 54.7% )
Average Used ROB Entries per cy:  32  ( 50.0% )


Register File statistics:
Total number of mappings created:    900
Max number of mappings used:         35

*  Register File #1 -- JFpuPRF:
   Number of physical registers:     72
   Total number of mappings created: 900
   Max number of mappings used:      35

*  Register File #2 -- JIntegerPRF:
   Number of physical registers:     64
   Total number of mappings created: 0
   Max number of mappings used:      0

如果我們查看「動態分配停頓週期」表格,我們會看到 SCHEDQ 的計數器報告為 272 個週期。每當分配邏輯因為排程器的佇列已滿而無法分配完整的群組時,此計數器就會遞增。

查看「分配邏輯」表格,我們會看到管線在 51.5% 的時間內只能分配兩個微指令。分配群組在 44.6% 的週期內被限制為一個微指令,這對應於 272 個週期。分配統計數據可透過使用命令選項 -all-stats-dispatch-stats 顯示。

下一個表格「排程器」呈現了一個直方圖,顯示了一個計數,代表在特定週期數上發出的微指令數量。在這種情況下,在 610 個模擬週期中,單個指令發出了 306 次 (50.2%),並且有 7 個週期沒有發出指令。

「排程器的佇列使用情況」表格顯示了在執行階段使用的緩衝區項目(即,排程器佇列項目)的平均數和最大數。資源 JFPU01 達到其最大值(18 個佇列項目中的 18 個)。請注意,AMD Jaguar 實作了三個排程器

  • JALU01 - 用於 ALU 指令的排程器。

  • JFPU01 - 用於浮點運算的排程器。

  • JLSAGU - 用於位址生成的排程器。

點積是由三個浮點指令組成的核心(一個向量乘法,後跟兩個水平加法)。這就解釋了為什麼只有浮點排程器看起來被使用。

完整的排程器佇列可能是由資料依賴鏈或硬體資源的次優使用造成的。有時,可以透過使用消耗不同排程器資源的不同指令重寫核心來減輕資源壓力。具有較小佇列的排程器對由長資料依賴關係的存在引起的瓶頸的抵抗力較差。排程器統計數據可透過使用命令選項 -all-stats-scheduler-stats 顯示。

下一個表格「退出控制單元」呈現了一個直方圖,顯示了一個計數,代表在特定週期數上退出的指令數量。在這種情況下,在 610 個模擬週期中,兩個指令在同一個週期內退出了 399 次 (65.4%),並且有 109 個週期沒有指令退出。退出統計數據可透過使用命令選項 -all-stats-retire-stats 顯示。

最後一個表格顯示的是「*暫存器檔案統計資訊*」。管線所使用的每個實體暫存器檔案 (PRF) 都會顯示在此表格中。以 AMD Jaguar 為例,這裡有兩個暫存器檔案,一個用於浮點數暫存器 (JFpuPRF),另一個用於整數暫存器 (JIntegerPRF)。表格顯示在處理的 900 個指令中,建立了 900 個映射。由於此點積範例僅使用浮點數暫存器,因此 JFPuPRF 負責建立 900 個映射。但是,我們可以看到,在任何給定時間,管線最多僅使用了 72 個可用暫存器插槽中的 35 個。我們可以得出結論,浮點數 PRF 是範例中唯一使用的暫存器檔案,並且它從未受到資源限制。暫存器檔案統計資訊可以使用命令選項 -all-stats-register-file-stats 顯示。

在此範例中,我們可以得出結論,IPC 主要受資料相依性限制,而不是受資源壓力限制。

指令流程

本節說明通過 llvm-mca 預設管線的指令流程,以及過程中涉及的功能單元。

預設管線會實作以下用於處理指令的階段順序。

  • 分派(指令會分派到排程器)。

  • 發佈(指令會發佈到處理器管線)。

  • 寫回(指令會執行,並且結果會寫回)。

  • 退出(指令會退出;寫入會在架構上提交)。

順序執行管線會實作以下階段順序

  • InOrderIssue(指令會發佈到處理器管線)。

  • 退出(指令會退出;寫入會在架構上提交)。

llvm-mca 假設在模擬開始之前,所有指令都已解碼並放入佇列中。因此,不會模擬指令提取和解碼階段。不會診斷前端的效能瓶頸。此外,llvm-mca 不會模擬分支預測。

指令分派

在分派階段,會從已解碼指令的佇列中按程式順序挑選指令,並分組分派到模擬的硬體排程器。

分派群組的大小取決於模擬硬體資源的可用性。處理器分派寬度預設為 LLVM 排程模型中 IssueWidth 的值。

在以下情況下,可以分派指令:

  • 分派群組的大小小於處理器的分派寬度。

  • 重新排序緩衝區中有足夠的項目。

  • 有足夠的實體暫存器可以進行暫存器重新命名。

  • 排程器未滿。

排程模型可以選擇性地指定處理器上可用的暫存器檔案。llvm-mca 使用該資訊來初始化暫存器檔案描述元。使用者可以使用命令選項 -register-file-size 來限制全域可用於暫存器重新命名的實體暫存器數量。此選項的值為零表示「*無限制*」。透過瞭解有多少暫存器可用於重新命名,該工具可以預測因缺少實體暫存器而導致的分派停頓。

指令消耗的重新排序緩衝區項目數量取決於目標排程模型為該指令指定的微運算碼數量。重新排序緩衝區負責追蹤「執行中」指令的進度,並按程式順序將其退出。重新排序緩衝區中的項目數量預設為目標排程模型中 MicroOpBufferSize 欄位所指定的值。

分派給排程器的指令會佔用排程器緩衝區的項目。 **llvm-mca** 會查詢排程模型,以確定指令所佔用的緩衝資源集。緩衝資源的處理方式與排程器資源相同。

指令發射 (Instruction Issue)

每個處理器排程器都實作了一個指令緩衝區。指令必須在排程器的緩衝區中等待,直到輸入暫存器運算元變為可用。只有在那時,指令才會變為可執行狀態,並且可以(可能無序地)發射執行。指令延遲由 **llvm-mca** 在排程模型的幫助下計算。

**llvm-mca** 的排程器旨在模擬多個處理器排程器。排程器負責跟踪數據依賴關係,並動態選擇指令所佔用的處理器資源。它將處理器資源單元和資源組的管理委託給資源管理器。資源管理器負責選擇指令所佔用的資源單元。例如,如果一條指令佔用一個資源組的 1 個週期,則資源管理器會從該組中選擇一個可用的單元;默認情況下,資源管理器使用循環選擇器來確保資源使用在組的所有單元之間均勻分佈。

**llvm-mca** 的排程器在內部將指令分為三組:

  • 等待集 (WaitSet):一組運算元尚未準備好的指令。

  • 就緒集 (ReadySet):一組準備執行的指令。

  • 發射集 (IssuedSet):一組正在執行的指令。

根據運算元的可用性,分派給排程器的指令將被放入等待集或就緒集中。

每個週期,排程器都會檢查指令是否可以從等待集移動到就緒集,以及就緒集中的指令是否可以發射到底層流水線。該算法優先考慮較舊的指令,而不是較新的指令。

寫回和退休階段 (Write-Back and Retire Stage)

發射的指令從就緒集移動到發射集。在那裡,指令會一直等待,直到它們到達寫回階段。此時,它們會從佇列中移除,並通知退休控制單元。

當指令執行完畢時,退休控制單元會將指令標記為「準備退休」。

指令按程序順序退休。暫存器文件會收到退休通知,以便它可以釋放在暫存器重命名階段為指令分配的物理暫存器。

載入/儲存單元和記憶體一致性模型 (Load/Store Unit and Memory Consistency Model)

為了模擬記憶體操作的無序執行,**llvm-mca** 使用模擬載入/儲存單元 (LSUnit) 來模擬載入和儲存的推測執行。

每個載入(或儲存)操作都會佔用載入(或儲存)佇列中的一個條目。使用者可以指定標誌 -lqueue-squeue 來分別限制載入和儲存佇列中的條目數。預設情況下,佇列是無界的。

LSUnit 為記憶體載入和儲存操作實現了寬鬆的一致性模型。規則如下:

  1. 只有在兩個載入操作之間沒有 intervening 儲存操作或屏障的情況下,才允許較新的載入操作超過較舊的載入操作。

  2. 如果載入操作與儲存操作沒有別名,則允許較新的載入操作超過較舊的儲存操作。

  3. 不允許較新的儲存操作超過較舊的儲存操作。

  4. 不允許較新的儲存操作超過較舊的載入操作。

預設情況下,LSUnit 樂觀地假設載入不會與儲存操作發生別名 (-noalias=true)。在這個假設下,較新的載入操作總是允許超越較舊的儲存操作。基本上,LSUnit 不會嘗試執行任何別名分析來預測載入和儲存何時不會彼此發生別名。

請注意,在寫入合併記憶體的情況下,規則 3 可以放寬以允許重新排序非別名儲存操作。話雖如此,目前沒有辦法進一步放寬記憶體模型 (-noalias 是唯一的選項)。基本上,沒有選項可以指定不同的記憶體類型(例如,回寫、寫入合併、寫入透通等),因此無法削弱或加強記憶體模型。

其他限制如下:

  • LSUnit 不知道何時可能會發生儲存到載入的轉發。

  • LSUnit 不了解任何有關快取層次結構和記憶體類型的資訊。

  • LSUnit 不知道如何識別序列化操作和記憶體柵欄。

LSUnit 不會嘗試預測載入或儲存是否命中或錯過 L1 快取。它只知道指令是否“MayLoad”和/或“MayStore”。對於載入,排程模型提供了一個“樂觀的”載入到使用延遲(通常與 L1D 命中時的載入到使用延遲相匹配)。

llvm-mca 本身並不知道序列化操作或類似記憶體屏障的指令。LSUnit 過去保守地使用指令的“MayLoad”、“MayStore”和未建模的副作用標誌來確定指令是否應被視為記憶體屏障。這在一般情況下是不準確的,並且已經更改,因此現在每個指令都有一個 IsAStoreBarrier 和 IsALoadBarrier 標誌。這些標誌是 mca 特定的,並且對每個指令預設為 false。如果任何指令應該設置這些標誌中的任何一個,則應該在目標的 InstrPostProcess 類中完成。例如,請查看 llvm/lib/Target/X86/MCA/X86CustomBehaviour.cpp 中的 X86InstrPostProcess::postProcessInstruction 方法。

載入/儲存屏障會佔用載入/儲存佇列中的一個條目。載入/儲存屏障強制執行載入/儲存的順序。較新的載入操作無法超越載入屏障。此外,較新的儲存操作無法超越儲存屏障。較新的載入操作必須等待記憶體/載入屏障執行完畢。當載入/儲存屏障成為載入/儲存佇列中最舊的條目時,它就被“執行”了。這也意味著,根據構造,所有較舊的載入/儲存操作都已執行完畢。

總之,載入/儲存一致性規則的完整集合如下:

  1. 儲存操作不能超越先前的儲存操作。

  2. 儲存操作不能超越先前的載入操作(無論 -noalias 設置為何)。

  3. 儲存操作必須等待較舊的儲存屏障完全執行完畢。

  4. 載入操作可以超越先前的載入操作。

  5. 除非設置了 -noalias,否則載入操作不能超越先前的儲存操作。

  6. 載入操作必須等待較舊的載入屏障完全執行完畢。

按序發射和執行

按序處理器被建模為單個 InOrderIssueStage 階段。它繞過分派、排程器和載入/儲存單元。一旦指令的操作數寄存器可用且資源需求得到滿足,指令就會立即發射。根據 LLVM 排程模型中 IssueWidth 參數的值,可以在一個週期內發射多條指令。

指令一旦發出,就會被移至 IssuedInst 集合,直到準備好退休為止。 llvm-mca 確保寫入操作按順序提交。但是,如果指令的至少一個寫入操作的 RetireOOO 屬性為 true,則允許該指令亂序提交寫入操作並退休。

自定義行為

由於某些指令在其排程模型中無法完美表達,因此 llvm-mca 並非總能完美模擬它們。然而,修改排程模型並非總是可行的選擇(可能是因為指令的建模有意不正確,或者指令的行為過於複雜)。在這些情況下,可以使用 CustomBehaviour 類別來強制執行正確的指令建模(通常是透過自定義資料依賴關係並偵測 llvm-mca 無法知道的危害)。

llvm-mca 附帶了一個通用和多個目標特定的 CustomBehaviour 類別。如果使用了 -disable-cb 旗標,或者該目標不存在目標特定的 CustomBehaviour 類別,則將使用通用類別。(通用類別不做任何事情。)目前,CustomBehaviour 類別只是順序管道的一部分,但未來計劃將其添加到亂序管道中。

CustomBehaviour 的主要方法是 checkCustomHazard(),它使用當前指令和管道中仍在執行的所有指令的列表來確定是否應該分派當前指令。作為輸出,該方法返回一個整數,表示當前指令必須停止的週期數(如果您不知道確切的數字,則這可能是一個低估值,值 0 表示不停止)。

如果您想為尚未擁有 CustomBehaviour 類別的目標添加一個類別,請參閱現有的實現以了解如何進行設置。這些類別是在目標特定的後端中實現的(例如 /llvm/lib/Target/AMDGPU/MCA/),以便它們可以訪問後端符號。

指令管理器

在某些架構上,某些指令的排程資訊並未包含識別最精確排程類別所需的所有資訊。例如,可能影響排程的資料可能儲存在 CSR 暫存器中。

其中一個例子是在 RISCV 上,暫存器中的值(例如 vtypevl)會改變向量指令的排程行為。由於 MCA 不追蹤暫存器中的值,因此可以使用指令註釋來指定這些值。

InstrumentManager 的主要函數是 getSchedClassID(),它可以訪問 MCInst 和該 MCInst 處於活動狀態的所有指令。此函數可以使用指令來覆蓋 MCInst 的排程類別。

在 RISCV 上,getSchedClassID() 使用包含 LMUL 資訊的指令註釋,將向量指令和活動的 LMUL 映射到描述該基礎指令和活動的 LMUL 的偽指令的排程類別。

自定義視圖

llvm-mca 附帶了多個視圖,例如時間軸視圖和摘要視圖。這些視圖是通用的,可以與大多數(如果不是全部)目標一起使用。如果您想為 llvm-mca 添加新的視圖,並且它不需要任何尚未通過 MC 層類別(MCSubtargetInfo、MCInstrInfo 等)公開的後端功能,請將其添加到 /tools/llvm-mca/View/ 目錄中。但是,如果您的新視圖是目標特定的,並且需要未公開的後端符號或功能,則可以在 /lib/Target/<目標名稱>/MCA/ 目錄中定義它。

若要啟用此目標專用的視圖,您必須使用此目標的 CustomBehaviour 類別來覆寫 CustomBehaviour::getViews() 方法。根據您希望視圖出現在輸出的位置,這些方法有 3 種變體:getStartViews()getPostInstrInfoViews()getEndViews()。這些方法會返回一個視圖向量,因此您需要返回一個包含目標所有目標專用視圖的向量。

由於這些目標專用(以及後端相依)視圖需要 CustomBehaviour::getViews() 變體,因此如果使用了 -disable-cb 旗標,這些視圖將不會被啟用。

啟用這些自訂視圖不會影響非自訂(通用)視圖。繼續使用常用的命令列參數來啟用/停用這些視圖。