2010-03-23 16 views
5

Me preocupaba la velocidad de C# cuando se trata de cálculos pesados, cuando se necesita utilizar la potencia bruta de la CPU.velocidad de C++ yC# en comparación con

Siempre pensé que C++ es mucho más rápido que C# en lo que respecta a los cálculos. Así que hice algunas pruebas rápidas. La primera prueba calcula los números primos < un entero n, la segunda prueba calcula algunos números pandigitales. La idea de la segunda prueba viene de aquí: Pandigital Numbers

C# primer cálculo:

using System; 
using System.Diagnostics; 

class Program 
{ 

    static int primes(int n) 
    { 

     uint i, j; 
     int countprimes = 0; 

     for (i = 1; i <= n; i++) 
     { 
      bool isprime = true; 

      for (j = 2; j <= Math.Sqrt(i); j++) 

       if ((i % j) == 0) 
       { 
        isprime = false; 
        break; 
       } 

      if (isprime) countprimes++; 
     } 

     return countprimes; 
    } 



    static void Main(string[] args) 
    { 
     int n = int.Parse(Console.ReadLine()); 
     Stopwatch sw = new Stopwatch(); 

     sw.Start(); 
     int res = primes(n); 
     sw.Stop(); 
     Console.WriteLine("I found {0} prime numbers between 0 and {1} in {2} msecs.", res, n, sw.ElapsedMilliseconds); 
     Console.ReadKey(); 
    } 
} 

C++ variante:

#include <iostream> 
#include <ctime> 
#include <cmath> 

int primes(unsigned long n) { 
unsigned long i, j; 
int countprimes = 0; 
    for(i = 1; i <= n; i++) { 
     int isprime = 1; 
     for(j = 2; j < sqrt((float)i); j++) 
      if(!(i%j)) { 
     isprime = 0; 
     break; 
    } 
    countprimes+= isprime; 
    } 
    return countprimes; 
} 

int main() { 
int n, res; 
cin>>n; 
unsigned int start = clock(); 

res = primes(n); 
int tprime = clock() - start; 
cout<<"\nI found "<<res<<" prime numbers between 1 and "<<n<<" in "<<tprime<<" msecs."; 
return 0; 
} 

Cuando me encontré con la prueba tratando de encontrar números primos < de 100.000, C# variante terminó en 0,409 segundos y la variante C++ en 0,614 segundos. Cuando los ejecuté para 1,000,000 C# terminaron en 6.039 segundos y C++ en aproximadamente 12.987 segundos.

prueba Pandigital en C#:

using System; 
using System.Diagnostics; 

class Program 
{ 
    static bool IsPandigital(int n) 
    { 
     int digits = 0; int count = 0; int tmp; 

     for (; n > 0; n /= 10, ++count) 
     { 
      if ((tmp = digits) == (digits |= 1 << (n - ((n/10) * 10) - 1))) 
       return false; 
     } 

     return digits == (1 << count) - 1; 
    } 

    static void Main() 
    { 
     int pans = 0; 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 

     for (int i = 1; i <= 123456789; i++) 
     { 
      if (IsPandigital(i)) 
      { 
       pans++; 
      } 
     } 
     sw.Stop(); 
     Console.WriteLine("{0}pcs, {1}ms", pans, sw.ElapsedMilliseconds); 
     Console.ReadKey(); 
    } 
} 

prueba Pandigital en C++:

#include <iostream> 
#include <ctime> 

using namespace std; 

int IsPandigital(int n) 
    { 
     int digits = 0; int count = 0; int tmp; 

     for (; n > 0; n /= 10, ++count) 
     { 
      if ((tmp = digits) == (digits |= 1 << (n - ((n/10) * 10) - 1))) 
       return 0; 
     } 

     return digits == (1 << count) - 1; 
    } 


int main() { 
    int pans = 0; 
    unsigned int start = clock(); 

    for (int i = 1; i <= 123456789; i++) 
    { 
     if (IsPandigital(i)) 
     { 
     pans++; 
     } 
    } 
    int ptime = clock() - start; 
    cout<<"\nPans:"<<pans<<" time:"<<ptime; 
    return 0; 
} 

C# variante se ejecuta en 29.906 segundos y C++ en alrededor de 36,298 segundos.

No toqué ningún conmutador del compilador y se compilaron los programas C# y C++ con las opciones de depuración. Antes de intentar ejecutar la prueba, estaba preocupado de que C# quedara muy por detrás de C++, pero ahora parece que hay una diferencia de velocidad bastante grande en C#.

¿Alguien puede explicar esto? C# está jitted y C++ está compilado nativo, por lo que es normal que un C++ sea más rápido que una variante de C#.

Gracias por las respuestas!

He rehecho todas las pruebas para la configuración de la versión.

Primera prueba (números primos)

C# (números < 100,0000): 0.189 segundos C++ (números < 100.0000): 0.036 segundos

C# (nummbers < 1.000.000): 5.300 segundos C++ (nummbers < 1000000): 1.166 segundos

segunda prueba (números Pandigital):

C#: 21. 224 segundos C++: 4.104 segundos

Por lo tanto, todo ha cambiado, ahora C++ es mucho más rápido. Mi error es que realicé la prueba de configuración de depuración. ¿Puedo ver alguna mejora en la velocidad si ejecuto los ejecutables de C# a través de ngen?

La razón por la que traté de comparar C# y C++ es porque conozco algunos conceptos básicos de ambos y quería aprender una API que tratara con la GUI.Estaba pensando que WPF es agradable, por lo tanto, dado que me estoy enfocando en el escritorio, quería ver si C# puede ofrecer suficiente velocidad y rendimiento cuando se trata de usar potencia pura de CPU para calcular varios cálculos (archivadores de archivos, criptografía, códecs, etc.) . Pero parece que lamentablemente C# no puede seguir el ritmo de C++ cuando se trata de velocidad.

Por lo tanto, supongo que siempre quedaré atrapado con esta pregunta Tough question on WPF, Win32, MFC, y buscaré una API adecuada.

+5

"¿Alguien puede explicar esto? C# está jitted y C++ está compilado nativo, por lo que es normal que un C++ sea más rápido que una variante C#." <- No en modo de depuración. –

+6

"... los programas de C++ se compilaron con opciones de depuración". Entonces, ¿por qué nos preocupamos por el rendimiento? – GManNickG

+4

'= 2; j <(i^(1/2)); j ++) 'Este código es incorrecto. Lo estás haciendo en modo bit, o no como exponenciador. –

Respuesta

8

¿Por qué asumir que el código jitted es más lento que el código nativo? La única penalización de velocidad sería el jitting real, que solo ocurre una vez (generalmente hablando). Dado un programa con un tiempo de ejecución de 30 segundos, estamos hablando de una porción minúscula del costo total.

Creo que puede confundir el código jitted con interpretó el código, que se compila línea por línea. Hay una diferencia bastante significativa entre los dos.

Como han señalado otros, también debe ejecutar esto en modo de lanzamiento; el modo de depuración convierte a en más optimizaciones, por lo que ambas versiones serán más lentas de lo que deberían (pero en cantidades diferentes).

Editar - Debo señalar una cosa, y es que esta línea:

for (j = 2; j <= Math.Sqrt(i); j++) 

es muy ineficiente y puede interferir con la evaluación comparativa. Debería calcular Math.Sqrt(i)fuera de del bucle interno. Es posible que esto ralentice ambas versiones en una cantidad equivalente, pero no estoy seguro, diferentes compiladores realizarán diferentes optimizaciones.

+1

+1. C++ sufre más por las optimizaciones desactivadas porque se basa en la inlineación para ser eficiente. Además, el JIT realizará algunas optimizaciones en C# incluso si el binario se construyó en modo de depuración. –

+2

No solo desactiva la mayoría de las optimizaciones, sino que puede agregar un montón de errores extraños. Visual Studio, por ejemplo, agrega control de desbordamiento, control de montón y otras cosas interesantes en la configuración predeterminada del modo de depuración. – kibibu

4

Vuelva a compilar el programa C++ con las optimizaciones completas activadas y vuelva a ejecutar las pruebas. El jit de C# optimizará el código cuando esté jodido, por lo que comparó el código optimizado C#/.NET con C++ no optimizado.

11

Necesita compilar C++ en modo de lanzamiento y habilitar optimizaciones para obtener los resultados de rendimiento que está buscando.

+0

Voy a intentar eso también. – Mack

12

el generador privilegiada en C++ no es correcto

i^(1/2) == i xor 0

^es el operador XOR bit a bit y/es la división de enteros.

primera edición, es correcto, pero ineficiente: Desde i xor 0 == i, el tamiz no se detiene en sqrt (i), pero en i.

segunda edición:

El tamizado se puede hacer un poco más eficiente. (Solo necesita calcular sqrt (n)). Esta es la forma en que he implementado la criba de Eratóstenes para mi propio uso (esto es en C99 sin embargo):

void sieve(const int n, unsigned char* primes) 
{ 
     memset(primes, 1, (n+1) * sizeof(unsigned char)); 

     // sieve of eratosthenes 
     primes[0] = primes[1] = 0; 
     int m = floor(sqrt(n)); 
     for (int i = 2; i <= m; i++) 
       if (primes[i]) // no need to remove multiples of i if it is not prime 
         for (int j = i; j <= (n/i); j++) 
           primes[i*j] = 0; 
} 
+0

Lo corrigí. Gracias. – Mack

+0

Me pregunto, sin embargo, si esto realmente lo hace funcionar más lento, porque la versión de C# tiene que evaluar 'Math.Sqrt (i)' en cada iteración, que es mucho más caro que el XOR de instrucción única, así que este pequeño error podría hacer que la versión de C++ * sea más rápida * porque ambas versiones del programa son ineficientes. – Aaronaught

+2

@Aaronaught: Excepto que la versión de C# está en bucle en Sqrt (i) y la versión de C++ está en bucle en i. –

2

primer lugar, nunca hacer esos puntos de referencia en el modo de depuración. Para obtener números significativos siempre use el modo de lanzamiento.

El JIT tiene la ventaja de conocer la plataforma en la que se ejecuta, mientras que el código precompilado puede no ser óptimo para la plataforma en la que se ejecuta.

+1

Tal vez, pero por supuesto está la sobrecarga de hacer la compilación en esa plataforma. –

6

Toma mucho más tiempo porque el algoritmo es incorrecto.

for(j = 2; j < (i^(1/2)); j++) 

es lo mismo que

for(j = 2; j < (i^0); j++) 

es lo mismo que

for(j = 2; j < i; j++) 

i es mucho más grande que sqrt (i). Mirando solo el tiempo de ejecución, es un orden de magnitud mayor que debería ser en la implementación de C++.

Además, como todos los demás dicen, no creo que tenga sentido realizar pruebas de rendimiento en modo de depuración.

+0

He corregido el error, lo siento. Trataré de ejecutarlos en modo de lanzamiento también. – Mack

0

Ambas pruebas no son válidas porque compiló sin optimizaciones.

La primera prueba no tiene sentido, incluso como una comparación de comportamiento no optimizado, debido a un error en el código; Math.Sqrt(i) devuelve la raíz cuadrada de i, i^(1/2) me devuelve i - por lo que el C++ está haciendo mucho más trabajo que el C#.

De manera más general, esto no es una tarea útil, estás tratando de crear un punto de referencia sintético que tenga poca o ninguna relación con el uso en el mundo real.

2

Es un mito persistente que el compilador JIT en código administrado genera código de máquina que es mucho menos eficiente que el generado por un compilador C/C++. El código administrado generalmente gana en administración de memoria y matemática en coma flotante, C/C++ generalmente gana cuando el optimizador de código puede pasar mucho más tiempo optimizando el código. En general, el código administrado es de alrededor del 80%, pero depende por completo del 10% del código donde un programa gasta el 90% de su tiempo.

Su prueba no lo mostrará, no habilitó el optimizador y no hay mucho que optimizar.

+0

"El código administrado generalmente gana en la administración de la memoria" <- Debido a que los programadores C/C++ son generalmente flojos y simplemente usan búferes gigantes en lugar de algo como un 'std :: vector ' que es común en los lenguajes JIT. Las velocidades del código compilado y el código JIT deberían ser casi las mismas, todo dicho y hecho, siempre que descontéis el tiempo que lleva cargar el jitter en la memoria (que puede ser bastante grande en el caso de Java: P) +1 –

+1

El código administrado difícilmente puede ganar en operaciones de punto flotante porque el .NET JIT actual no emite ninguna instrucción SSE. –

+0

Hay más de uno, el x64 sí. Ejemplo: http://stackoverflow.com/questions/686483/c-vs-c-big-performance-difference/687741#687741 –

1

chicos, antes de comparar la velocidad del programa de uno a otro, por favor molestaos en leer varios artículos sobre instrucciones de CPU, ensamblaje, gestión de caché, etc. Y el autor es simplemente un amigo ridículamente divertido. Comprobando el rendimiento de las compilaciones de depuración.

Billy O'Neal: ¿cuál es la diferencia entre asignar un gran búfer y usar solo una pequeña parte de él y usar cosas asignadas dinámicamente como vector en palabras de bajo lenguaje? Una vez que se asignó el búfer grande, nadie se preocupa por las cosas que no se usaron. No se necesitan más operaciones de soporte. Mientras que para cosas dinámicas como el vector, la comprobación constante de los límites de memoria requiere que no se escape. Recuerde, los programadores de C++ no son perezosos (lo cual es cierto, lo admito), pero también son inteligentes.

0

¿Qué tal esto:

for(sqrti = 1; sqrti <= 11112; sqrti++) { 
    int nexti = (1+sqrti)*(1+sqrti) 
    for (i = sqrti*sqrti; i < nexti; i++) 
    { 
    int isprime = 1; 
    for(j = 2; j < sqrti; j++) 
     if(!(i%j)) { 
     isprime = 0; 
     break; 
    } 
    } 

} countprimes + = ISPrime; }

Cuestiones relacionadas