Ich finde massive Leistungsunterschiede zwischen ähnlichem Code in C anc C #.
Der C-Code lautet:
#include <stdio.h>
#include <time.h>
#include <math.h>
main()
{
int i;
double root;
clock_t start = clock();
for (i = 0 ; i <= 100000000; i++){
root = sqrt(i);
}
printf("Time elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);
}
Und die C # (Konsolen-App) ist:
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
DateTime startTime = DateTime.Now;
double root;
for (int i = 0; i <= 100000000; i++)
{
root = Math.Sqrt(i);
}
TimeSpan runTime = DateTime.Now - startTime;
Console.WriteLine("Time elapsed: " + Convert.ToString(runTime.TotalMilliseconds/1000));
}
}
}
Mit dem obigen Code wird das C # in 0,328125 Sekunden (Release-Version) abgeschlossen und das C benötigt 11,14 Sekunden, um ausgeführt zu werden.
Das c wird mit mingw zu einer ausführbaren Windows-Datei kompiliert.
Ich war immer davon ausgegangen, dass C / C ++ schneller oder zumindest mit C # .net vergleichbar ist. Was genau bewirkt, dass das C über 30-mal langsamer läuft?
BEARBEITEN: Es scheint, dass der C # -Optimierer den Stamm entfernt hat, da er nicht verwendet wurde. Ich habe die Root-Zuordnung in root + = geändert und die Summe am Ende ausgedruckt. Ich habe das C auch mit cl.exe kompiliert, wobei das Flag / O2 für maximale Geschwindigkeit gesetzt ist.
Die Ergebnisse sind jetzt: 3,75 Sekunden für den C 2,61 Sekunden für den C #
Das C dauert noch länger, aber das ist akzeptabel
quelle
Antworten:
Da Sie niemals 'root' verwenden, hat der Compiler möglicherweise den Aufruf entfernt, um Ihre Methode zu optimieren.
Sie könnten versuchen, die Quadratwurzelwerte in einem Akkumulator zu akkumulieren, sie am Ende der Methode auszudrucken und zu sehen, was los ist.
Bearbeiten: siehe Jalfs Antwort unten
quelle
Sie müssen Debug-Builds vergleichen. Ich habe gerade Ihren C-Code kompiliert und bekommen
Wenn Sie keine Optimierungen aktivieren, ist jedes Benchmarking, das Sie durchführen, völlig wertlos. (Und wenn Sie Optimierungen aktivieren, wird die Schleife wegoptimiert. Daher ist auch Ihr Benchmarking-Code fehlerhaft. Sie müssen ihn zwingen, die Schleife auszuführen, normalerweise indem Sie das Ergebnis oder ähnliches zusammenfassen und am Ende ausdrucken.)
Es scheint, dass Sie im Grunde genommen messen, "welcher Compiler den meisten Debugging-Overhead einfügt". Und es stellt sich heraus, dass die Antwort C ist. Aber das sagt uns nicht, welches Programm am schnellsten ist. Denn wenn Sie Geschwindigkeit wollen, aktivieren Sie Optimierungen.
Übrigens sparen Sie sich auf lange Sicht viele Kopfschmerzen, wenn Sie die Vorstellung aufgeben, dass Sprachen "schneller" sind als die anderen. C # hat nicht mehr Geschwindigkeit als Englisch.
Es gibt bestimmte Dinge in der C-Sprache, die selbst in einem naiven, nicht optimierenden Compiler effizient wären, und es gibt andere, die sich stark auf einen Compiler verlassen, um alles weg zu optimieren. Und das gilt natürlich auch für C # oder eine andere Sprache.
Die Ausführungsgeschwindigkeit wird bestimmt durch:
Ein guter C # -Compiler liefert effizienten Code. Ein fehlerhafter C-Compiler generiert langsamen Code. Was ist mit einem C-Compiler, der C # -Code generiert hat, den Sie dann über einen C # -Compiler ausführen können? Wie schnell würde das laufen? Sprachen haben keine Geschwindigkeit. Ihr Code tut es.
quelle
i
, undsqrt
genau das wird gemessen.Ich werde mich kurz fassen, es ist bereits als beantwortet markiert. C # hat den großen Vorteil, ein gut definiertes Gleitkommamodell zu haben. Dies entspricht zufällig dem nativen Betriebsmodus des FPU- und SSE-Befehlssatzes auf x86- und x64-Prozessoren. Kein Zufall da. Der JITter kompiliert Math.Sqrt () mit einigen Inline-Anweisungen.
Native C / C ++ ist mit jahrelanger Abwärtskompatibilität ausgestattet. Die Optionen / fp: präzise, / fp: schnell und / fp: strenge Kompilierung sind am sichtbarsten. Dementsprechend muss eine CRT-Funktion aufgerufen werden, die sqrt () implementiert und die ausgewählten Gleitkommaoptionen überprüft, um das Ergebnis anzupassen. Das ist langsam.
quelle
Ich bin ein C ++ - und ein C # -Entwickler. Ich habe seit der ersten Beta des .NET Frameworks C # -Anwendungen entwickelt und mehr als 20 Jahre Erfahrung in der Entwicklung von C ++ - Anwendungen. Erstens wird C # -Code NIEMALS schneller sein als eine C ++ - Anwendung, aber ich werde nicht lange auf verwalteten Code, seine Funktionsweise, die Inter-Op-Schicht, interne Speicherverwaltungsschichten, das dynamische Typsystem und den Garbage Collector eingehen. Lassen Sie mich dennoch weiter sagen, dass die hier aufgeführten Benchmarks alle zu FALSCHEN Ergebnissen führen.
Lassen Sie mich erklären: Das erste, was wir berücksichtigen müssen, ist der JIT-Compiler für C # (.NET Framework 4). Jetzt erzeugt die JIT nativen Code für die CPU unter Verwendung verschiedener Optimierungsalgorithmen (die tendenziell aggressiver sind als das mit Visual Studio gelieferte Standard-C ++ - Optimierungsprogramm), und der vom .NET JIT-Compiler verwendete Befehlssatz spiegelt die tatsächliche CPU genauer wider Auf der Maschine könnten bestimmte Ersetzungen im Maschinencode vorgenommen werden, um Taktzyklen zu reduzieren und die Trefferquote im CPU-Pipeline-Cache zu verbessern und weitere Hyper-Threading-Optimierungen wie die Neuordnung von Befehlen und Verbesserungen in Bezug auf die Verzweigungsvorhersage zu erzielen.
Dies bedeutet, dass Ihre C ++ - Anwendung möglicherweise langsamer als die entsprechende C # - oder .NET-basierte Anwendung ausgeführt wird, wenn Sie Ihre C ++ - Anwendung nicht mit den richtigen Pararmetern für den RELEASE-Build kompilieren (nicht für den DEBUG-Build). Stellen Sie beim Angeben der Projekteigenschaften in Ihrer C ++ - Anwendung sicher, dass Sie "vollständige Optimierung" und "schnellen Code bevorzugen" aktivieren. Wenn Sie einen 64-Bit-Computer haben, MÜSSEN Sie angeben, dass x64 als Zielplattform generiert werden soll. Andernfalls wird Ihr Code über eine Konvertierungsunterschicht (WOW64) ausgeführt, wodurch die Leistung erheblich verringert wird.
Sobald Sie die richtigen Optimierungen im Compiler durchgeführt haben, erhalte ich 0,72 Sekunden für die C ++ - Anwendung und 1,16 Sekunden für die C # -Anwendung (beide im Release-Build). Da die C # -Anwendung sehr einfach ist und den in der Schleife verwendeten Speicher auf dem Stapel und nicht auf dem Heap zuweist, ist sie tatsächlich viel leistungsfähiger als eine echte Anwendung, die an Objekten, umfangreichen Berechnungen und größeren Datenmengen beteiligt ist. Die angegebenen Zahlen sind also optimistische Zahlen, die auf C # und das .NET-Framework ausgerichtet sind. Trotz dieser Tendenz ist die C ++ - Anwendung in etwas mehr als der Hälfte der Zeit fertig als die entsprechende C # -Anwendung. Beachten Sie, dass der von mir verwendete Microsoft C ++ - Compiler nicht über die richtige Pipeline- und Hyperthreading-Optimierung verfügte (Verwenden von WinDBG zum Anzeigen der Montageanweisungen).
Wenn wir nun den Intel-Compiler verwenden (der übrigens ein Branchengeheimnis für die Generierung von Hochleistungsanwendungen auf AMD / Intel-Prozessoren ist), wird derselbe Code für die ausführbare C ++ - Datei in 0,54 Sekunden ausgeführt, für Microsoft Visual Studio 2010 in 0,72 Sekunden Am Ende sind die Endergebnisse für C ++ 0,44 Sekunden und für C # 1,16 Sekunden. Der vom .NET JIT-Compiler erzeugte Code dauert also 214% länger als die ausführbare C ++ - Datei. Die meiste Zeit in den .54 Sekunden wurde damit verbracht, die Zeit vom System zu erhalten und nicht innerhalb der Schleife selbst!
Was in der Statistik ebenfalls fehlt, sind die Start- und Bereinigungszeiten, die nicht in den Timings enthalten sind. C # -Anwendungen verbringen in der Regel viel mehr Zeit mit dem Starten und Beenden als C ++ - Anwendungen. Der Grund dafür ist kompliziert und hängt mit den Validierungsroutinen für den .NET-Laufzeitcode und dem Speicherverwaltungssubsystem zusammen, das zu Beginn (und folglich am Ende) des Programms viel Arbeit leistet, um die Speicherzuordnungen und den Müll zu optimieren Kollektor.
Bei der Messung der Leistung von C ++ und .NET IL ist es wichtig, den Assemblycode zu überprüfen, um sicherzustellen, dass ALLE Berechnungen vorhanden sind. Was ich gefunden habe, ist, dass ohne zusätzlichen Code in C # der größte Teil des Codes in den obigen Beispielen tatsächlich aus der Binärdatei entfernt wurde. Dies war auch bei C ++ der Fall, als Sie einen aggressiveren Optimierer wie den mit dem Intel C ++ - Compiler gelieferten verwendeten. Die Ergebnisse, die ich oben angegeben habe, sind zu 100% korrekt und werden auf Baugruppenebene validiert.
Das Hauptproblem bei vielen Foren im Internet ist, dass viele Neulinge Microsoft-Marketingpropaganda hören, ohne die Technologie zu verstehen, und falsche Behauptungen aufstellen, dass C # schneller als C ++ ist. Die Behauptung ist, dass C # theoretisch schneller als C ++ ist, weil der JIT-Compiler den Code für die CPU optimieren kann. Das Problem bei dieser Theorie ist, dass im .NET Framework viele Installationen vorhanden sind, die die Leistung verlangsamen. Sanitär, das in C ++ - Anwendung nicht vorhanden ist. Darüber hinaus kennt ein erfahrener Entwickler den richtigen Compiler für die jeweilige Plattform und verwendet beim Kompilieren der Anwendung die entsprechenden Flags. Auf Linux- oder Open Source-Plattformen ist dies kein Problem, da Sie Ihre Quelle verteilen und Installationsskripte erstellen können, die den Code mithilfe der entsprechenden Optimierung kompilieren. Auf Windows- oder Closed Source-Plattformen müssen Sie mehrere ausführbare Dateien mit jeweils spezifischen Optimierungen verteilen. Die Windows-Binärdateien, die bereitgestellt werden, basieren auf der vom MSI-Installationsprogramm erkannten CPU (mithilfe benutzerdefinierter Aktionen).
quelle
Meine erste Vermutung ist eine Compiler-Optimierung, da Sie nie root verwenden. Sie weisen es einfach zu und überschreiben es dann immer wieder.
Edit: verdammt, um 9 Sekunden schlagen!
quelle
Versuchen Sie, Ihren Code in zu ändern, um festzustellen, ob die Schleife entfernt wird
ähnlich im C-Code, und drucken Sie dann den Wert von root außerhalb der Schleife.
quelle
Möglicherweise bemerkt der c # -Compiler, dass Sie root nirgendwo verwenden, und überspringt einfach die gesamte for-Schleife. :) :)
Das mag nicht der Fall sein, aber ich vermute, was auch immer die Ursache ist, es hängt von der Implementierung des Compilers ab. Versuchen Sie, Ihr C-Programm mit dem Microsoft-Compiler (cl.exe, verfügbar als Teil des win32 sdk) mit Optimierungen und Freigabemodus zu kompilieren. Ich wette, Sie werden eine Perf-Verbesserung gegenüber dem anderen Compiler sehen.
EDIT: Ich glaube nicht, dass der Compiler die for-Schleife einfach optimieren kann, da er wissen müsste, dass Math.Sqrt () keine Nebenwirkungen hat.
quelle
Was auch immer der Zeitunterschied ist. kann sein, dass "verstrichene Zeit" ungültig ist. Es wäre nur dann gültig, wenn Sie garantieren können, dass beide Programme unter genau den gleichen Bedingungen ausgeführt werden.
Vielleicht sollten Sie einen Sieg versuchen. entspricht $ / usr / bin / time my_cprog; / usr / bin / time my_csprog
quelle
Ich habe (basierend auf Ihrem Code) zwei weitere vergleichbare Tests in C und C # zusammengestellt. Diese beiden schreiben ein kleineres Array mit dem Modul-Operator für die Indizierung (es erhöht den Overhead, aber hey, wir versuchen, die Leistung [auf einer groben Ebene] zu vergleichen).
C-Code:
In C #:
Diese Tests schreiben Daten in ein Array (daher sollte es der .NET-Laufzeit nicht gestattet sein, die sqrt-Operation auszusortieren), obwohl das Array erheblich kleiner ist (kein übermäßiger Speicher verwendet werden wollte). Ich habe diese in der Release-Konfiguration kompiliert und in einem Konsolenfenster ausgeführt (anstatt über VS zu starten).
Auf meinem Computer variiert das C # -Programm zwischen 6,2 und 6,9 Sekunden, während die C-Version zwischen 6,9 und 7,1 variiert.
quelle
Wenn Sie den Code auf Assembly-Ebene nur in einem Schritt ausführen, einschließlich des Durchlaufens der Quadratwurzel-Routine, erhalten Sie wahrscheinlich die Antwort auf Ihre Frage.
Keine Notwendigkeit für fundiertes Raten.
quelle
Der andere Faktor, der hier ein Problem sein kann, ist, dass der C-Compiler zu generischem nativem Code für die Prozessorfamilie kompiliert, auf die Sie abzielen, während die MSIL, die beim Kompilieren des C # -Codes generiert wurde, dann JIT kompiliert wird, um auf den genauen Prozessor abzuzielen, den Sie mit einem beliebigen Prozessor abgeschlossen haben Optimierungen, die möglich sein könnten. Daher kann der aus dem C # generierte native Code erheblich schneller sein als der C.
quelle
Es scheint mir, dass dies nichts mit den Sprachen selbst zu tun hat, sondern vielmehr mit den verschiedenen Implementierungen der Quadratwurzelfunktion.
quelle
Eigentlich Jungs, die Schleife wird NICHT weg optimiert. Ich habe Johns Code kompiliert und die resultierende .exe untersucht. Die Eingeweide der Schleife sind wie folgt:
Es sei denn, die Laufzeit ist intelligent genug, um zu erkennen, dass die Schleife nichts tut und sie überspringt?
Bearbeiten: Ändern des C # in:
Die Ergebnisse in der verstrichenen Zeit (auf meinem Computer) liegen zwischen 0,047 und 2,17. Aber ist das nur der Aufwand für das Hinzufügen von 100 Millionen Additionsbetreibern?
quelle