llvm-mca - LLVM 機器碼分析器

概要

llvm-mca [選項] [輸入]

描述

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

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

此工具的主要目標不僅是預測程式碼在目標平台上運行的效能,還有助於診斷潛在的效能問題。

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

例如,您可以使用 clang 編譯程式碼,輸出組譯碼,並將其直接管道輸入到 llvm-mca 進行分析

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

或用於 Intel 語法

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

llvm-mca 通過輸入開頭是否存在 .intel_syntax 指令來偵測 Intel 語法。預設情況下,其輸出語法與其輸入的語法相符。)

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

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

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

選項

如果 input 是 “-” 或省略,則 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=<store queue size>

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

-timeline

啟用時間軸視圖。

-timeline-max-iterations=<iterations>

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

-timeline-max-cycles=<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

上面的範例中的程式碼定義了一個名為 “A simple example” 的區域,其中包含單個指令。請注意,區域名稱不必在 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 選項以發射最佳化報告 也可以幫助偵測遺漏的最佳化。

儀器區域

儀器區域描述了由特殊的 LLVM-MCA 註解指令保護的組譯碼區域。

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

其中 INSTRUMENT_TYPE 是目標定義的類型,並期望使用 data

以子字串 LLVM-MCA-<INSTRUMENT_TYPE> 開頭的註解將資料帶入 llvm-mca 的範圍,以便在其分析中用於所有後續指令。

如果在指令列表中稍後找到具有相同 INSTRUMENT_TYPE 的註解,則原始儀器區域將自動結束,並且將開始新的儀器區域。

如果存在包含不同 INSTRUMENT_TYPE 的註解,則兩個資料集仍然可用。與分析區域相反,儀器區域不需要註解來結束區域。

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

只有當指令 (MCInst) 的位置在範圍 [R.RangeStart, R.RangeEnd] 內時,才會將指令添加到儀器區域 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 數區塊反向吞吐量 (Block RThroughput)。

欄位派送寬度是在每個模擬週期中派送到亂序後端的微指令的最大數量。對於具有依序後端的處理器,派送寬度是在每個模擬週期中發射到後端的微指令的最大數量。

IPC 的計算方法是將模擬的指令總數除以週期的總數。

欄位區塊反向吞吐量是區塊吞吐量的倒數。區塊吞吐量是一個理論量,計算為在沒有迴圈攜帶相依性的情況下,每個模擬時脈週期可以執行的最大區塊(即迭代)數。區塊吞吐量受到派送率和硬體資源可用性的上限限制。

在沒有迴圈攜帶資料相依性的情況下,觀察到的 IPC 趨於理論最大值,該理論最大值可以通過將單次迭代的指令數除以 區塊反向吞吐量 來計算。

欄位“每週期 uOps 數”的計算方法是將模擬的微指令總數除以週期的總數。派送寬度與此欄位之間的差異是效能問題的指標。在沒有迴圈攜帶資料相依性的情況下,觀察到的“每週期 uOps 數”應趨於理論最大吞吐量,該理論最大吞吐量可以通過將單次迭代的 uOps 數除以 區塊反向吞吐量 來計算。

欄位每週期 uOps 數受到派送寬度的上限限制。這是因為派送寬度限制了派送群組的最大大小。IPC 和“每週期 uOps 數”都受到硬體平行量的限制。硬體資源的可用性影響資源壓力分佈,並且限制了每個週期可以平行執行的指令數。派送寬度與理論最大 uOps 吞吐量(通過將單次迭代的 uOps 數除以 區塊反向吞吐量 計算得出)之間的差異是硬體資源缺乏導致的效能瓶頸的指標。一般來說,區塊反向吞吐量越低越好。

在此範例中,每次迭代的 uOps 數/區塊反向吞吐量 為 1.50。由於沒有迴圈攜帶相依性,因此當迭代次數趨於無窮大時,觀察到的 每週期 uOps 數 預計會接近 1.50。派送寬度 (2.00) 與理論最大 uOp 吞吐量 (1.50) 之間的差異是硬體資源缺乏導致的效能瓶頸的指標,而資源壓力視圖可以幫助識別有問題的資源使用情況。

報告的第二部分是 指令資訊視圖。它顯示序列中每個指令的延遲和反向吞吐量。它還報告與微指令數和運算碼屬性(即 “MayLoad”、“MayStore” 和 “HasSideEffects”)相關的額外資訊。

欄位反向吞吐量是指令吞吐量的倒數。吞吐量的計算方法是在沒有運算元相依性的情況下,每個時脈週期可以執行的相同類型指令的最大數量。在此範例中,向量浮點乘法的反向吞吐量為 1 週期/指令。這是因為 FP 乘法器 JFPM 僅可從 pipeline 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 - 浮點 pipeline #1)上執行,每個迭代平均消耗 1 個資源週期。請注意,在 AMD Jaguar 上,向量浮點乘法只能發射到 pipeline JFPU1,而水平浮點加法只能發射到 pipeline JFPU0。

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

時間軸視圖

時間軸視圖產生一個詳細的報告,說明每個指令通過指令 pipeline 的狀態轉換。此視圖通過命令列選項 -timeline 啟用。當指令通過 pipeline 的各個階段時,它們的狀態會顯示在視圖報告中。這些狀態由以下字元表示

  • 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 派送時,運算元已可用,並且 pipeline JFPU1 已準備好服務另一個指令。因此,指令可以立即在 JFPU1 pipeline 上發射。指令僅在排程器的佇列中花費 1 個週期就證明了這一點。

在寫回階段和退役事件之間存在 5 個週期的間隙。這是因為指令必須按程式順序退役,因此 [1,0] 必須等待 [0,2] 先退役(即,它必須等到週期 10)。

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

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

表格平均等待時間有助於診斷由長延遲指令和可能長的資料相依性引起的效能問題,這可能會限制 ILP。最後一行 <total> 顯示了所有測量指令的總體平均值。請注意,llvm-mca 預設情況下假設派送事件和發射事件之間至少有 1 個週期。

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

瓶頸分析

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

此分析可能很耗費資源。它試圖將後端壓力(由 pipeline 資源壓力和資料相依性引起)的增加與動態派送停滯相關聯。

以下是 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 命令列選項啟用派送邏輯、重新排序緩衝區、退役控制單元和暫存器檔案的額外統計資訊和效能計數器。

以下是 -all-stats 輸出的範例,該輸出由 llvm-mca 針對先前章節中討論的點積範例的 300 次迭代所產生。

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 的預設管線的流程,以及過程中涉及的功能單元。

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

  • 調度(指令被調度到調度器)。

  • 發射(指令被發射到處理器管線)。

  • 寫回(指令被執行,結果被寫回)。

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

循序管線實作以下階段序列

  • 循序發射(指令被發射到處理器管線)。

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

llvm-mca 假設指令在模擬開始之前都已被解碼並放置到佇列中。因此,未對指令提取和解碼階段進行建模。前端的效能瓶頸未被診斷出來。此外,llvm-mca 不對分支預測進行建模。

指令調度

在調度階段,指令按照程式順序從已解碼指令的佇列中選取,並以群組形式調度到模擬硬體調度器。

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

如果滿足以下條件,則可以調度指令

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

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

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

  • 調度器未滿。

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

指令消耗的重新排序緩衝區條目數量取決於目標調度模型為該指令指定的微操作碼數量。重新排序緩衝區負責追蹤「執行中」指令的進度,並按照程式順序將它們退役。重新排序緩衝區中的條目數量預設為目標調度模型中 MicroOpBufferSize 欄位指定的值。

調度到調度器的指令會消耗調度器緩衝區條目。llvm-mca 查詢調度模型以確定指令消耗的緩衝資源集。緩衝資源被視為調度器資源。

指令發射

每個處理器調度器都實作一個指令緩衝區。指令必須在調度器的緩衝區中等待,直到輸入暫存器運算元變為可用。只有在那個時候,指令才符合執行條件,並且可以(可能亂序地)發射以執行。llvm-mca 藉助調度模型來計算指令延遲。

llvm-mca 的調度器旨在模擬多個處理器調度器。調度器負責追蹤資料相依性,並動態選擇指令消耗的處理器資源。它將處理器資源單元和資源群組的管理委派給資源管理器。資源管理器負責選擇指令消耗的資源單元。例如,如果指令消耗資源群組的 1 個週期,則資源管理器從群組中選擇一個可用的單元;預設情況下,資源管理器使用循環選擇器來保證資源使用在群組的所有單元之間均勻分佈。

llvm-mca 的調度器在內部將指令分組為三個集合

  • WaitSet:一組運算元尚未就緒的指令。

  • ReadySet:一組已準備好執行的指令。

  • IssuedSet:一組正在執行的指令。

根據運算元的可用性,調度到調度器的指令要么放置在 WaitSet 中,要么放置在 ReadySet 中。

每個週期,調度器都會檢查指令是否可以從 WaitSet 移動到 ReadySet,以及是否可以將 ReadySet 中的指令發射到底層管線。該演算法優先考慮較舊的指令,而不是較新的指令。

寫回和退役階段

已發射的指令從 ReadySet 移動到 IssuedSet。在那裡,指令等待直到它們到達寫回階段。在那時,它們從佇列中移除,並且退役控制單元收到通知。

當指令被執行時,退役控制單元將指令標記為「準備好退役」。

指令按照程式順序退役。暫存器檔案收到退役通知,以便它可以釋放在暫存器重新命名階段為指令分配的實體暫存器。

載入/儲存單元和記憶體一致性模型

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

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

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

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

  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 類別中完成。例如,查看 X86InstrPostProcess::postProcessInstruction 方法,該方法位於 llvm/lib/Target/X86/MCA/X86CustomBehaviour.cpp 中。

載入/儲存屏障消耗載入/儲存佇列的一個條目。載入/儲存屏障強制執行載入/儲存的排序。較新的載入不能通過載入屏障。此外,較新的儲存不能通過儲存屏障。較新的載入必須等待記憶體/載入屏障執行。當載入/儲存屏障成為載入/儲存佇列中最舊的條目時,它就會被「執行」。這也意味著,根據構造,所有較舊的載入/儲存都已執行。

總之,完整的載入/儲存一致性規則是

  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 上,包含 LMUL 資訊的儀器註解被 getSchedClassID() 用於將向量指令和啟用的 LMUL 映射到描述該基本指令和啟用的 LMUL 的偽指令的調度類別。

自訂檢視

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

要啟用此目標特定的檢視,您必須使用此目標的 CustomBehaviour 類別來覆寫 CustomBehaviour::getViews() 方法。這些方法有 3 個變體,具體取決於您希望檢視在輸出中出現的位置:getStartViews()getPostInstrInfoViews()getEndViews()。這些方法傳回檢視的向量,因此您將希望傳回一個向量,其中包含目標的所有目標特定檢視。

由於這些目標特定(且後端相關)的檢視需要 CustomBehaviour::getViews() 變體,因此如果使用了 -disable-cb 旗標,則不會啟用這些檢視。

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