c# - 靜態類別c - 非靜態成員參考必須相對於特定物件




為什麼添加局部變量會使.NET代碼變慢 (4)

為什麼要註釋這個for循環的前兩行,並在42%的加速時取消註釋第三個結果?

int count = 0;
for (uint i = 0; i < 1000000000; ++i) {
    var isMultipleOf16 = i % 16 == 0;
    count += isMultipleOf16 ? 1 : 0;
    //count += i % 16 == 0 ? 1 : 0;
}

在時序背後是非常不同的彙編代碼:循環中的13對7指令。 該平台是運行.NET 4.0 x64的Windows 7。 啟用了代碼優化,測試應用程序在VS2010之外運行。 [ 更新: Repro項目 ,對驗證項目設置很有用。]

消除中間佈爾值是一個基本的優化,是我1980年代龍書中最簡單的一個。 在生成CIL或JITing x64機器代碼時,如何不應用優化?

有沒有“真正的編譯器,我希望你優化這段代碼,請”切換? 雖然我同情過早優化類似於對金錢熱愛的情緒,但我可以看到試圖描述一個複雜算法的挫敗感,這個算法在整個慣例中分散。 你可以通過熱點工作,但沒有暗示更廣泛的溫暖區域可以通過手動調整我們通常認為理所當然的編譯器來大大改善。 我當然希望我在這裡遺漏一些東西。

更新: x86也會出現速度差異,但取決於方法即時編譯的順序。 請參閱為什麼JIT訂單會影響性能?

彙編代碼 (根據要求):

    var isMultipleOf16 = i % 16 == 0;
00000037  mov         eax,edx 
00000039  and         eax,0Fh 
0000003c  xor         ecx,ecx 
0000003e  test        eax,eax 
00000040  sete        cl 
    count += isMultipleOf16 ? 1 : 0;
00000043  movzx       eax,cl 
00000046  test        eax,eax 
00000048  jne         0000000000000050 
0000004a  xor         eax,eax 
0000004c  jmp         0000000000000055 
0000004e  xchg        ax,ax 
00000050  mov         eax,1 
00000055  lea         r8d,[rbx+rax] 
    count += i % 16 == 0 ? 1 : 0;
00000037  mov         eax,ecx 
00000039  and         eax,0Fh 
0000003c  je          0000000000000042 
0000003e  xor         eax,eax 
00000040  jmp         0000000000000047 
00000042  mov         eax,1 
00000047  lea         edx,[rbx+rax] 

問題應該是“我為什麼在我的機器上看到這樣的差異?”。 我不能重現如此巨大的速度差異,並懷疑你的環境有特定的東西。 很難說它可能是什麼。 可以是你前一段時間設置的一些(編譯器)選項而忘記它們。

我創建了一個控制台應用程序,在發布模式下重建(x86)並在VS外部運行。 結果幾乎相同,兩種方法均為1.77秒。 這是確切的代碼:

static void Main(string[] args)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    int count = 0;

    for (uint i = 0; i < 1000000000; ++i)
    {
        // 1st method
        var isMultipleOf16 = i % 16 == 0;
        count += isMultipleOf16 ? 1 : 0;

        // 2nd method
        //count += i % 16 == 0 ? 1 : 0;
    }

    sw.Stop();
    Console.WriteLine(string.Format("Ellapsed {0}, count {1}", sw.Elapsed, count));
    Console.ReadKey();
}

請任何有5分鐘複製代碼,重建,在VS外部運行並在結果中發布評論的人。 我想避免說“它適用於我的機器”。

編輯

為了確保我已經創建了64位 Winforms應用程序,結果與問題類似 - 第一種方法比第二種方法(1.05秒) (1.57秒)。 我觀察到的差異是33% - 仍然很多。 似乎.NET4 64位JIT編譯器中存在一個錯誤。


我不能說.NET編譯器或它的優化,甚至不能說它執行它的優化。

但在這種特定情況下,如果編譯器將該布爾變量折疊到實際語句中,並且您嘗試調試此代碼,則優化代碼將與編寫的代碼不匹配。 您將無法單步執行isMulitpleOf16分配並檢查其值。

這只是可以關閉優化的一個例子。 可能還有其他人。 優化可以在代碼的加載階段期間發生,而不是從CLR的代碼生成階段發生。

現代運行時非常複雜,特別是如果您在運行時投入JIT和動態優化。 感謝代碼完成它所說的話。


我認為這與你的其他問題有關。 當我按如下方式更改您的代碼時,多行版本獲勝。

哎呀,只在x86上。 在x64上,多行是最慢的,並且條件性地勝過它們。

class Program
{
    static void Main()
    {
        ConditionalTest();
        SingleLineTest();
        MultiLineTest();
        ConditionalTest();
        SingleLineTest();
        MultiLineTest();
        ConditionalTest();
        SingleLineTest();
        MultiLineTest();
    }

    public static void ConditionalTest()
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        int count = 0;
        for (uint i = 0; i < 1000000000; ++i) {
            if (i % 16 == 0) ++count;
        }
        stopwatch.Stop();
        Console.WriteLine("Conditional test --> Count: {0}, Time: {1}", count, stopwatch.ElapsedMilliseconds);
    }

    public static void SingleLineTest()
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        int count = 0;
        for (uint i = 0; i < 1000000000; ++i) {
            count += i % 16 == 0 ? 1 : 0;
        }
        stopwatch.Stop();
        Console.WriteLine("Single-line test --> Count: {0}, Time: {1}", count, stopwatch.ElapsedMilliseconds);
    }

    public static void MultiLineTest()
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        int count = 0;
        for (uint i = 0; i < 1000000000; ++i) {
            var isMultipleOf16 = i % 16 == 0;
            count += isMultipleOf16 ? 1 : 0;
        }
        stopwatch.Stop();
        Console.WriteLine("Multi-line test  --> Count: {0}, Time: {1}", count, stopwatch.ElapsedMilliseconds);
    }
}

這是.NET Framework中的一個錯誤。

好吧,我真的只是猜測,但我提交了一份關於Microsoft Connect的錯誤報告,看看他們說了什麼。 在Microsoft刪除該報告後,我在GitHub上的roslyn項目中重新提交了該報告。

更新: Microsoft已將此問題移至coreclr項目。 從關於這個問題的評論來看,把它稱為bug似乎有點強烈; 它更像是一個缺失的優化。







jit