Estoy encontrando enormes diferencias de rendimiento entre código similar 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 # se completa en 0.328125 segundos (versión de lanzamiento) y el C tarda 11.14 segundos en ejecutarse.
La c se está compilando en un ejecutable de Windows usando mingw.
Siempre he asumido que C / C ++ era más rápido o al menos comparable a C # .net. ¿Qué está causando exactamente que el C funcione 30 veces más lento?
EDITAR: Parece que el optimizador de C # estaba eliminando la raíz porque 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 configurado para la velocidad máxima.
Los resultados son ahora: 3,75 segundos para C 2,61 segundos para C #
La C todavía está tardando más, pero esto es aceptable
fuente
Respuestas:
Como nunca usa 'root', es posible que el compilador haya eliminado la llamada para optimizar su método.
Puede intentar acumular los valores de la raíz cuadrada en un acumulador, imprimirlo al final del método y ver qué está pasando.
Editar: vea la respuesta de Jalf a continuación
fuente
Debes comparar versiones de depuración. Acabo de compilar tu código C y obtuve
Si no habilita las optimizaciones, cualquier evaluación comparativa que haga no tendrá ningún valor. (Y si habilita las optimizaciones, el ciclo se optimiza. Por lo tanto, su código de evaluación comparativa también tiene fallas. Debe forzarlo a ejecutar el ciclo, generalmente resumiendo el resultado o similar e imprimiéndolo al final).
Parece que lo que está midiendo es básicamente "qué compilador inserta la mayor sobrecarga de depuración". Y resulta que la respuesta es C. Pero eso no nos dice qué programa es más rápido. Porque cuando quieres velocidad, habilitas optimizaciones.
Por cierto, a la larga se ahorrará muchos dolores de cabeza si abandona la noción de que los idiomas 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 ocurre con C # o cualquier otro idioma.
La velocidad de ejecución está determinada por:
Un buen compilador de C # producirá un código eficiente. Un compilador de C incorrecto generará código lento. ¿Qué pasa con un compilador C que generó código C #, que luego podría ejecutar a través de un compilador C #? ¿Qué tan rápido funcionaría eso? Los idiomas no tienen velocidad. Tu código lo hace.
fuente
i
, ysqrt
eso es lo que se está midiendo.Lo haré breve, ya está marcado como respondido. C # tiene la gran ventaja de tener un modelo de punto flotante bien definido. Eso simplemente coincide con el modo de operación nativo de las instrucciones FPU y SSE establecidas en procesadores x86 y x64. No es casualidad. JITter compila Math.Sqrt () en algunas instrucciones en línea.
Native C / C ++ está cargado de años de compatibilidad con versiones anteriores. Las opciones de compilación / fp: precisa, / fp: rápida y / fp: estricta son las más visibles. En consecuencia, debe llamar a una función CRT que implemente sqrt () y verifique las opciones de punto flotante seleccionadas para ajustar el resultado. Eso es lento.
fuente
Soy un desarrollador de C ++ y C #. He desarrollado aplicaciones C # desde la primera versión beta del framework .NET y tengo 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 inter-op, los componentes internos de la administración de memoria, el sistema de tipos dinámicos y el recolector de basura. Sin embargo, permítanme continuar diciendo que los puntos de referencia enumerados aquí producen resultados INCORRECTOS.
Déjame explicarte: lo primero que debemos considerar es el compilador JIT para C # (.NET Framework 4). Ahora, el JIT produce código nativo para la CPU utilizando varios algoritmos de optimización (que tienden a ser más agresivos que el optimizador C ++ predeterminado que viene con Visual Studio) y el conjunto de instrucciones utilizado por el compilador .NET JIT es un reflejo más cercano de la CPU real. en la máquina, por lo que se podrían realizar ciertas sustituciones en el código de la máquina para reducir los ciclos de reloj y mejorar la tasa de aciertos en la caché de la canalización de la CPU y producir más optimizaciones de hiperprocesamiento, como el reordenamiento de instrucciones y mejoras relacionadas con la predicción de rama.
Lo que esto significa es que a menos que compile su aplicación C ++ usando los parámetros correctos para la compilación RELEASE (no la compilación DEBUG), su aplicación C ++ puede funcionar más lentamente que la correspondiente aplicación basada en C # o .NET. Al especificar las propiedades del proyecto en su aplicación C ++, asegúrese de habilitar "optimización completa" y "favorecer el código rápido". Si tiene una máquina de 64 bits, DEBE especificar generar x64 como 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 realiza 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 compilación). Dado que 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 se está desempeñando 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 Microsoft C ++ que utilicé no tenía las optimizaciones correctas de canalización y subprocesamiento (usando WinDBG para ver las instrucciones de ensamblaje).
Ahora bien, si usamos el compilador Intel (que por cierto es un secreto de la industria 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 .72 segundos usando Microsoft Visual Studio 2010 Entonces, al final, los resultados finales son .54 segundos para C ++ y 1.16 segundos para C #. Por tanto, el código producido por el compilador .NET JIT tarda un 214% más que el ejecutable de C ++. La mayor parte del tiempo invertido en los .54 segundos fue para obtener el tiempo del sistema y no dentro del bucle en sí.
Lo que también falta en las estadísticas son los tiempos de inicio y limpieza que no están incluidos en los tiempos. Las aplicaciones C # tienden a dedicar mucho más tiempo al inicio y finalización que las aplicaciones C ++. La razón detrás de esto es complicada y tiene que ver con las rutinas de validación de código en tiempo de ejecución .NET y el subsistema de administració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 ensamblador para asegurarse de que TODOS los cálculos estén ahí. Lo que encontré es que sin poner código adicional en C #, la mayor parte del código en los ejemplos anteriores se eliminó del binario. Este también fue el caso con C ++ cuando usó un optimizador más agresivo como el que viene con el compilador Intel C ++. Los resultados que proporcioné anteriormente son 100% correctos y están validados a nivel de ensamblaje.
El principal problema con muchos foros en Internet es que muchos novatos escuchan la propaganda de marketing de Microsoft sin comprender 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; plomería que no existe en la aplicación C ++. Además, un desarrollador experimentado sabrá cuál es 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 puede distribuir su fuente y crear scripts de instalación que compilen el código utilizando la optimización adecuada. En Windows o plataforma de código cerrado, 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 de msi (mediante acciones personalizadas).
fuente
mi primera suposición es una optimización del compilador porque nunca usas root. Simplemente lo asigna y luego lo sobrescribe una y otra vez.
Editar: ¡maldita sea, batir por 9 segundos!
fuente
Para ver si el bucle se está optimizando, intente cambiar su código a
ans de manera similar en el código C, y luego imprime el valor de root fuera del ciclo.
fuente
Tal vez el compilador de c # se da cuenta de que no usa root en ningún lugar, por lo que simplemente omite todo el ciclo for. :)
Puede que ese no sea el caso, pero sospecho que sea cual sea la causa, depende de la implementación del compilador. Intente compilar su programa en C con el compilador de Microsoft (cl.exe, disponible como parte de win32 sdk) con optimizaciones y modo de lanzamiento. Apuesto a que verá una mejora de rendimiento sobre el otro compilador.
EDITAR: No creo que el compilador pueda simplemente optimizar el bucle for, porque tendría que saber que Math.Sqrt () no tiene efectos secundarios.
fuente
Cualquiera que sea la diferencia horaria. puede ser, ese "tiempo transcurrido" no es válido. Solo sería válido si puede garantizar que ambos programas se ejecuten exactamente en las mismas condiciones.
Quizás deberías intentar una victoria. equivalente a $ / usr / bin / time my_cprog; / usr / bin / time my_csprog
fuente
Reuní (según su código) dos pruebas más comparables en C y C #. Estos dos escriben una matriz más pequeña usando el operador de módulo para indexar (agrega un poco de sobrecarga, pero bueno, estamos tratando de comparar el rendimiento [a un nivel básico]).
Código C:
C ª#:
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 memoria excesiva). Los compilé en la configuración de lanzamiento y los ejecuté desde dentro de una ventana de 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.
fuente
Si solo realiza un solo paso del código a nivel de ensamblador, incluido el paso a través de la rutina de raíz cuadrada, probablemente obtendrá la respuesta a su pregunta.
No hay necesidad de adivinar.
fuente
El otro factor que puede ser un problema aquí es que el compilador de C compila en código nativo genérico para la familia de procesadores a la que se dirige, mientras que el MSIL generado cuando compiló el código C # luego se compila con JIT para apuntar al procesador exacto que tiene completo con cualquier optimizaciones que puedan ser posibles. Por lo tanto, el código nativo generado a partir de C # puede ser considerablemente más rápido que el de C.
fuente
Me parece que esto no tiene nada que ver con los lenguajes en sí, sino con las diferentes implementaciones de la función de raíz cuadrada.
fuente
En realidad, chicos, el bucle NO se está optimizando. Compilé el código de John y examiné el .exe resultante. Las entrañas del bucle son las siguientes:
¿A menos que el tiempo de ejecución sea lo suficientemente inteligente como para darse cuenta de que el bucle no hace nada y lo omite?
Editar: Cambiar el C # para que sea:
Resultados en el tiempo transcurrido (en mi máquina) pasando de 0.047 a 2.17. Pero, ¿es eso solo la sobrecarga de agregar 100 millones de operadores adicionales?
fuente