簡體   English   中英

在調用c ++函數時,CLR如何避免thunking?

[英]How does the CLR avoid thunking when calling c++ functions?

MSDN聲明

無論使用何種互操作技術,每次托管函數調用非托管函數時都需要特殊的轉換序列(稱為thunks),反之亦然。 這些thunk是由Visual C ++編譯器自動插入的,但重要的是要記住,累積起來,這些轉換在性能方面可能很昂貴。

然而,CLR肯定會一直調用C ++和Win32函數。 為了處理文件/網絡/窗口以及幾乎任何其他內容,必須調用非托管代碼。 它是如何擺脫分塊懲罰的?

這是一個用C ++ / CLI編寫的實驗,可能有助於描述我的問題:

#define REPS 10000000

#pragma unmanaged
void go1() {
    for (int i = 0; i < REPS; i++)
        pow(i, 3);
}
#pragma managed
void go2() {
    for (int i = 0; i < REPS; i++)
        pow(i, 3);
}
void go3() {
    for (int i = 0; i < REPS; i++)
        Math::Pow(i, 3);
}

public ref class C1 {
public:
    static void Go() {
        auto sw = Stopwatch::StartNew();
        go1();
        Console::WriteLine(sw->ElapsedMilliseconds);
        sw->Restart();
        go2();
        Console::WriteLine(sw->ElapsedMilliseconds);
        sw->Restart();
        go3();
        Console::WriteLine(sw->ElapsedMilliseconds);
    }
};

//Go is called from a C# app

結果是(一致地):

405 (go1 - pure C++)
818 (go2 - managed code calling C++)
289 (go3 - pure managed)

為什么go3比go1更快有點神秘,但這不是我的問題。 我的問題是,我們從go1&go2看到,thunking懲罰增加了400ms。 go3如何擺脫這種懲罰, 因為它調用C ++來進行實際計算?

即使這個實驗由於某種原因無效,我的問題仍然存在 - 每次調用C ++ / Win32時,CLR是否真的有一個thunking懲罰?

基准測試是一種黑色藝術,你在這里得到了一些誤導性的結果。 運行Release版本非常重要,如果你這樣做,那么你現在會注意到go1()不再需要時間了。 本機代碼優化器具有它的特殊知識,如果你不使用它的結果,那么它完全消除它。

您必須更改代碼才能獲得可靠的結果。 首先在Go()測試體周圍放置一個循環,重復至少20次。 這消除了jitting和緩存開銷,並有助於查看大的標准偏差。 敲掉REPS 0,這樣你就不用等太久了。 贊成工具>選項>調試>常規,“抑制JIT優化”未選中。 更改代碼,我建議:

__declspec(noinline)
double go1() {
    double sum = 0;
    for (int i = 0; i < REPS; i++)
        sum += pow(i, 3);
    return sum;
}

注意sum變量如何強制優化器保持調用,使用__declspec可以防止刪除整個函數並避免污染Go()體。 對go2和go3執行相同操作,使用[MethodImpl(MethodImplOptions :: NoInlining)]。

我在筆記本電腦上看到的結果:x64:75,84,84,x86:73,89,89 + 5 / -3毫秒。

工作中有三種不同的機制:

  • go1()代碼生成正如您在本機代碼中所期望的那樣,在x64模式下直接調用__libm_sse2_pow_precise()CRT函數。 這里沒什么了不起的,除了在Release版本中刪除它的風險。
  • go2()使用你詢問的thunk。 文檔對於thunking有點過於恐慌,所需要的只是代碼在堆棧上編寫cookie,以防止垃圾收集器在查找對象根時陷入非托管堆棧幀。 當它還必須轉換函數參數和/或返回值時,它可能更昂貴,但這不是這里的情況。 抖動優化器不能消除pow()調用,它沒有CRT功能的特殊知識。
  • go3()使用了一種非常不同的機制,盡管測量結果相似。 Math :: Pow()在CLR中是特殊的,它使用所謂的FCall機制 沒有thunking,直接從托管代碼到編譯的C ++機器代碼。 這種微優化在CLR / BCL中非常常見。 有些必要,因為它對可能引發異常的參數執行檢查,所以會產生額外的開銷。 此外,抖動優化器沒有消除調用的基本原因是,它通常避免了使異常消失的優化。

暫無
暫無

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

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