簡體   English   中英

(如何)使用LLVM機器碼分析器預測代碼片段的運行時間?

[英](How) can I predict the runtime of a code snippet using LLVM Machine Code Analyzer?

我使用llvm-mca來計算一堆代碼的總周期 ,認為它們會預測它的運行時間。 但是,動態測量運行時幾乎沒有相關性。 那么: 為什么由llvm-mca計算的總周期不能准確預測運行時? 我可以用llvm-mca以更好的方式預測運行時嗎?


細節:

我想知道不同類型的begin (和end )迭代器的以下代碼的運行時, startValue0.00ULL

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-BEGINLLVM-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.

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