Comenzaré por estar en desacuerdo con parte de la respuesta aceptada (y bien votada) a esta pregunta al afirmar:
En realidad, hay muchas razones por las cuales el código JITted se ejecutará más lentamente que un programa C ++ (u otro lenguaje sin sobrecarga de tiempo de ejecución) correctamente optimizado, incluyendo:
los ciclos de cálculo gastados en el código JITting en tiempo de ejecución no están, por definición, disponibles para su uso en la ejecución del programa.
cualquier ruta activa en el JITter estará compitiendo con su código por instrucciones y caché de datos en la CPU. Sabemos que el caché domina cuando se trata de rendimiento y los lenguajes nativos como C ++ no tienen este tipo de contención, por definición.
El presupuesto de tiempo de un optimizador de tiempo de ejecución está necesariamente mucho más limitado que el de un optimizador de tiempo de compilación (como señaló otro comentarista)
En pocas palabras: En última instancia, será casi seguro que será capaz de crear una implementación más rápida en C ++ de lo que podía en C # .
Ahora, dicho esto, cuánto más rápido realmente no es cuantificable, ya que hay demasiadas variables: la tarea, el dominio del problema, el hardware, la calidad de las implementaciones y muchos otros factores. Deberá realizar pruebas en su escenario para determinar la diferencia en el rendimiento y luego decidir si vale la pena el esfuerzo y la complejidad adicionales.
Este es un tema muy largo y complejo, pero creo que vale la pena mencionar, en aras de la exhaustividad, que el optimizador de tiempo de ejecución de C # es excelente y es capaz de realizar ciertas optimizaciones dinámicas en tiempo de ejecución que simplemente no están disponibles para C ++ con su tiempo de compilación ( estático) optimizador. Incluso con esto, la ventaja sigue siendo típicamente profunda en la corte de la aplicación nativa, pero el optimizador dinámico es la razón del calificador " casi seguro" dado anteriormente.
-
En términos de rendimiento relativo, también me perturbaron las cifras y las discusiones que vi en algunas otras respuestas, así que pensé en intervenir y, al mismo tiempo, brindar algo de apoyo para las declaraciones que hice anteriormente.
Una gran parte del problema con esos puntos de referencia es que no puede escribir código C ++ como si estuviera escribiendo C # y esperar obtener resultados representativos (por ejemplo, realizar miles de asignaciones de memoria en C ++ le dará números terribles).
En cambio, escribí un código C ++ un poco más idiomático y lo comparé con el código C # que @Wiory proporcionó. Los dos cambios principales que hice al código C ++ fueron:
1) vector usado :: reserve ()
2) aplanó la matriz 2d a 1d para lograr una mejor ubicación de caché (bloque contiguo)
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);
}
Tiempo de ejecución (lanzamiento): 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);
}
Tiempo de ejecución (Release): Init: 398µs (sí, eso es microsegundos), Fill: 152ms
Tiempo total de ejecución: C #: 289 ms, C ++ 152 ms (aproximadamente 90% más rápido)
Observaciones
Cambiar la implementación de C # a la misma implementación de matriz 1d produjo Init: 40ms, Fill: 171ms, Total: 211ms ( C ++ todavía era casi un 40% más rápido ).
Es mucho más difícil diseñar y escribir código "rápido" en C ++ que escribir código "regular" en cualquier idioma.
Es (quizás) asombrosamente fácil obtener un bajo rendimiento en C ++; vimos eso con el rendimiento de vectores sin reservas. Y hay muchas trampas como esta.
El rendimiento de C # es bastante sorprendente si se considera todo lo que sucede en tiempo de ejecución. Y ese rendimiento es relativamente fácil de acceder.
Más datos anecdóticos que comparan el rendimiento de C ++ y C #: https://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=gpp&lang2=csharpcore
La conclusión es que C ++ le brinda mucho más control sobre el rendimiento. ¿Quieres usar un puntero? ¿Una referencia? ¿Pila de memoria? ¿Montón? ¿Polimorfismo dinámico o eliminar la sobrecarga de tiempo de ejecución de una tabla virtual con polimorfismo estático (a través de plantillas / CRTP)? En C ++ que tiene que ... er, llegar a realizar todas estas opciones (y más) a sí mismo, a ser posible para que sus soluciones mejores direcciones el problema que está abordaje.
Pregúntese si realmente desea o necesita ese control, porque incluso para el ejemplo trivial anterior, puede ver que aunque hay una mejora significativa en el rendimiento, requiere una inversión más profunda para acceder.