2010-04-07 5 views
13

(Hintergrund: Why should I use int instead of a byte or short in C#)int, kurz, Byte-Leistung in Back-to-Back-for-Schleifen

Zu meiner eigenen Neugier über die Vor-und Nachteile der Verwendung der "angemessene Größe" integer vs der "optimiert" erfüllen Integer Ich habe den folgenden Code geschrieben, der das bestätigte, was ich vorher über die int-Leistung in .Net (und was im obigen Link erklärt wurde) wahr ist, nämlich dass es für die int-Leistung optimiert ist und nicht für kurz oder Byte.

DateTime t; 
long a, b, c; 

t = DateTime.Now; 
for (int index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
}   
a = DateTime.Now.Ticks - t.Ticks; 

t = DateTime.Now; 
for (short index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 

b=DateTime.Now.Ticks - t.Ticks; 

t = DateTime.Now;   
for (byte index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 
c=DateTime.Now.Ticks - t.Ticks; 

Console.WriteLine(a.ToString()); 
Console.WriteLine(b.ToString()); 
Console.WriteLine(c.ToString()); 

Dies ergibt etwa konsistente Ergebnisse im Bereich der ...

~ 950000

~ 2000000

~ 1700000

, die in Einklang mit was ich erwarten würde zu sehen.

aber wenn ich versuche, die Schleifen für jeden Datentyp wie folgt zu wiederholen ...

t = DateTime.Now; 
for (int index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 
for (int index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 
for (int index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 
a = DateTime.Now.Ticks - t.Ticks; 

Die Zahlen sind eher wie ...

~ 4500000

~ 3100000

~ 300000

Was ich rätselhaft finde. Kann jemand eine Erklärung anbieten?

HINWEIS: Im Interesse des Vergleichs wie für wie ich habe die Schleifen auf 127 wegen des Bereichs der Byte Werttyp begrenzt. Auch dies ist ein Akt Neugier nicht Produktionscode Mikro-Optimierung.

+1

'Byte' hat den Bereich von 0-255. Es ist kein signierter Datentyp. –

+6

Die Klasse "DateTime" eignet sich auch nicht für Profilerstellung auf niedriger Ebene. Verwenden Sie 'System.Diagnostics.Stopwatch'. –

+0

@Aaronaught, Jon: Danke für die Lösung. Ich habe einige Klarstellungen ... index <255/127; ... In diesem Code ist 255/127 immer Byte/Short/Int Datentyp Oder .Net IL ändert seinen Datentyp von 255/127 in den Index Datentyp für entsprechende für Schleifen? Wir können eine Konstante für den entsprechenden Datentyp for-loop und check-it deklarieren? – Thulasiram

Antwort

35

Zunächst einmal, es ist nicht .NET, die für int Leistung optimiert ist, ist es die Maschine, die optimiert ist, weil 32 Bit ist die native Wortgröße (es sei denn, Sie auf x64 sind, wobei in diesem Fall ist es long oder 64 Bit) .

Zweitens schreiben Sie auf die Konsole innerhalb jeder Schleife - das wird auch viel teurer sein, als den Schleifenzähler zu inkrementieren und zu testen, also messen Sie hier nichts Realistisches.

Drittens hat ein byte einen Bereich von bis zu 255, so dass Sie 254 mal loopen können (wenn Sie 255 versuchen, wird es überlaufen und die Schleife wird nie enden - aber Sie müssen nicht bei 128 stoppen).

Viertens, Sie tun nicht überall in der Nähe von genug Iterationen zu profilieren. Das Iterieren einer engen Schleife 128 oder sogar 254 mal ist bedeutungslos. Was Sie tun sollten, ist die Schleife in eine andere Schleife, die viel öfter iteriert, sagen wir 10 Millionen, und überprüfen Sie die Ergebnisse davon.

Schließlich wird die Verwendung von DateTime.Now in Berechnungen zu etwas Timing "Rauschen" beim Profiling führen. Es wird empfohlen (und einfacher), stattdessen die Klasse Stopwatch zu verwenden.

Unterm Strich braucht dies viele Änderungen, bevor es eine gültige Perf-Test sein kann.


Hier ist, was ich ein genaueres Testprogramm würde betrachten: Dies läuft jede Schleife innerhalb einer viel größeren Schleife (5.000.000 Iterationen) und führt im Inneren der eine sehr einfache Bedienung

class Program 
{ 
    const int TestIterations = 5000000; 

    static void Main(string[] args) 
    { 
     RunTest("Byte Loop", TestByteLoop, TestIterations); 
     RunTest("Short Loop", TestShortLoop, TestIterations); 
     RunTest("Int Loop", TestIntLoop, TestIterations); 
     Console.ReadLine(); 
    } 

    static void RunTest(string testName, Action action, int iterations) 
    { 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     for (int i = 0; i < iterations; i++) 
     { 
      action(); 
     } 
     sw.Stop(); 
     Console.WriteLine("{0}: Elapsed Time = {1}", testName, sw.Elapsed); 
    } 

    static void TestByteLoop() 
    { 
     int x = 0; 
     for (byte b = 0; b < 255; b++) 
      ++x; 
    } 

    static void TestShortLoop() 
    { 
     int x = 0; 
     for (short s = 0; s < 255; s++) 
      ++x; 
    } 

    static void TestIntLoop() 
    { 
     int x = 0; 
     for (int i = 0; i < 255; i++) 
      ++x; 
    } 
} 

Schleife (erhöht eine Variable). Die Ergebnisse waren für mich:

Byte Loop: Abgelaufene Zeit = 00: 00: 03.8949910
Short Loop: Abgelaufene Zeit = 00: 00: 03.9098782
Int Loop: Abgelaufene Zeit = 00: 00: 03,2986990

Also kein nennenswerter Unterschied.

Auch stellen Sie sicher, dass Sie im Freigabemodus Profil, viele Leute vergessen und im Debug-Modus testen, die deutlich weniger genau sein wird.

+1

Ooh danke, ich habe noch nie wirklich versucht, meinen Code vorher zu profilieren. Gute Punkte, an Bord genommen :) – gingerbreadboy

+2

@Aaronaught: Ich liebe, wie ähnlich unsere Benchmarks sind :) –

+2

@Jon: Ich schwöre, ich habe deine nicht kopiert. : P – Aaronaught

11

Die meiste Zeit dieser Zeit wird wahrscheinlich damit verbracht, auf die Konsole zu schreiben. tut etwas anderes als das Versuchen in der Schleife ...

Zusätzlich:

  • DateTime.Now zu verwenden ist eine schlechte Art und Weise Zeit zu messen. Verwenden Sie System.Diagnostics.Stopwatch statt
  • Sobald Sie den Anruf loszuwerden, wird eine Schleife von 127 Iterationen zu kurz sein, um zu messen. Sie müssen die Schleife Lose von Zeiten ausführen, um eine sinnvolle Messung zu erhalten.

Hier ist meine Benchmark:

using System; 
using System.Diagnostics; 

public static class Test 
{  
    const int Iterations = 100000; 

    static void Main(string[] args) 
    { 
     Measure(ByteLoop); 
     Measure(ShortLoop); 
     Measure(IntLoop); 
     Measure(BackToBack); 
     Measure(DelegateOverhead); 
    } 

    static void Measure(Action action) 
    { 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     GC.Collect(); 
     Stopwatch sw = Stopwatch.StartNew(); 
     for (int i = 0; i < Iterations; i++) 
     { 
      action(); 
     } 
     sw.Stop(); 
     Console.WriteLine("{0}: {1}ms", action.Method.Name, 
          sw.ElapsedMilliseconds); 
    } 

    static void ByteLoop() 
    { 
     for (byte index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
    } 

    static void ShortLoop() 
    { 
     for (short index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
    } 

    static void IntLoop() 
    { 
     for (int index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
    } 

    static void BackToBack() 
    { 
     for (byte index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
     for (short index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
     for (int index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
    } 

    static void DelegateOverhead() 
    { 
     // Nothing. Let's see how much 
     // overhead there is just for calling 
     // this repeatedly... 
    } 
} 

Und die Ergebnisse:

ByteLoop: 6585ms 
ShortLoop: 6342ms 
IntLoop: 6404ms 
BackToBack: 19757ms 
DelegateOverhead: 1ms 

(Das auf einem Netbook ist - die Anzahl der Iterationen einstellen, bis Sie etwas Vernünftiges :)

erhalten

Das scheint zu zeigen, dass es im Grunde keinen signifikanten Unterschied gibt, welchen Typ Sie verwenden.

+0

aber alle Schleifen schreiben auf die Konsole die gleiche Anzahl von Malen, dh 127 x n-Schleifen – gingerbreadboy

+0

obwohl ich denke, die int.toString() könnte länger dauern als byte.toString() vielleicht? – gingerbreadboy

+5

@runrunraygun: 'Console.WriteLine' ist eine asynchrone Operation mit unzuverlässiger Ausführungszeit. Es ist zwar nicht sehr wahrscheinlich, dass es dramatische Auswirkungen auf Ihre Ergebnisse hat, aber verwenden Sie etwas zuverlässigeres. Außerdem ist 'int.ToString()' nicht die gleiche Funktion wie 'byte.ToString() ', also führen Sie nicht in jeder Schleife die gleiche Aktion aus. –

0

Das Profilieren von .Net-Code ist sehr schwierig, weil die Laufzeitumgebung, in der der kompilierte Bytecode läuft, Laufzeitoptimierungen am Bytecode ausführen kann.In Ihrem zweiten Beispiel entdeckte der JIT-Compiler wahrscheinlich den wiederholten Code und erstellte eine optimierte Version. Aber ohne eine wirklich detaillierte Beschreibung, wie das Laufzeitsystem funktioniert, ist es unmöglich zu wissen, was mit Ihrem Code passieren wird. Und es wäre töricht, auf der Basis von Experimenten zu raten, da Microsoft vollkommen in ihren Rechten ist, die JIT-Engine jederzeit neu zu gestalten, vorausgesetzt, sie brechen keine Funktionalität.

+0

Das Ausführen des Codes innerhalb des Debuggers (oder, genauer gesagt, das Kompilieren und Ausführen unter den Standardeinstellungen für das Debug-Profil, mit dem ein VS-Projekt erstellt wird) eliminiert die Möglichkeit der Art der Optimierung, von der Sie sprechen. –

+0

@Adam: Aber wer würde Code unter einem Debugger ausführen. Ich habe bemerkt, dass der Code in VS2005 viel langsamer innerhalb des Debuggers läuft als eigenständig. IIRC, jemand hier erwähnte, dass die Ausgabe des Debug-.net-Compilers und des Release-.net-Compilers fast identisch waren und es die Tatsache war, dass der Code stand-alone im Gegensatz zu innerhalb des Debuggers ausgeführt wurde, der den Unterschied ausmachte. – Skizz

+0

Das Deaktivieren von Optimierungen (was standardmäßig in der Debug-Konfiguration vorgenommen wird) ist genau das, was die Art von "Wegoptimierung" beseitigt, von der Sie sprechen. Das Anfügen von * any * Debuggern kann sich negativ auf die Leistung auswirken, aber das ist ein anderes Problem. Die Ausgaben des Compilers mit aktivierten Optimierungen unterscheiden sich tatsächlich von der Ausgabe mit deaktivierten Optimierungen. –

1

Ich habe die beiden obigen Programme ausprobiert, da sie so aussahen, als würden sie unterschiedliche und möglicherweise widersprüchliche Ergebnisse auf meiner Entwicklungsmaschine erzeugen.

Ausgänge von Aaronaughts' Test-Harnisch

Short Loop: Elapsed Time = 00:00:00.8299340 
Byte Loop: Elapsed Time = 00:00:00.8398556 
Int Loop: Elapsed Time = 00:00:00.3217386 
Long Loop: Elapsed Time = 00:00:00.7816368 

Ints sind viel schneller

Ausgänge von Jons

ByteLoop: 1126ms 
ShortLoop: 1115ms 
IntLoop: 1096ms 
BackToBack: 3283ms 
DelegateOverhead: 0ms 

nichts drin

Jon hat die große feste Konstante des Aufrufs tostring in den Ergebnissen, die den möglichen Nutzen verbergen können s das könnte auftreten, wenn die Arbeit in der Schleife weniger war. Aaronaught verwendet ein 32-Bit-Betriebssystem, das nicht so sehr von Ints zu profitieren scheint wie das x64-Rig, das ich verwende.

Hardware/Software Die Ergebnisse wurden mit Turbo deaktiviert auf ein Core i7 975 bei 3,33 GHz und der Kern Affinität gesetzt Auswirkungen anderer Aufgaben zu reduzieren. Performance-Einstellungen sind alle auf Maximum eingestellt und Virenscanner/unnötige Hintergrund-Tasks wurden ausgesetzt. Windows 7 x64 Ultimate mit 11 GB Reserverad und sehr wenig IO-Aktivität. Führen Sie die Release-Konfiguration aus, die in 2008 erstellt wurde, ohne dass ein Debugger oder Profiler angehängt wurde.

Wiederholbarkeit Ursprünglich 10 mal wiederholt die Reihenfolge der Ausführung für jeden Test wiederholt. Variation war vernachlässigbar, also habe ich nur mein erstes Ergebnis veröffentlicht. Bei maximaler CPU-Last blieb das Verhältnis der Ausführungszeiten konstant. Wiederholen Sie läuft auf mehreren x64 xp xeon Klingen gibt etwa gleiche Ergebnisse nach Berücksichtigung der CPU-Generation und Ghz

Profil Redgate/Jetbrains/Slimtune/CLR-Profiler und meine eigenen Profiler alle zeigen, dass die Ergebnisse korrekt sind.

Debug Build Die Verwendung der Debug-Einstellungen in VS liefert konsistente Ergebnisse wie bei Aaronaught.

+0

Ich führe eine x64-Box. Das ist ein ziemlich anomales Ergebnis für den ersten Test - es sieht so aus, als ob die 'short' und' byte' Versionen viel länger brauchten, als sie sollten, während die 'int' Version meiner sehr nahe kam. Hast du den Test ein paar Mal durchgeführt? Hattest du sonst noch etwas zur gleichen Zeit? – Aaronaught

+0

Haben Sie versucht, die short-byte-int-Schleifen neu zu ordnen, um zu sehen, ob es einen Unterschied gibt? Nur für den Fall, dass der JIT-Compiler entscheidet, dass eine dritte Schleife sich zu optimieren lohnt, da es sich um eine übliche Operation handelt. Nur ein Gedanke. Wäre interessant zu sehen. – Skizz

+0

@Aaronaught Die Umstellung meiner Konfiguration auf x86-DLLs hat meine Ergebnisse geglättet. Deshalb habe ich angenommen, dass Sie ein 32-Bit-Betriebssystem verwenden. – Steve

0

Der Konsolenschreibvorgang hat keine Auswirkung auf die tatsächliche Leistung der Daten. Es hat mehr mit der Interaktion mit den Konsolenbibliotheksaufrufen zu tun. Schlage vor, dass Sie in diesen Schleifen etwas Interessantes tun, das unabhängig von der Datengröße ist.

Vorschläge: Bit-Verschiebungen, vervielfacht, Array Manipulation, aber auch viele andere ...

4

Nur aus Neugier ich ein litte das Programm von Aaronaught modifiziert und es in beiden x86 und x64-Modus kompiliert. Seltsam, Int funktioniert viel schneller in x64:

x86

Byte Loop: Abgelaufene Zeit = 00: 00: 00.8636454
Short Loop: Abgelaufene Zeit = 00.00.00.8795518
UShort Loop: Abgelaufene Zeit = 00: 00: 00.8630357
Int Loop: Abgelaufene Zeit = 00: 00: 00.5184154
UInt Loop: Abgelaufene Zeit = 00: 00: 00.4950156
lange Schleife: Abgelaufene Zeit = 00: 00: 01.2941183
ULong Loop: Abgelaufene Zeit = 00: 00: 01,3023409

x64

Byte Loop: Abgelaufene Zeit = 00: 00: 01.0646588
Short Loop: Abgelaufene Zeit = 00.00: 01.0719330
UShort Loop: 00 Abgelaufene Zeit =: 00: 01.0711545
Int Loop: Abgelaufene Zeit = 00: 00: 00.2462848
UInt Loop: Abgelaufene Zeit = 00: 00: 00.4708777
lange Schleife: Abgelaufene Zeit = 00.00 : 00.5242272
ULong Schleife: verstrichene Zeit = 00: 00: 00.5144035