2015-11-18 15 views
6

Ich habe den folgenden C# -Code Benchmark unter Release-Modus versuchen:Langsame Ausführung unter 64 Bit. Möglicher RyuJIT-Fehler?

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace ConsoleApplication54 
{ 
class Program 
{ 
    static void Main(string[] args) 
    { 
     int counter = 0; 
     var sw = new Stopwatch(); 
     unchecked 
     { 
      int sum = 0; 
      while (true) 
      { 
       try 
       { 
        if (counter > 20) 
         throw new Exception("exception"); 
       } 
       catch 
       { 
       } 

       sw.Restart(); 
       for (int i = 0; i < int.MaxValue; i++) 
       { 
        sum += i; 
       } 
       counter++; 
       Console.WriteLine(sw.Elapsed); 
      } 

     } 
    } 
} 
} 

Ich bin auf einer 64-Bit-Maschine und VS 2015 installiert. Wenn ich den Code unter 32-Bit ausführe, wird jede Iteration um 0.6 Sekunden, gedruckt auf der Konsole ausgeführt. Wenn ich es unter 64-Bit laufe, springt die Dauer für jede Iteration einfach auf 4 Sekunden! Ich habe den Beispielcode in meinem Kollegencomputer versucht, auf dem nur VS 2013 installiert ist. Dort laufen sowohl 32-Bit- als auch 64-Bit-Versionen um 0.6 Sekunden.

Zusätzlich, wenn wir nur den Versuch catch Block entfernen, läuft es auch in 0.6 Sekunden mit VS 2015 in 64-Bit.

Das sieht wie eine ernste RyuJIT-Regression aus, wenn es einen Versuch Catch-Block gibt. Hab ich recht ?

+0

Ihr Computer ist super Genie! für mich dauert es etwa 10 Sekunden jede Wiederholung :(und keinen Unterschied hier. sowohl 32bit und 64bit gibt ziemlich gleiche Ergebnisse. –

+0

@ M.kazem. Ich glaube nicht, dass das möglich ist. Mein Computer ist ein Surface Pro 3 i7 mit U Es ist definitiv kein Kraftpaket. Bist du sicher, dass du es im Release-Modus startest und ohne Debugging startest? BTW habe bisher 4 verschiedene Computer ausprobiert. –

+0

Oh nein nein ich habe es, weil du die Option 'Code optimieren aktiviert hast 'in Lösung Eigenschaften. Jetzt bekomme ich' 1.3s' für 32bit und '3.9s' für 64 bit. –

Antwort

11

Bankmarkierung ist eine schöne Kunst. Machen Sie eine kleine Änderung an Ihrem Code:

Console.WriteLine("{0}", sw.Elapsed, sum); 

Und Sie werden jetzt den Unterschied verschwinden sehen. Oder, um es anders auszudrücken, die x86-Version ist jetzt genauso langsam wie der x64-Code. Sie können sich wahrscheinlich herausfinden, was RyuJIT nicht tun, was das Vermächtnis Jitter von dieser geringfügigen Änderung tat, es beseitigt nicht das unnötig

sum += i; 

Etwas, das man sehen kann, wenn man an dem erzeugten Maschinencode mit Debug suchen> Windows> Disassemblierung. Was in der Tat eine Eigenart in RyuJIT ist. Die Eliminierung von toten Codes ist nicht so gründlich wie der Legacy-Jitter. Ansonsten, nicht ganz ohne Grund, hat Microsoft den x64-Jitter wegen Fehlern neu geschrieben, die er nicht einfach beheben konnte. Einer davon war ein ziemlich unangenehmes Problem mit dem Optimierer, er hatte keine Obergrenze für die Zeit, die er für die Optimierung einer Methode aufwenden musste. Da es bei Methoden mit sehr großen Körpern eher schlechtes Verhalten hervorruft, könnte es Dutzende von Millisekunden lang im Wald liegen und spürbare Ausführungspausen verursachen.

Nennt es einen Fehler, meh, nicht wirklich. Schreibe normalen Code und der Jitter wird dich nicht enttäuschen. Optimierung tut für immer an der üblichen Stelle beginnen, zwischen den Ohren des Programmierers.

0

Nach ein paar Tests habe ich ein paar interessante Ergebnisse. Meine Tests drehten sich um den try catch Block. Wie das OP gezeigt hat, ist die Ausführungszeit dieselbe, wenn Sie diesen Block entfernen. Ich habe das ein wenig weiter eingegrenzt und bin zu dem Schluss gekommen, dass es wegen der counter Variable in if Aussage im try Block ist.

Läßt die redundanten entfernen throw:

   try 
       { 
        if (counter== 0) { } 
       } 
       catch 
       { 
       } 

Sie werden die gleichen Ergebnisse mit diesem Code erhalten, wie Sie mit dem ursprünglichen Code tun.

Lets Änderungszähler ein tatsächlicher int Wert sein:

   try 
       { 
        if (1 == 0) { } 
       } 
       catch 
       { 
       } 

Mit diesem Code, die 64-Bit-Version in der Ausführungszeit von 4 Sekunden bis etwa 1,7 Sekunden reduziert hat. Immer noch das Doppelte der 32-Bit-Version. Aber ich fand das interessant.Leider habe ich nach meiner schnellen Google-Suche keinen Grund gefunden, aber ich werde ein bisschen mehr graben und diese Antwort aktualisieren, wenn ich herausfinden werde, warum das passiert.

Was die verbleibende zweite, dass wir die 64-Bit-Version rasieren möchte, kann ich sehen, dass dies Sie die sum von i in Ihrer for Schleife erhöht wird. Lässt sich dies ändern, so dass sum nicht seine Grenzen überschreiten:

  for (int i = 0; i < int.MaxValue; i++) 
      { 
       sum ++; 
      } 

Diese Änderung (zusammen mit der Änderung des try Block) wird die Ausführungszeit des 64-Bit-Anwendung zu 0,7 Sekunden reduzieren. Meine Argumentation für die 1 Sekunde Unterschied in der Zeit ist aufgrund der künstlichen Art, dass die 64-Bit-Version muss eine int behandeln, die natürlich 32 Bits ist. In der 32-Bit-Version sind dem Int32 32 Bits zugeordnet (sum). Wenn sum über seine Grenzen geht, ist es leicht, diese Tatsache zu bestimmen.

In der 64-Bit-Version sind dem Int32 64 Bits zugeordnet (sum). Wenn die Summe ihre Grenzen überschreitet, muss ein Mechanismus vorhanden sein, um dies zu erkennen, was zur Verlangsamung führen könnte. Vielleicht dauert sogar der Vorgang des Hinzufügens von sum & i länger aufgrund der Zunahme der zugewiesenen redundanten Bits.

Ich theoretisiere hier; Nimm das nicht als Evangelium. Ich dachte nur, ich würde meine Ergebnisse veröffentlichen. Ich bin mir sicher, dass jemand anderes das Problem, das ich gefunden habe, beleuchten kann.

-

aktualisieren

@HansPassant ‚s Antwort darauf hingewiesen, dass die sum += i; Linie beseitigt werden kann, da es nicht notwendig erachtet wird, was durchaus Sinn macht, sum ist nicht außerhalb der for Schleife verwendet wird, . Nachdem er den Wert von sum außerhalb der for-Schleife eingeführt hatte, bemerkten wir, dass die x86-Version genauso langsam war wie die x64-Version. Also habe ich beschlossen, ein bisschen zu testen. Lässt die auf die folgende for-Schleife und den Druck ändern:

   int x = 0; 
       for (int i = 0; i < int.MaxValue; i++) 
       { 
        sum += i; 
        x = sum; 
       } 
       counter++; 
       Console.WriteLine(sw.Elapsed + " " + x); 

können Sie sehen, dass ich ein neues int x eingeführt haben, die den Wert von sum in der for Schleife zugewiesen wird. Dieser Wert von x wird nicht auf die Konsole geschrieben. sum verlässt die for Schleife nicht. Dies, glaube ich oder nicht, reduziert die Ausführungszeit für x64 auf 0,7 Sekunden. Die x86-Version springt jedoch auf 1,4 Sekunden.

+0

Es spielt keine Rolle, wenn 'sum' über seine Grenzen hinausgeht - es läuft in einem ungeprüften Kontext, so dass Überlauf ignoriert wird - wenn der Code überhaupt läuft. Wie Hans hervorhebt, ist es * möglich * (x86, älteres x64 JIT), da die 'sum'-Variable nach dieser Schleife nicht gelesen wird, die Schleife als Optimierung vollständig zu eliminieren. –

+0

@Damien_The_Unbeliever Sie haben tatsächlich einen sehr interessanten Punkt angesprochen .. Lassen Sie mich meine Antwort mit meinen Ergebnissen aktualisieren. –

+0

@Damien_The_Unbeliever Ich glaube nicht, dass die Tatsache, dass dies unkontrolliert ist, wirklich zählt. Das bedeutet nur, dass es definitiv keine OverflowException auslösen wird, wenn die Summe ihre Grenzen erreicht. (Anders als wenn Sie es in checked geändert haben). Trotzdem muss es immer noch eine künstliche Überprüfung der Grenzen in 64 Bit durchführen, da dem Int 64 Bits zugeordnet sind. In der Theorie denke ich. Auch hier bin ich kein Experte, das hat mich nur interessiert. –