Ich werde damit beginnen, einem Teil der akzeptierten (und gut bewerteten) Antwort auf diese Frage nicht zuzustimmen, indem ich sage:
Es gibt tatsächlich viele Gründe, warum JITted-Code langsamer ausgeführt wird als ein ordnungsgemäß optimiertes C ++ - Programm (oder eine andere Sprache ohne Laufzeit-Overhead), darunter:
Rechenzyklen, die zur Laufzeit für JITting-Code aufgewendet werden, sind per Definition für die Verwendung bei der Programmausführung nicht verfügbar.
Alle Hot Paths im JITter konkurrieren mit Ihrem Code um Anweisungen und Datencache in der CPU. Wir wissen, dass der Cache die Leistung dominiert und Muttersprachen wie C ++ per Definition diese Art von Konflikten nicht haben.
Das Zeitbudget eines Laufzeitoptimierers ist notwendigerweise viel eingeschränkter als das eines Optimierers zur Kompilierungszeit (wie ein anderer Kommentator hervorhob).
Fazit: Letztendlich können Sie mit ziemlicher Sicherheit eine schnellere Implementierung in C ++ erstellen als in C # .
Nun, wie viel schneller wirklich ist, ist nicht quantifizierbar, da es zu viele Variablen gibt: Aufgabe, Problemdomäne, Hardware, Qualität der Implementierungen und viele andere Faktoren. Sie haben Tests für Ihr Szenario durchgeführt, um den Leistungsunterschied festzustellen und dann zu entscheiden, ob sich der zusätzliche Aufwand und die Komplexität lohnen.
Dies ist ein sehr langes und komplexes Thema, aber der Vollständigkeit halber sollte erwähnt werden, dass das Laufzeitoptimierungsprogramm von C # hervorragend ist und bestimmte dynamische Optimierungen zur Laufzeit durchführen kann, die C ++ mit seiner Kompilierungszeit einfach nicht zur Verfügung stehen ( statischer) Optimierer. Trotzdem liegt der Vorteil in der Regel immer noch tief im Gericht der nativen Anwendung, aber der dynamische Optimierer ist der Grund für das oben angegebene Qualifikationsmerkmal "mit ziemlicher Sicherheit".
- -
In Bezug auf die relative Leistung war ich auch beunruhigt über die Zahlen und Diskussionen, die ich in einigen anderen Antworten gesehen habe. Ich dachte, ich würde mich einschalten und gleichzeitig die Aussagen unterstützen, die ich oben gemacht habe.
Ein großer Teil des Problems mit diesen Benchmarks besteht darin, dass Sie keinen C ++ - Code schreiben können, als ob Sie C # schreiben würden, und repräsentative Ergebnisse erwarten würden (z. B. wenn Sie Tausende von Speicherzuweisungen in C ++ durchführen, erhalten Sie schreckliche Zahlen.)
Stattdessen habe ich etwas mehr idiomatischen C ++ - Code geschrieben und mit dem bereitgestellten C # -Code @Wiory verglichen. Die zwei wichtigsten Änderungen, die ich am C ++ - Code vorgenommen habe, waren:
1) verwendeter Vektor :: Reserve ()
2) Reduzierte das 2d-Array auf 1d, um eine bessere Cache-Lokalität zu erreichen (zusammenhängender Block).
C # (.NET 4.6.1)
private static void TestArray()
{
const int rows = 5000;
const int columns = 9000;
DateTime t1 = System.DateTime.Now;
double[][] arr = new double[rows][];
for (int i = 0; i < rows; i++)
arr[i] = new double[columns];
DateTime t2 = System.DateTime.Now;
Console.WriteLine(t2 - t1);
t1 = System.DateTime.Now;
for (int i = 0; i < rows; i++)
for (int j = 0; j < columns; j++)
arr[i][j] = i;
t2 = System.DateTime.Now;
Console.WriteLine(t2 - t1);
}
Laufzeit (Release): Init: 124 ms, Fill: 165 ms
C ++ 14 (Clang v3.8 / C2)
#include <iostream>
#include <vector>
auto TestSuite::ColMajorArray()
{
constexpr size_t ROWS = 5000;
constexpr size_t COLS = 9000;
auto initStart = std::chrono::steady_clock::now();
auto arr = std::vector<double>();
arr.reserve(ROWS * COLS);
auto initFinish = std::chrono::steady_clock::now();
auto initTime = std::chrono::duration_cast<std::chrono::microseconds>(initFinish - initStart);
auto fillStart = std::chrono::steady_clock::now();
for(auto i = 0, r = 0; r < ROWS; ++r)
{
for (auto c = 0; c < COLS; ++c)
{
arr[i++] = static_cast<double>(r * c);
}
}
auto fillFinish = std::chrono::steady_clock::now();
auto fillTime = std::chrono::duration_cast<std::chrono::milliseconds>(fillFinish - fillStart);
return std::make_pair(initTime, fillTime);
}
Laufzeit (Release): Init: 398µs (ja, das sind Mikrosekunden), Fill: 152ms
Gesamtlaufzeiten: C #: 289 ms, C ++ 152 ms (ungefähr 90% schneller)
Beobachtungen
Das Ändern der C # -Implementierung auf dieselbe 1d-Array-Implementierung ergab Init: 40 ms, Fill: 171 ms, Total: 211 ms ( C ++ war immer noch fast 40% schneller ).
Es ist viel schwieriger, "schnellen" Code in C ++ zu entwerfen und zu schreiben, als "normalen" Code in beiden Sprachen zu schreiben.
Es ist (vielleicht) erstaunlich einfach, in C ++ eine schlechte Leistung zu erzielen. Wir haben das mit uneingeschränkter Vektorleistung gesehen. Und es gibt viele solche Fallstricke.
Die Leistung von C # ist ziemlich erstaunlich, wenn man bedenkt, was zur Laufzeit vor sich geht. Und diese Leistung ist vergleichsweise leicht zugänglich.
Weitere anekdotische Daten zum Vergleich der Leistung von C ++ und C #: https://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=gpp&lang2=csharpcore
Unter dem Strich gibt Ihnen C ++ viel mehr Kontrolle über die Leistung. Möchten Sie einen Zeiger verwenden? Eine Referenz? Speicher stapeln? Haufen? Dynamischer Polymorphismus oder Eliminieren des Laufzeitaufwands einer vtable mit statischem Polymorphismus (über Templates / CRTP)? In C ++ müssen Sie ... äh, bekommen alle diese Entscheidungen (und mehr) selbst, am besten so , dass Ihre Lösung am besten Adressen das Problem Sie anpacken zu machen.
Fragen Sie sich, ob Sie diese Steuerung tatsächlich möchten oder benötigen, denn selbst für das obige triviale Beispiel können Sie feststellen, dass die Leistung zwar erheblich verbessert wird, für den Zugriff jedoch eine tiefere Investition erforderlich ist.