Zacznę od tego, że nie zgadzam się z częścią zaakceptowanej (i dobrze ocenionej) odpowiedzi na to pytanie, stwierdzając:
Istnieje wiele powodów, dla których kod JITted będzie działał wolniej niż odpowiednio zoptymalizowany program C ++ (lub inny język bez narzutów), w tym:
cykle obliczeniowe spędzone na kodzie JITting w czasie wykonywania są z definicji niedostępne do użycia podczas wykonywania programu.
wszelkie ścieżki dostępu w JITter będą konkurować z twoim kodem o instrukcje i pamięć podręczną danych w CPU. Wiemy, że pamięć podręczna dominuje pod względem wydajności, a języki rodzime, takie jak C ++, z definicji nie mają tego rodzaju rywalizacji.
budżet czasowy optymalizatora czasu działania jest z konieczności znacznie bardziej ograniczony niż budżet optymalizatora czasu kompilacji (jak zauważył inny komentator)
Konkluzja: W ostatecznym rozrachunku, to będzie prawie na pewno będzie w stanie stworzyć szybszą realizację w C ++ niż można w C # .
To powiedziawszy, o ile szybciej tak naprawdę nie da się kwantyfikować, ponieważ istnieje zbyt wiele zmiennych: zadanie, dziedzina problemów, sprzęt, jakość implementacji i wiele innych czynników. Przeprowadzisz testy swojego scenariusza, aby określić różnicę w wydajności, a następnie zdecydujesz, czy warto dodatkowy wysiłek i złożoność.
Jest to bardzo długi i złożony temat, ale uważam, że warto wspomnieć ze względu na kompletność, że optymalizator środowiska wykonawczego C # jest doskonały i jest w stanie przeprowadzić pewne dynamiczne optymalizacje w czasie wykonywania, które po prostu nie są dostępne dla C ++ ze względu na czas kompilacji ( statyczny) optymalizator. Mimo to przewaga jest zwykle głęboko w sądzie natywnej aplikacji, ale dynamiczny optymalizator jest przyczyną „ prawie podanego wyżej kwalifikatora pewno”.
-
Jeśli chodzi o względną wydajność, niepokoiły mnie również liczby i dyskusje, które widziałem w innych odpowiedziach, więc pomyślałem, że włączy się i jednocześnie zapewnię wsparcie dla oświadczeń, które wypowiedziałem powyżej.
Ogromną częścią problemu z tymi testami porównawczymi jest to, że nie możesz napisać kodu C ++ tak, jakbyś pisał w C # i spodziewał się uzyskania reprezentatywnych wyników (np. Wykonanie tysięcy alokacji pamięci w C ++ da ci straszne liczby).
Zamiast tego napisałem nieco więcej idiomatycznego kodu C ++ i porównałem go z podanym kodem C # @Wiory. Dwie główne zmiany, które wprowadziłem w kodzie C ++ to:
1) używany wektor :: Reserve ()
2) spłaszczono tablicę 2d do 1d, aby uzyskać lepszą lokalizację pamięci podręcznej (ciągły blok)
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);
}
Czas pracy (Release): Init: 124ms, Fill: 165ms
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);
}
Czas pracy (wydanie): Init: 398µs (tak, to mikrosekundy), Fill: 152ms
Całkowity czas pracy: C #: 289ms, C ++ 152ms (około 90% szybciej)
Spostrzeżenia
Zmiana implementacji C # na tę samą implementację tablicy 1d dała Init: 40ms, Fill: 171ms, Total: 211ms ( C ++ był wciąż prawie 40% szybszy ).
Znacznie trudniej jest zaprojektować i napisać „szybki” kod w C ++ niż napisać „zwykły” kod w dowolnym języku.
Zadziwiająco łatwo jest uzyskać słabą wydajność w C ++; widzieliśmy to przy niezastrzeżonej wydajności wektorów. I jest wiele takich pułapek.
Wydajność C # jest raczej niesamowita, jeśli wziąć pod uwagę wszystko, co dzieje się w czasie wykonywania. Ta wydajność jest stosunkowo łatwo dostępna.
Więcej niepotwierdzonych danych porównujących wydajność C ++ i C #: https://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=gpp&lang2=csharpcore
Najważniejsze jest to, że C ++ daje znacznie większą kontrolę nad wydajnością. Czy chcesz użyć wskaźnika? Referencja? Pamięć stosu? Sterta? Dynamiczny polimorfizm czy wyeliminować obciążenie środowiska wykonawczego vtable ze statycznym polimorfizmem (poprzez szablony / CRTP)? W C ++ musisz ... er, dostać się dołożenia wszelkich tych wyborów (i więcej) samemu, najlepiej tak, że najlepsze rozwiązanie odnosi się do problemu jesteś Walka.
Zadaj sobie pytanie, czy naprawdę chcesz lub potrzebujesz tej kontroli, ponieważ nawet w powyższym trywialnym przykładzie widać, że chociaż istnieje znaczna poprawa wydajności, dostęp do niej wymaga głębszej inwestycji.