2013-08-10 12 views
8

Running pusty dla pętli z dużą liczbą powtórzeń, Dostaję szalenie różne numery, jak długo to trwa do uruchomienia:Niepewność wydajności dla pętli w .NET x64: koligacja liczby parzystej?

public static class Program 
{ 
    static void Main() 
    { 
     var sw = new Stopwatch(); 
     sw.Start(); 
     for (var i = 0; i < 1000000000; ++i) 
     { 
     } 
     sw.Stop(); 
     Console.WriteLine(sw.ElapsedMilliseconds); 
    } 
} 

Powyższy będzie działać w około 200ms na moim komputerze, ale jeśli zwiększ go do 1000000001, a następnie tak długo trwa 4x! Następnie, jeśli zrobię to 1000000002, znowu będzie 200 ms!

Ten wydaje się być stanie się dla parzystej liczby iteracji. Jeśli przejdę na for (var i = 1; i < 1000000001, (zanotuj od 1 zamiast 0), to 200 ms. Lub jeśli robię i <= 1000000001 (zauważ mniej niż lub równy), to 200 ms. Lub też (var i = 0; i < 2000000000; i += 2).

Ta opcja pojawia się tylko na x64, ale we wszystkich wersjach .NET do (co najmniej) 4.0. Pojawia się również tylko w trybie zwolnienia z odłączonym debuggerem.

UPDATE Myślałam, że jest to prawdopodobnie ze względu na jakiś mądry nieco przesunięcie w JIT, ale następujący wydaje się zaprzeczyć, że: jeśli robisz coś takiego stworzyć obiekt wewnątrz tej pętli, a następnie że trwa około 4x tak długo, zbyt:

public static class Program 
{ 
    static void Main() 
    { 
     var sw = new Stopwatch(); 
     sw.Start(); 
     object o = null; 
     for (var i = 0; i < 1000000000; i++) 
     { 
      o = new object(); 
     } 
     sw.Stop(); 
     Console.WriteLine(o); // use o so the compiler won't optimize it out 
     Console.WriteLine(sw.ElapsedMilliseconds); 
    } 
} 

to trwa około 1 sekundę na moim komputerze, ale wtedy zwiększa się o 1 do 1000000001 trwa 4 sekundy. To dodatkowe 3000 ms, więc nie mogło to być spowodowane przesunięciem bitów, ponieważ oznaczałoby to również różnicę 3000 ms w pierwotnym problemie.

+0

Być może rozwija dwie iteracje pętli, jeśli limit jest równy, a następnie zdaje sobie sprawę, że wynik pierwszej połowy iteracji nigdy nie jest używany i optymalizuje go. – CodesInChaos

Odpowiedz

6

Więc tutaj są disassemblies:

00000031 xor   eax,eax 
    for (var i = 0; i < 1000000001; ++i) 
00000033 inc   eax   
00000035 cmp   eax,3B9ACA01h 
0000003a jl   0000000000000033 
0000003c movzx  eax,byte ptr [rbx+18h] 
00000040 test  eax,eax 
00000042 je   0000000000000073 

I

00000031 xor   eax,eax 
    for (var i = 0; i < 1000000000; ++i) 
00000033 add   eax,4 
00000036 cmp   eax,3B9ACA00h 
0000003b jl   0000000000000033 
0000003d movzx  eax,byte ptr [rbx+18h] 
00000041 test  eax,eax 
00000043 je   0000000000000074 

Jedyna różnica widzę, że w nawet pętli, indeks pętli jest zwiększana przez 4 naraz (add eax 4) zamiast 1 na raz (inc eax), więc z tego powodu kończy 4x szybciej.

To tylko spekulacje, ale uważam, że jest to czynnik unrolling the loop o współczynnik 4, więc umieszcza ciało 4 razy w pętli i zaledwie 4 razy szybciej. Ponieważ jednak ciało jest puste, puste 4 razy ciało wciąż jest puste, zyskujesz znacznie większy zysk, niż można się spodziewać po rozwinięciu pętli.

+0

Jak widzisz demontaż? – lobsterism

+2

stackoverflow.com/questions/3423547/how-can-i-view-the-disassembly-of-optimised-jitted-net-code – Esailija

+3

Tak, to jest rozwijanie pętli w pracy. Jest to bardziej widoczne w [tej odpowiedzi] (http://stackoverflow.com/a/2057228/17034) o tym, że optymalizacja idzie źle. Lepszy optymalizator dzieli to na dwie sekcje, jedną rozwijaną i drugą, która zajmuje się ostatnimi kilkoma iteracjami. Ale optymalizator jittera nie ma wystarczająco dużo czasu, aby obalić wartości odstające. –

Powiązane problemy