remarks - C#contra C-Gran diferencia de rendimiento
see cref c# (13)
Como nunca usas ''root'', es posible que el compilador haya eliminado la llamada para optimizar tu método.
Podría intentar acumular los valores de la raíz cuadrada en un acumulador, imprimirlo al final del método y ver qué está sucediendo.
Editar: vea la respuesta de Jalf a continuación
Estoy encontrando diferencias de rendimiento masivas entre códigos similares en C anc C #.
El código C es:
#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);
}
Y la C # (aplicación de consola) es:
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));
}
}
}
Con el código anterior, el C # completa en 0.328125 segundos (versión de lanzamiento) y el C tarda 11.14 segundos en ejecutarse.
El c se está compilando a un ejecutable de Windows usando mingw.
Siempre he supuesto que C / C ++ era más rápido o al menos comparable con C # .net. ¿Qué está haciendo exactamente que la C se ejecute más de 30 veces más lenta?
EDITAR: Parece que el optimizador de C # estaba eliminando la raíz, ya que no se estaba utilizando. Cambié la asignación de raíz a root + = e imprimí el total al final. También compilé el C usando cl.exe con el indicador / O2 establecido para la velocidad máxima.
Los resultados son ahora: 3.75 segundos para C 2.61 segundos para C #
La C todavía tarda más, pero esto es aceptable
Cualquiera que sea el tiempo dif. puede ser que ese "tiempo transcurrido" no sea válido. Solo sería válido si puede garantizar que ambos programas se ejecutan bajo las mismas condiciones.
Tal vez deberías probar una victoria. equivalente a $ / usr / bin / time my_cprog; / usr / bin / time my_csprog
Debe comparar construcciones de depuración. Acabo de compilar tu código C, y obtuve
Time elapsed: 0.000000
Si no habilita las optimizaciones, cualquier evaluación comparativa que realice es completamente inútil. (Y si habilita las optimizaciones, el ciclo se optimiza. Por lo tanto, su código de evaluación comparativa también tiene fallas. Debe obligarlo a ejecutar el ciclo, generalmente sumando el resultado o similar e imprimiéndolo al final)
Parece que lo que estás midiendo es básicamente "qué compilador inserta la mayor parte de la depuración". Y resulta que la respuesta es C. Pero eso no nos dice qué programa es el más rápido. Porque cuando quieres velocidad, habilitas las optimizaciones.
Por cierto, te ahorrarás muchos dolores de cabeza a largo plazo si abandonas cualquier noción de idiomas que son "más rápidos" que los demás. C # no tiene más velocidad que el inglés.
Hay ciertas cosas en el lenguaje C que serían eficientes incluso en un compilador ingenuo que no optimiza, y hay otras que dependen en gran medida de un compilador para optimizar todo. Y, por supuesto, lo mismo aplica para C # o cualquier otro idioma.
La velocidad de ejecución está determinada por:
- la plataforma en la que se ejecuta (sistema operativo, hardware, otro software que se ejecuta en el sistema)
- el compilador
- su código fuente
Un buen compilador de C # arrojará un código eficiente. Un mal compilador de C generará código lento. ¿Qué pasa con un compilador de C que generó código C #, que luego podría ejecutar a través de un compilador C #? ¿Qué tan rápido correría eso? Los idiomas no tienen velocidad. Tu código si.
El otro factor que puede ser un problema aquí es que el compilador de C compila el código genérico nativo para la familia de procesadores a la que se dirige, mientras que el MSIL generado cuando compiló el código C # se compila luego para compilar exactamente el procesador que ha completado con cualquier optimizaciones que pueden ser posibles. Por lo tanto, el código nativo generado a partir del C # puede ser considerablemente más rápido que el C.
En realidad chicos, el bucle NO se está optimizando. Recopilé el código de John y examiné el .exe resultante. Las agallas del bucle son las siguientes:
IL_0005: stloc.0
IL_0006: ldc.i4.0
IL_0007: stloc.1
IL_0008: br.s IL_0016
IL_000a: ldloc.1
IL_000b: conv.r8
IL_000c: call float64 [mscorlib]System.Math::Sqrt(float64)
IL_0011: pop
IL_0012: ldloc.1
IL_0013: ldc.i4.1
IL_0014: add
IL_0015: stloc.1
IL_0016: ldloc.1
IL_0017: ldc.i4 0x5f5e100
IL_001c: ble.s IL_000a
¿A menos que el tiempo de ejecución sea lo suficientemente inteligente como para darse cuenta de que el ciclo no hace nada y se salta?
Editar: Cambiar el C # para ser:
static void Main(string[] args)
{
DateTime startTime = DateTime.Now;
double root = 0.0;
for (int i = 0; i <= 100000000; i++)
{
root += Math.Sqrt(i);
}
System.Console.WriteLine(root);
TimeSpan runTime = DateTime.Now - startTime;
Console.WriteLine("Time elapsed: " +
Convert.ToString(runTime.TotalMilliseconds / 1000));
}
Resultados en el tiempo transcurrido (en mi máquina) pasando de 0.047 a 2.17. Pero, ¿es solo la sobrecarga de agregar 100 millones de operadores adicionales?
Junté (según tu código) dos pruebas más comparables en C y C #. Estos dos escriben una matriz más pequeña utilizando el operador de módulo para la indexación (agrega un poco de sobrecarga, pero bueno, estamos tratando de comparar el rendimiento [en un nivel bruto]).
Código C:
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
void main()
{
int count = (int)1e8;
int subcount = 1000;
double* roots = (double*)malloc(sizeof(double) * subcount);
clock_t start = clock();
for (int i = 0 ; i < count; i++)
{
roots[i % subcount] = sqrt((double)i);
}
clock_t end = clock();
double length = ((double)end - start) / CLOCKS_PER_SEC;
printf("Time elapsed: %f/n", length);
}
Cª#:
using System;
namespace CsPerfTest
{
class Program
{
static void Main(string[] args)
{
int count = (int)1e8;
int subcount = 1000;
double[] roots = new double[subcount];
DateTime startTime = DateTime.Now;
for (int i = 0; i < count; i++)
{
roots[i % subcount] = Math.Sqrt(i);
}
TimeSpan runTime = DateTime.Now - startTime;
Console.WriteLine("Time elapsed: " + Convert.ToString(runTime.TotalMilliseconds / 1000));
}
}
}
Estas pruebas escriben datos en una matriz (por lo que no se debe permitir que el tiempo de ejecución de .NET elimine la operación sqrt), aunque la matriz es significativamente más pequeña (no quería usar una cantidad excesiva de memoria). Los compilé en la configuración de lanzamiento y los ejecuté desde una ventana de la consola (en lugar de comenzar a través de VS).
En mi computadora, el programa C # varía entre 6.2 y 6.9 segundos, mientras que la versión C varía entre 6.9 y 7.1.
Lo mantendré breve, ya está marcado como respondido. C # tiene la gran ventaja de tener un modelo de coma flotante bien definido. Eso simplemente coincide con el modo de operación nativo de las instrucciones FPU y SSE establecidas en los procesadores x86 y x64. No es una coincidencia allí. JITter compila Math.Sqrt () con algunas instrucciones en línea.
Native C / C ++ está cargado de años de compatibilidad con versiones anteriores. Las opciones de compilación / fp: precise, / fp: fast y / fp: strict son las más visibles. En consecuencia, debe llamar a una función CRT que implementa sqrt () y verifica las opciones de coma flotante seleccionadas para ajustar el resultado. Eso es lento
Me parece que esto no tiene nada que ver con los lenguajes en sí, sino que tiene que ver con las diferentes implementaciones de la función de raíz cuadrada.
Para ver si el bucle está siendo optimizado, intente cambiar su código a
root += Math.Sqrt(i);
an similarmente en el código C, y luego imprimir el valor de la raíz fuera del ciclo.
Quizás el compilador de c # advierte que no usas root en ninguna parte, por lo que simplemente omite todo el ciclo for. :)
Puede que ese no sea el caso, pero sospecho que cualquiera que sea la causa, depende de la implementación del compilador. Intente compilar su programa C con el compilador de Microsoft (cl.exe, disponible como parte de la sdk win32) con optimizaciones y modo de lanzamiento. Apuesto a que verás una mejora de rendimiento sobre el otro compilador.
EDITAR: No creo que el compilador solo pueda optimizar el ciclo for, porque debería saber que Math.Sqrt () no tiene ningún efecto secundario.
Si solo hace un paso en el código en el nivel de ensamblado, incluido el paso por la rutina de raíz cuadrada, probablemente obtendrá la respuesta a su pregunta.
No hay necesidad de adivinar.
Soy un desarrollador de C ++ y C #. Desarrollé aplicaciones C # desde la primera versión beta del framework .NET y he tenido más de 20 años de experiencia en el desarrollo de aplicaciones C ++. En primer lugar, el código C # NUNCA será más rápido que una aplicación C ++, pero no pasaré por una larga discusión sobre el código administrado, cómo funciona, la capa interoperativa, las funciones internas de administración de memoria, el sistema de tipo dinámico y el recolector de basura. Sin embargo, permítanme continuar diciendo que los puntos de referencia enumerados aquí producen resultados INCORRECTOS.
Permítanme explicar: lo primero que debemos tener en cuenta es el compilador JIT para C # (.NET Framework 4). Ahora el JIT produce código nativo para la CPU usando varios algoritmos de optimización (que tienden a ser más agresivos que el optimizador predeterminado de C ++ que viene con Visual Studio) y el conjunto de instrucciones utilizado por el compilador .NET JIT son un reflejo más cercano de la CPU real en la máquina se podrían hacer ciertas sustituciones en el código de la máquina para reducir los ciclos de reloj y mejorar la tasa de aciertos en la memoria caché de la CPU y producir optimizaciones adicionales de subprocesos tales como reordenamiento de instrucciones y mejoras relacionadas con la predicción de bifurcación.
Lo que esto significa es que a menos que compile su aplicación C ++ utilizando los parámetros adecuados para la compilación RELEASE (no la compilación DEBUG), entonces su aplicación C ++ puede funcionar más lentamente que la aplicación correspondiente basada en C # o .NET. Al especificar las propiedades del proyecto en su aplicación C ++, asegúrese de habilitar la "optimización completa" y "favorecer el código rápido". Si tiene una máquina de 64 bits, DEBE especificar generar x64 como la plataforma de destino, de lo contrario su código se ejecutará a través de una subcapa de conversión (WOW64) que reducirá sustancialmente el rendimiento.
Una vez que realice las optimizaciones correctas en el compilador, obtengo .72 segundos para la aplicación C ++ y 1.16 segundos para la aplicación C # (ambos en la versión de lanzamiento). Como la aplicación C # es muy básica y asigna la memoria utilizada en el bucle en la pila y no en el montón, en realidad está funcionando mucho mejor que una aplicación real involucrada en objetos, cálculos pesados y con conjuntos de datos más grandes. Entonces las cifras proporcionadas son cifras optimistas sesgadas hacia C # y el marco .NET. Incluso con este sesgo, la aplicación C ++ se completa en poco más de la mitad del tiempo que la aplicación C # equivalente. Tenga en cuenta que el compilador de C ++ de Microsoft que utilicé no tenía las optimizaciones adecuadas para el pipeteo y el hyperthreading (usando WinDBG para ver las instrucciones de ensamblaje).
Ahora, si usamos el compilador Intel (que por cierto es un secreto industrial para generar aplicaciones de alto rendimiento en procesadores AMD / Intel), el mismo código se ejecuta en .54 segundos para el ejecutable C ++ frente a los .72 segundos que utilizan Microsoft Visual Studio 2010 Así que, al final, los resultados finales son .54 segundos para C ++ y 1.16 segundos para C #. Por lo tanto, el código producido por el compilador .NET JIT demora un 214% más que el ejecutable de C ++. ¡La mayor parte del tiempo pasado en .54 segundos fue para obtener el tiempo del sistema y no dentro del bucle mismo!
Lo que también falta en las estadísticas es el inicio y los tiempos de limpieza que no están incluidos en los tiempos. Las aplicaciones C # tienden a pasar mucho más tiempo en la puesta en marcha y en la terminación que las aplicaciones C ++. La razón de esto es complicada y tiene que ver con las rutinas de validación de código de tiempo de ejecución de .NET y el subsistema de gestión de memoria que realiza mucho trabajo al principio (y en consecuencia, al final) del programa para optimizar las asignaciones de memoria y la basura coleccionista.
Al medir el rendimiento de C ++ y .NET IL, es importante mirar el código de ensamblado para asegurarse de que TODOS los cálculos estén allí. Lo que encontré es que sin poner algún código adicional en C #, la mayoría del código en los ejemplos anteriores en realidad se eliminaron del binario. Este también fue el caso con C ++ cuando utilizó un optimizador más agresivo como el que viene con el compilador Intel C ++. Los resultados que proporcioné arriba son 100% correctos y validados en el nivel de ensamblaje.
El principal problema con muchos foros en Internet es que muchos novatos escuchan propaganda de marketing de Microsoft sin entender la tecnología y hacen afirmaciones falsas de que C # es más rápido que C ++. La afirmación es que, en teoría, C # es más rápido que C ++ porque el compilador JIT puede optimizar el código para la CPU. El problema con esta teoría es que existe una gran cantidad de plomería en el marco .NET que ralentiza el rendimiento; fontanería que no existe en la aplicación C ++. Además, un desarrollador experimentado conocerá el compilador correcto para usar para la plataforma dada y usará los indicadores apropiados al compilar la aplicación. En las plataformas Linux o de código abierto, esto no es un problema porque podría distribuir su fuente y crear scripts de instalación que compilan el código utilizando la optimización adecuada. En la plataforma de Windows o de fuente cerrada, tendrá que distribuir varios ejecutables, cada uno con optimizaciones específicas. Los binarios de Windows que se implementarán se basan en la CPU detectada por el instalador msi (utilizando acciones personalizadas).
mi primera suposición es una optimización del compilador porque nunca usas root. Usted simplemente lo asigna, luego lo sobrescribe una y otra vez.
Editar: ¡maldición, vencer por 9 segundos!