[英](How) can I predict the runtime of a code snippet using LLVM Machine Code Analyzer?
我使用llvm-mca來計算一堆代碼的總周期 ,認為它們會預測它的運行時間。 但是,動態測量運行時幾乎沒有相關性。 那么: 為什么由llvm-mca計算的總周期不能准確預測運行時? 我可以用llvm-mca以更好的方式預測運行時嗎?
細節:
我想知道不同類型的begin
(和end
)迭代器的以下代碼的運行時, startValue
為0.0
或0ULL
:
std::accumulate(begin, end, starValue)
為了預測運行時,我使用Compiler Explorer( https://godbolt.org/z/5HDzSF )及其LLVM機器碼分析器(llvm-mca)插件,因為llvm-mca是“一個使用可用信息的性能分析工具”在LLVM(例如調度模型)中靜態測量性能“。 我使用了以下代碼:
using vec_t = std::vector<double>;
vec_t generateRandomVector(vec_t::size_type size)
{
std::random_device rnd_device;
std::mt19937 mersenne_engine {rnd_device()};
std::uniform_real_distribution dist{0.0,1.1};
auto gen = [&dist, &mersenne_engine](){
return dist(mersenne_engine);
};
vec_t result(size);
std::generate(result.begin(), result.end(), gen);
return result;
}
double start()
{
vec_t vec = generateRandomVector(30000000);
vec_t::iterator vectorBegin = vec.begin();
vec_t::iterator vectorEnd = vec.end();
__asm volatile("# LLVM-MCA-BEGIN stopwatchedAccumulate");
double result = std::accumulate(vectorBegin, vectorEnd, 0.0);
__asm volatile("# LLVM-MCA-END");
return result;
}
但是,我看到llvm-mca計算機的總周期與運行相應的std :: accumulate的掛鍾時間之間沒有相關性。 例如,在上面的代碼中,Total Cycles是2806,運行時間是14ms。 當我切換到startValue 0ULL
,Total Cycles為2357,但運行時為117ms。
TL:DR:LLVM-MCA分析了這些注釋之間的整個代碼塊,就好像它是循環的主體一樣 ,並向您展示了所有這些指令的100次迭代的循環計數。
但是除了實際(微小)循環之外,大多數指令都是循環設置,而循環之后的SIMD水平和實際上只運行一次。 (這就是為什么循環計數是數千,而不是400 = 100時間,對於具有double
累加器的0.0
版本,Skylake上vaddpd
的4周期延遲。)
如果取消選中Godbolt編譯器資源管理器中的“//”框,或修改asm語句以添加"nop # LLVM-MCA-END"
類的"nop # LLVM-MCA-END"
,您將能夠在asm窗口中找到這些行並查看LLVM-MCA正在關注它的“循環”。
LLVM MCA模擬指定的匯編指令序列,並計算在指定目標體系結構上每次迭代執行時估計要執行的周期數。 LLVM MCA進行了許多簡化,例如(在我的腦海中):( 1)它假定所有條件分支都通過,(2)它假定所有內存訪問都是回寫內存類型並且所有內存訪問都是L1高速緩存,(3)它假設前端工作最佳,(4) call
指令不被跟隨到被調用的程序,它們只是通過。 還有其他假設我現在不記得了。
從本質上講,LLVM MCA(如Intel IACA)僅適用於后端計算綁定的簡單循環。 在IACA中,雖然支持大多數指令,但未對一些指令進行詳細建模。 作為示例,假設預取指令僅消耗微體系結構資源,但基本上消耗零延遲並且對存儲器層次結構的狀態沒有影響。 但在我看來,MCA完全忽略了這些指示。 無論如何,這與你的問題並不特別相關。
現在回到你的代碼。 在您提供的Compiler Explorer鏈接中,您沒有將任何選項傳遞給LLVM MCA。 因此,默認目標體系結構生效,即運行該工具的任何體系結構。 這恰好是SKX。 您提到的周期總數是針對SKX的,但是您是否在SKX上運行代碼尚不清楚。 您應該使用-mcpu
選項指定體系結構。 這與您傳遞給gcc的-march
無關。 另請注意,將核心周期與毫秒進行比較沒有意義。 您可以使用RDTSC
指令來測量核心周期的執行時間。
注意編譯器如何內聯對std::accumulate
的調用。 顯然,此代碼從匯編行405開始, std::accumulate
的最后一條指令在第444行,總共38條指令。 LLVM MCA估計與實際性能不匹配的原因現在已經變得清晰。 該工具假設所有這些指令都在循環中執行以進行大量迭代。 顯然事實並非如此。 420-424只有一個循環:
.L75:
vaddpd ymm0, ymm0, YMMWORD PTR [rax]
add rax, 32
cmp rax, rcx
jne .L75
只有此代碼應該是MCA的輸入。 在源代碼級別,實際上沒有辦法告訴MCA僅分析此代碼。 您必須手動內聯std::accumulate
並將LLVM-MCA-BEGIN
和LLVM-MCA-END
標記放在其中的某個位置。
當將0ULL
而不是0.0
傳遞給std::accumulate
,LLVM MCA的輸入將從匯編指令402開始並在441結束。請注意,分析中將完全省略MCA不支持的任何指令(例如vcvtsi2sdq
)。 實際在循環中的代碼部分是:
.L78:
vxorpd xmm0, xmm0, xmm0
vcvtsi2sdq xmm0, xmm0, rax
test rax, rax
jns .L75
mov rcx, rax
and eax, 1
vxorpd xmm0, xmm0, xmm0
shr rcx
or rcx, rax
vcvtsi2sdq xmm0, xmm0, rcx
vaddsd xmm0, xmm0, xmm0
.L75:
vaddsd xmm0, xmm0, QWORD PTR [rdx]
vcomisd xmm0, xmm1
vcvttsd2si rax, xmm0
jb .L77
vsubsd xmm0, xmm0, xmm1
vcvttsd2si rax, xmm0
xor rax, rdi
.L77:
add rdx, 8
cmp rsi, rdx
jne .L78
請注意,在目標地址位於塊中某處的代碼中存在條件跳轉jns
。 MCA將假設跳躍將會失敗。 如果在實際運行的代碼中不是這種情況,MCA將不必要地增加7條指令的開銷。 還有另一個跳躍, jb
,但我認為這個對於大型向量並不重要,並且大部分時間都會失敗。 最后一次跳轉, jne
,也是最后一條指令,因此MCA將假設下一條指令再次成為最高指令。 對於足夠多的迭代,這個假設是完全正確的。
總的來說,顯而易見的是,第一個代碼比第二個代碼小得多,因此它可能要快得多。 您的測量確實證實了這一點 您也不需要使用微體系結構分析工具來理解原因。 第二個代碼只是做了很多計算。 因此,您可以快速得出結論,在所有體系結構上,在性能和代碼大小方面傳遞0.0
更好。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.