Rendimiento de C ++ frente a Java / C #


119

Tengo entendido que C / C ++ produce código nativo para ejecutarse en una arquitectura de máquina en particular. Por el contrario, lenguajes como Java y C # se ejecutan sobre una máquina virtual que abstrae la arquitectura nativa. Lógicamente, parecería imposible que Java o C # igualen la velocidad de C ++ debido a este paso intermedio, sin embargo, me han dicho que los últimos compiladores ("puntos calientes") pueden alcanzar esta velocidad o incluso superarla.

Quizás esto sea más una pregunta de compilador que una pregunta de idioma, pero ¿alguien puede explicar en inglés simple cómo es posible que uno de estos lenguajes de máquinas virtuales funcione mejor que un idioma nativo?


Java y C # pueden realizar la optimización en función de cómo se ejecuta realmente la aplicación utilizando el código, ya que está disponible en tiempo de ejecución. por ejemplo, puede incluir código en línea en una biblioteca compartida que en realidad puede cambiar mientras el programa se está ejecutando y aún ser correcto.
Peter Lawrey

Algunas medidas reales para verificar antes de leer mucha teoría muy escasa en estas respuestas: shootout.alioth.debian.org/u32/…
Justicle

Respuestas:


178

En general, C # y Java pueden ser tan rápidos o más rápidos porque el compilador JIT, un compilador que compila su IL la primera vez que se ejecuta, puede realizar optimizaciones que un programa compilado en C ++ no puede hacer porque puede consultar la máquina. Puede determinar si la máquina es Intel o AMD; Pentium 4, Core Solo o Core Duo; o si es compatible con SSE4, etc.

Un programa C ++ tiene que compilarse de antemano normalmente con optimizaciones mixtas para que funcione decentemente bien en todas las máquinas, pero no está optimizado tanto como podría estarlo para una sola configuración (es decir, procesador, conjunto de instrucciones, otro hardware).

Además, ciertas características del lenguaje permiten que el compilador en C # y Java haga suposiciones sobre su código que le permite optimizar ciertas partes que simplemente no son seguras para el compilador de C / C ++. Cuando tienes acceso a los punteros, hay muchas optimizaciones que simplemente no son seguras.

Además, Java y C # pueden realizar asignaciones de montón de manera más eficiente que C ++ porque la capa de abstracción entre el recolector de basura y su código le permite hacer toda su compresión de montón a la vez (una operación bastante cara).

Ahora no puedo hablar por Java en el siguiente punto, pero sé que C #, por ejemplo, eliminará los métodos y las llamadas a métodos cuando sepa que el cuerpo del método está vacío. Y utilizará este tipo de lógica en todo su código.

Como puede ver, hay muchas razones por las que ciertas implementaciones de C # o Java serán más rápidas.

Dicho todo esto, se pueden hacer optimizaciones específicas en C ++ que harán volar cualquier cosa que pueda hacer con C #, especialmente en el ámbito de los gráficos y en cualquier momento que esté cerca del hardware. Los punteros hacen maravillas aquí.

Entonces, dependiendo de lo que estés escribiendo, iría con uno u otro. Pero si está escribiendo algo que no depende del hardware (controlador, videojuego, etc.), no me preocuparía por el rendimiento de C # (de nuevo, no puedo hablar de Java). Estará bien.

En el lado de Java, @Swati señala un buen artículo:

https://www.ibm.com/developerworks/library/j-jtp09275


Su razonamiento es falso: los programas C ++ se crean para su arquitectura de destino, no necesitan cambiar en tiempo de ejecución.
Justicle

3
@Justicle Lo mejor que ofrecerá su compilador de C ++ para diferentes arquitecturas suele ser x86, x64, ARM y otras cosas. Ahora puede decirle que use funciones específicas (por ejemplo, SSE2) y, si tiene suerte, incluso generará algún código de respaldo si esa función no está disponible, pero eso es lo más detallado posible. Ciertamente, no hay especialización dependiendo de los tamaños de caché y demás.
Voo

4
Consulte shootout.alioth.debian.org/u32/… para ver ejemplos de que esta teoría no ocurre.
Justicle

1
Para ser honesto, esta es una de las peores respuestas. Es tan infundado que podría simplemente invertirlo. Demasiada generalización, demasiado desconocimiento (optimizar funciones vacías es en realidad solo la punta del iceberg). Un compilador de C ++ de lujo tiene: Time. Otro lujo: no se impone ningún control. Pero encuentre más en stackoverflow.com/questions/145110/c-performance-vs-java-c/… .
Sebastian Mach

1
@OrionAdrian está bien, ahora estamos en el círculo completo ... Vea shootout.alioth.debian.org/u32/… para ver ejemplos de que esta teoría no está sucediendo. En otras palabras, muéstrenos que se puede probar que su teoría es correcta antes de hacer vagas declaraciones especulativas.
Justicle

197

JIT frente al compilador estático

Como ya se dijo en las publicaciones anteriores, JIT puede compilar IL / bytecode en código nativo en tiempo de ejecución. Se mencionó el costo de eso, pero no hasta su conclusión:

JIT tiene un problema masivo es que no puede compilar todo: la compilación JIT lleva tiempo, por lo que JIT compilará solo algunas partes del código, mientras que un compilador estático producirá un binario nativo completo: para algunos tipos de programas, el El compilador simplemente superará fácilmente al JIT.

Por supuesto, C # (o Java, o VB) suele ser más rápido para producir una solución viable y robusta que C ++ (aunque solo sea porque C ++ tiene semántica compleja, y la biblioteca estándar de C ++, aunque interesante y poderosa, es bastante pobre en comparación con la completa alcance de la biblioteca estándar de .NET o Java), por lo que, por lo general, la diferencia entre C ++ y .NET o Java JIT no será visible para la mayoría de los usuarios, y para aquellos binarios que son críticos, bueno, aún puede llamar al procesamiento de C ++ desde C # o Java (incluso si este tipo de llamadas nativas pueden ser bastante costosas en sí mismas) ...

Metaprogramación C ++

Tenga en cuenta que, por lo general, está comparando el código de tiempo de ejecución de C ++ con su equivalente en C # o Java. Pero C ++ tiene una característica que puede superar a Java / C # desde el primer momento, que es la metaprogramación de plantilla: el procesamiento del código se realizará en el momento de la compilación (por lo tanto, aumentará enormemente el tiempo de compilación), lo que dará como resultado un tiempo de ejecución cero (o casi cero).

Todavía he visto un efecto de la vida real en esto (jugué solo con conceptos, pero para entonces, la diferencia eran segundos de ejecución para JIT, y cero para C ++), pero vale la pena mencionarlo, junto con la plantilla de hechos, la metaprogramación no es trivial...

Editar 2011-06-10: en C ++, jugar con tipos se realiza en tiempo de compilación, lo que significa producir código genérico que llama a código no genérico (por ejemplo, un analizador genérico de cadena a tipo T, llamando a la API de biblioteca estándar para los tipos T que reconoce, y hacer que el analizador sea fácilmente extensible por su usuario) es muy fácil y muy eficiente, mientras que el equivalente en Java o C # es, en el mejor de los casos, doloroso de escribir, y siempre será más lento y resuelto en tiempo de ejecución, incluso cuando los tipos se conocen en tiempo de compilación, lo que significa que su única esperanza es que el JIT lo alinee todo.

...

Editar 2011-09-20: El equipo detrás de Blitz ++ ( página de inicio , Wikipedia ) fue por ese camino, y aparentemente, su objetivo es alcanzar el rendimiento de FORTRAN en los cálculos científicos moviéndose tanto como sea posible desde la ejecución en tiempo de ejecución hasta el tiempo de compilación, a través de la metaprogramación de plantillas C ++ . Entonces, la parte " Todavía he visto un efecto real en esta " parte que escribí arriba aparentemente lo hace existe en la vida real.

Uso de memoria nativa C ++

C ++ tiene un uso de memoria diferente al de Java / C # y, por lo tanto, tiene diferentes ventajas / defectos.

No importa la optimización JIT, nada funcionará tan rápido como el acceso directo del puntero a la memoria (ignoremos por un momento los cachés del procesador, etc.). Por lo tanto, si tiene datos contiguos en la memoria, acceder a ellos a través de punteros de C ++ (es decir, punteros de C ... Démosle a Caesar lo que le corresponde) será más rápido que en Java / C #. Y C ++ tiene RAII, lo que facilita mucho el procesamiento que en C # o incluso en Java. C ++ no necesitausing analizar la existencia de sus objetos. Y C ++ no tiene finallycláusula. Esto no es un error.

:-)

Y a pesar de las estructuras de tipo primitivo de C #, los objetos "en la pila" de C ++ no costarán nada en la asignación y destrucción, y no necesitarán GC para trabajar en un hilo independiente para realizar la limpieza.

En cuanto a la fragmentación de memoria, los asignadores de memoria en 2008 no son los viejos asignadores de memoria de 1980 que generalmente se comparan con un GC: la asignación de C ++ no se puede mover en la memoria, es cierto, pero luego, como en un sistema de archivos Linux: ¿Quién necesita el disco duro? desfragmentar cuando la fragmentación no ocurre? El uso del asignador correcto para la tarea correcta debería ser parte del kit de herramientas para desarrolladores de C ++. Ahora, escribir asignadores no es fácil, y luego, la mayoría de nosotros tenemos mejores cosas que hacer, y para la mayor parte del uso, RAII o GC es más que suficiente.

Editar 2011-10-04: para ver ejemplos sobre asignadores eficientes: en las plataformas Windows, desde Vista, el montón de fragmentación baja está habilitado de forma predeterminada. Para versiones anteriores, el LFH se puede activar llamando a la función WinAPI HeapSetInformation ). En otros sistemas operativos, se proporcionan asignadores alternativos (consultehttps://secure.wikimedia.org/wikipedia/en/wiki/Malloc para obtener una lista)

Ahora, el modelo de memoria se está volviendo algo más complicado con el auge de la tecnología de múltiples núcleos y subprocesos. En este campo, supongo que .NET tiene la ventaja, y me dijeron que Java ocupaba el primer lugar. Es fácil para algunos hackers "en el metal desnudo" elogiar su código "cerca de la máquina". Pero ahora, es bastante más difícil producir un mejor ensamblaje a mano que dejar que el compilador haga su trabajo. Para C ++, el compilador se volvió generalmente mejor que el hacker desde hace una década. Para C # y Java, esto es aún más fácil.

Aún así, el nuevo estándar C ++ 0x impondrá un modelo de memoria simple a los compiladores de C ++, que estandarizará (y así simplificará) el código de multiprocesamiento / paralelo / subprocesamiento efectivo en C ++, y hará que las optimizaciones sean más fáciles y seguras para los compiladores. Pero luego, veremos en un par de años si sus promesas se cumplen.

C ++ / CLI frente a C # / VB.NET

Nota: En esta sección, estoy hablando de C ++ / CLI, es decir, el C ++ alojado por .NET, no el C ++ nativo.

La semana pasada, recibí una capacitación sobre optimización de .NET y descubrí que el compilador estático es muy importante de todos modos. Tan importante que JIT.

El mismo código compilado en C ++ / CLI (o su antecesor, Managed C ++) podría ser veces más rápido que el mismo código producido en C # (o VB.NET, cuyo compilador produce el mismo IL que C #).

Porque el compilador estático de C ++ era mucho mejor para producir código ya optimizado que el de C #.

Por ejemplo, la inserción de funciones en .NET está limitada a funciones cuyo código de bytes es menor o igual que 32 bytes de longitud. Entonces, algún código en C # producirá un descriptor de acceso de 40 bytes, que el JIT nunca incluirá. El mismo código en C ++ / CLI producirá un descriptor de acceso de 20 bytes, que será insertado por el JIT.

Otro ejemplo son las variables temporales, que simplemente son compiladas por el compilador de C ++ mientras aún se mencionan en el IL producido por el compilador de C #. La optimización de la compilación estática de C ++ dará como resultado menos código, por lo que autoriza una optimización JIT más agresiva, nuevamente.

Se especuló que la razón de esto era el hecho de que el compilador C ++ / CLI se benefició de las vastas técnicas de optimización del compilador nativo C ++.

Conclusión

Amo C ++.

Pero hasta donde yo lo veo, C # o Java son en general una mejor apuesta. No porque sean más rápidos que C ++, sino porque cuando sumas sus cualidades, terminan siendo más productivos, necesitan menos capacitación y tienen bibliotecas estándar más completas que C ++. Y como ocurre con la mayoría de programas, sus diferencias de velocidad (de una forma u otra) serán insignificantes ...

Editar (2011-06-06)

Mi experiencia en C # /. NET

Ahora tengo 5 meses de codificación C # profesional casi exclusiva (que se suma a mi CV ya lleno de C ++ y Java, y un toque de C ++ / CLI).

Jugué con WinForms (Ejem ...) y WCF (¡genial!), Y WPF (¡Genial! Tanto a través de XAML como de C # sin formato. WPF es tan fácil que creo que Swing simplemente no se puede comparar con él) y C # 4.0.

La conclusión es que, si bien es más fácil / rápido producir un código que funcione en C # / Java que en C ++, es mucho más difícil producir un código fuerte, seguro y robusto en C # (e incluso más difícil en Java) que en C ++. Las razones abundan, pero se pueden resumir en:

  1. Los genéricos no son tan poderosos como las plantillas ( intente escribir un método Parse genérico eficiente (de cadena a T), o un equivalente eficiente de boost :: lexical_cast en C # para comprender el problema )
  2. RAII sigue siendo incomparable ( GC todavía puede tener fugas (sí, tuve que manejar ese problema) y solo manejará la memoria. Incluso C # usingno es tan fácil y poderoso porque escribir una implementación correcta de Dispose es difícil )
  3. C # readonlyy Java finalno son tan útiles como C ++const ( no hay forma de que pueda exponer datos complejos de solo lectura (un árbol de nodos, por ejemplo) en C # sin un trabajo tremendo, mientras que es una función incorporada de C ++. Los datos inmutables son una solución interesante , pero no todo puede volverse inmutable, por lo que ni siquiera es suficiente, ni mucho menos ).

Por lo tanto, C # sigue siendo un lenguaje agradable siempre que desee algo que funcione, pero un lenguaje frustrante en el momento en que desee algo que siempre y con seguridad funcione .

Java es aún más frustrante, ya que tiene los mismos problemas que C #, y más: al carecer del equivalente de la usingpalabra clave de C # , un colega mío muy hábil pasó demasiado tiempo asegurándose de que sus recursos se liberaran correctamente, mientras que el equivalente en C ++ habría ha sido fácil (usando destructores y punteros inteligentes).

Así que supongo que la ganancia de productividad de C # / Java es visible para la mayoría del código ... hasta el día en que necesita que el código sea lo más perfecto posible. Ese día conocerás el dolor. (no creerá lo que le piden nuestro servidor y aplicaciones GUI ...).

Acerca de Java y C ++ del lado del servidor

Me mantuve en contacto con los equipos de servidores (trabajé 2 años entre ellos, antes de volver al equipo de GUI), al otro lado del edificio, y aprendí algo interesante.

En los últimos años, la tendencia era que las aplicaciones de servidor de Java estuvieran destinadas a reemplazar las antiguas aplicaciones de servidor de C ++, ya que Java tiene muchos marcos / herramientas y es fácil de mantener, implementar, etc., etc.

... Hasta que el problema de la baja latencia asomó su fea cabeza en los últimos meses. Luego, las aplicaciones del servidor de Java, sin importar la optimización intentada por nuestro experto equipo de Java, simplemente perdieron la carrera contra el viejo servidor C ++, no realmente optimizado.

Actualmente, la decisión es mantener los servidores Java para uso común donde el rendimiento, aunque sigue siendo importante, no se ve afectado por el objetivo de baja latencia, y optimizar agresivamente las aplicaciones de servidor C ++, que ya son más rápidas, para necesidades de latencia baja y latencia ultrabaja.

Conclusión

Nada es tan simple como se esperaba.

Java, e incluso más C #, son lenguajes geniales, con extensas bibliotecas y marcos estándar, donde se puede codificar rápidamente y obtener resultados muy pronto.

Pero cuando necesita potencia bruta, optimizaciones potentes y sistemáticas, soporte de compilador sólido, funciones de lenguaje potentes y seguridad absoluta, Java y C # dificultan la obtención de los últimos porcentajes de calidad que faltan pero críticos que necesita para mantenerse por encima de la competencia.

Es como si necesitara menos tiempo y desarrolladores con menos experiencia en C # / Java que en C ++ para producir código de calidad promedio, pero por otro lado, en el momento en que necesitaba un código de calidad excelente para perfeccionar, de repente fue más fácil y rápido obtener los resultados. justo en C ++.

Por supuesto, esta es mi propia percepción, quizás limitada a nuestras necesidades específicas.

Pero aún así, es lo que sucede hoy, tanto en los equipos de GUI como en los equipos del lado del servidor.

Por supuesto, actualizaré esta publicación si sucede algo nuevo.

Editar (2011-06-22)

"Encontramos que en lo que respecta al rendimiento, C ++ gana por un amplio margen. Sin embargo, también requirió los esfuerzos de ajuste más extensos, muchos de los cuales se realizaron con un nivel de sofisticación que no estaría disponible para el programador promedio.

[...] La versión de Java fue probablemente la más sencilla de implementar, pero la más difícil de analizar en cuanto a rendimiento. Específicamente, los efectos en torno a la recolección de basura fueron complicados y muy difíciles de ajustar ".

Fuentes:

Editar (2011-09-20)

"La palabra corriente en Facebook es que 'el código C ++ razonablemente escrito se ejecuta rápido ' , lo que subraya el enorme esfuerzo invertido en optimizar el código PHP y Java. Paradójicamente, el código C ++ es más difícil de escribir que en otros lenguajes, pero el código eficiente es un mucho más fácil [escribir en C ++ que en otros lenguajes] " .

- Herb Sutter en // build / , citando a Andrei Alexandrescu

Fuentes:


8
Editar después de 5 meses de C # describe exactamente mi propia experiencia (plantillas mejor, const mejor, RAII). +1. Esas tres siguen siendo mis características asesinas personales para C ++ (o D, para las que todavía no tenía tiempo).
Sebastian Mach

"El procesamiento del código se realizará en el momento de la compilación". Por lo tanto, la metaprogramación de plantillas solo funciona en el programa que está disponible en tiempo de compilación, lo que a menudo no es el caso, por ejemplo, es imposible escribir una biblioteca de expresiones regulares de rendimiento competitivo en vanilla C ++ porque es incapaz de generar código en tiempo de ejecución (un aspecto importante de metaprogramación).
JD

"jugar con tipos se realiza en tiempo de compilación ... el equivalente en Java o C # es, en el mejor de los casos, doloroso de escribir, y siempre será más lento y se resolverá en tiempo de ejecución, incluso cuando los tipos se conocen en tiempo de compilación". En C #, eso solo es cierto para los tipos de referencia y no para los tipos de valor.
JD

1
"No importa la optimización JIT, nada funcionará tan rápido como el acceso directo a la memoria con un puntero ... si tiene datos contiguos en la memoria, acceder a ellos a través de punteros C ++ (es decir, punteros C ... Démosle a Caesar lo que le corresponde) pasará más rápido que en Java / C # ". La gente ha observado que Java supera a C ++ en la prueba SOR del punto de referencia SciMark2 precisamente porque los punteros impiden las optimizaciones relacionadas con el aliasing. blogs.oracle.com/dagastine/entry/sun_java_is_faster_than
JD

También vale la pena señalar que .NET se especializa en tipos de genéricos en bibliotecas vinculadas dinámicamente después de la vinculación, mientras que C ++ no puede porque las plantillas deben resolverse antes de vincular. Y, obviamente, la gran ventaja que tienen los genéricos sobre las plantillas son los mensajes de error comprensibles.
JD

48

Siempre que hablo de rendimiento administrado frente a rendimiento no administrado, me gusta señalar la serie que Rico (y Raymond) hicieron comparando las versiones C ++ y C # de un diccionario chino / inglés. Esta búsqueda en Google te permitirá leer por ti mismo, pero me gusta el resumen de Rico.

Entonces, ¿estoy avergonzado de mi aplastante derrota? Apenas. El código administrado obtuvo un muy buen resultado sin apenas esfuerzo. Para derrotar al manejado Raymond tuvo que:

  • Escribe sus propias cosas de E / S de archivos
  • Escribe su propia clase de cuerdas
  • Escribe su propio asignador
  • Escribe su propio mapeo internacional

Por supuesto, usó bibliotecas de nivel inferior disponibles para hacer esto, pero todavía es mucho trabajo. ¿Puede llamar a lo que queda un programa STL? No lo creo, creo que mantuvo la clase std :: vector que finalmente nunca fue un problema y mantuvo la función de búsqueda. Casi todo lo demás se ha ido.

Entonces, sí, definitivamente puedes vencer al CLR. Creo que Raymond puede hacer que su programa sea aún más rápido.

Curiosamente, el tiempo para analizar el archivo según lo informado por los temporizadores internos de ambos programas es aproximadamente el mismo: 30 ms para cada uno. La diferencia está en los gastos generales.

Para mí, la conclusión es que se necesitaron 6 revisiones para que la versión no administrada superara a la versión administrada que era un puerto simple del código no administrado original. Si necesita hasta el último bit de rendimiento (y tiene el tiempo y la experiencia para obtenerlo), tendrá que seguir sin administrarlo, pero para mí, tomaré el orden de magnitud de ventaja que tengo en las primeras versiones sobre las 33 % Gano si lo intento 6 veces.


3
enlace está muerto, se encuentra el artículo mencionado aquí: blogs.msdn.com/b/ricom/archive/2005/05/10/416151.aspx
gjvdkamp

En primer lugar, si miramos el código de Raymond Chen, claramente no comprende muy bien C ++ o las estructuras de datos. Su código casi llega directamente al código C de bajo nivel incluso en los casos en que el código C no tiene beneficios de rendimiento (simplemente parece ser una especie de desconfianza y tal vez una falta de conocimiento sobre cómo usar los perfiladores). Tampoco pudo entender la forma más sólida algorítmicamente de implementar un diccionario (usó std :: find por el amor de Dios). Si hay algo bueno sobre Java, Python, C #, etc., todos proporcionan diccionarios muy eficientes ...
stinky472

Los intentos o incluso std :: map obtendrían resultados mucho más favorables hacia C ++ o incluso una tabla hash. Finalmente, un diccionario es exactamente el tipo de programa que más se beneficia de las bibliotecas y marcos de alto nivel. No demuestra diferencias en el lenguaje tanto como las bibliotecas involucradas (de las cuales, felizmente diría que C # es mucho más completo y proporciona muchas más herramientas adecuadas para la tarea). Muestre un programa que manipula grandes bloques de memoria en comparación, como un código de matriz / vector a gran escala. Eso resolverá esto rápidamente incluso si, como en este caso, los codificadores no saben qué ...
stinky472

26

La compilación para optimizaciones de CPU específicas generalmente está sobrevalorada. Simplemente tome un programa en C ++ y compile con optimización para pentium PRO y ejecútelo en un pentium 4. Luego recompile con optimizar para pentium 4. Pasé largas tardes haciéndolo con varios programas. Resultados generales Por lo general, menos del 2-3% de aumento de rendimiento. Entonces, las ventajas teóricas del JIT son casi nulas. La mayoría de las diferencias de rendimiento solo se pueden observar cuando se utilizan funciones de procesamiento de datos escalares, algo que eventualmente necesitará un ajuste fino manual para lograr el máximo rendimiento de todos modos. Las optimizaciones de ese tipo son lentas y costosas de realizar, lo que las hace a veces inadecuadas para JIT de todos modos.

En el mundo real y en aplicaciones reales, C ++ suele ser más rápido que java, principalmente debido a una menor huella de memoria que da como resultado un mejor rendimiento de la caché.

Pero para usar todas las capacidades de C ++, el desarrollador debe trabajar duro. Puede lograr resultados superiores, pero debe usar su cerebro para eso. C ++ es un lenguaje que decidió presentarte más herramientas, cobrando el precio de que debes aprenderlas para poder usar bien el lenguaje.


4
No es tanto que esté compilando para la optimización de la CPU, sino que está compilando para la optimización de la ruta en tiempo de ejecución. Si encuentra que un método se llama muy a menudo con un parámetro específico, puede precompilar esa rutina con ese parámetro como una constante que podría (en el caso de un booleano que controla el flujo) factorizar cantidades gigantescas de trabajo. C ++ no puede acercarse a hacer ese tipo de optimización.
Bill K

1
Entonces, ¿cómo lo hacen los JIT para recompilar rutinas para aprovechar las rutas de ejecución observadas, y cuánta diferencia hace eso?
David Thornley

2
@Bill I puede estar mezclando dos cosas ... pero, ¿la predicción de bifurcaciones realizada en tiempo de ejecución en la tubería de instrucción no logra objetivos similares independientemente del idioma?
Hardy

@ Hardy, sí, la CPU puede hacer predicciones de bifurcaciones independientemente del idioma, pero no puede factorizar un ciclo completo al observar que el ciclo no tiene ningún efecto en nada. Tampoco observará que mult (0) está programado para devolver 0 y simplemente reemplazar toda la llamada al método con if (param == 0) result = 0; y evitar toda la función / llamada al método. C podría hacer estas cosas si el compilador tuviera una descripción general completa de lo que estaba sucediendo, pero generalmente no tiene suficiente información en el momento de la compilación.
Bill K

21

JIT (Just In Time Compiling) puede ser increíblemente rápido porque se optimiza para la plataforma de destino.

Esto significa que puede aprovechar cualquier truco del compilador que su CPU pueda admitir, independientemente de la CPU en la que el desarrollador haya escrito el código.

El concepto básico de .NET JIT funciona así (muy simplificado):

Llamar a un método por primera vez:

  • El código de su programa llama a un método Foo ()
  • El CLR mira el tipo que implementa Foo () y obtiene los metadatos asociados con él
  • A partir de los metadatos, el CLR sabe en qué dirección de memoria se almacena el IL (código de byte intermedio).
  • El CLR asigna un bloque de memoria y llama al JIT.
  • El JIT compila el IL en código nativo, lo coloca en la memoria asignada y luego cambia el puntero de función en los metadatos de tipo de Foo () para apuntar a este código nativo.
  • Se ejecuta el código nativo.

Llamar a un método por segunda vez:

  • El código de su programa llama a un método Foo ()
  • El CLR mira el tipo que implementa Foo () y encuentra el puntero de función en los metadatos.
  • Se ejecuta el código nativo en esta ubicación de memoria.

Como puede ver, la segunda vez, es prácticamente el mismo proceso que C ++, excepto con la ventaja de las optimizaciones en tiempo real.

Dicho esto, todavía hay otros problemas generales que ralentizan un lenguaje administrado, pero el JIT ayuda mucho.


Por cierto, Jonathan, creo que alguien todavía está rechazando tus cosas. Cuando te voté, tenías un -1 en esta publicación.
Brian R. Bondy

12

Me gusta la respuesta de Orion Adrian , pero tiene otro aspecto.

La misma pregunta se planteó hace décadas sobre el lenguaje ensamblador frente a los lenguajes "humanos" como FORTRAN. Y parte de la respuesta es similar.

Sí, un programa C ++ es capaz de ser más rápido que C # en cualquier algoritmo dado (¿no trivial?), Pero el programa en C # a menudo será tan rápido o más rápido que una implementación "ingenua" en C ++ y una versión optimizada en C ++ tardará más en desarrollarse y aún podría superar a la versión C # por un margen muy pequeño. Entonces, ¿realmente vale la pena?

Tendrás que responder a esa pregunta una por una.

Dicho esto, soy un fanático de C ++ desde hace mucho tiempo, y creo que es un lenguaje increíblemente expresivo y poderoso, a veces subestimado. Pero en muchos problemas de la "vida real" (para mí, personalmente, eso significa "del tipo que me pagan por resolver"), C # hará el trabajo antes y de forma más segura.

¿La mayor multa que pagas? Muchos programas .NET y Java acaparan la memoria. He visto que las aplicaciones .NET y Java consumen "cientos" de megabytes de memoria, cuando los programas C ++ de complejidad similar apenas alcanzan las "decenas" de MB.


7

No estoy seguro de con qué frecuencia encontrará que el código Java se ejecutará más rápido que C ++, incluso con Hotspot, pero intentaré explicar cómo podría suceder.

Piense en el código Java compilado como lenguaje de máquina interpretado para la JVM. Cuando el procesador de Hotspot nota que ciertas partes del código compilado se van a utilizar muchas veces, realiza una optimización en el código de la máquina. Dado que el ensamblaje de ajuste manual es casi siempre más rápido que el código compilado en C ++, está bien pensar que el código de máquina ajustado mediante programación no lo será demasiado. malo.

Entonces, para el código altamente repetitivo, pude ver dónde sería posible que Hotspot JVM ejecutara Java más rápido que C ++ ... hasta que la recolección de basura entre en juego. :)


¿Podría ampliar la afirmación Since hand-tuning Assembly is almost always faster than C++ compiled code? ¿Qué quiere decir "ensamblador de ajuste manual" y "código compilado C ++"?
paercebal

Bueno, se basa en la idea de que el optimizador de un compilador sigue reglas y los codificadores no. Por lo tanto, siempre habrá código que el optimizador descubra que no puede optimizar perfectamente, mientras que un humano podría hacerlo, ya sea mirando una imagen más grande o sabiendo más sobre lo que realmente está haciendo el código. Agregaré que este es un comentario de hace 3 años, y sé más sobre HotSpot que antes, y puedo ver fácilmente que la optimización dinámica es una forma MUY agradable de hacer que el código se ejecute más rápido.
billjamesdev

1. Las optimizaciones de Hotspot o cualquier otro JIT siguen siendo optimizaciones del compilador. JIT tiene la ventaja sobre un compilador estático de poder insertar algunos resultados (código llamado frecuentemente), o incluso hacer optimizaciones basadas en el procesador en ejecución, pero sigue siendo una optimización del compilador. . . 2. Supongo que está hablando de optimización de algoritmos, no de "ajuste fino del ensamblaje". El "ajuste fino del ensamblaje manual por un codificador humano" no ha producido mejores resultados que las optimizaciones del compilador desde hace más de una década. De hecho, un humano que juega con el ensamblaje generalmente
arruina

Ok, entiendo que estoy usando la terminología incorrecta, "optimización del compilador" en lugar de "optimización estática". Me gustaría señalar que, al menos en la industria de los juegos, tan recientemente como para la PS2 todavía usábamos ensamblaje codificado a mano en lugares para "optimizar" para los chips específicos que sabíamos que estaban en la consola; Los compiladores cruzados para estos nuevos chips aún no son tan sofisticados como los de las arquitecturas x86. Volviendo a la pregunta original anterior: el JIT tiene la ventaja de poder medir antes de optimizar, lo cual es algo bueno (TM)
billjamesdev

Tenga en cuenta que la mayoría de los GC de producción también usan ensambladores escritos a mano porque C / C ++ no lo corta.
JD

6

Generalmente, el algoritmo de su programa será mucho más importante para la velocidad de su aplicación que el idioma . Puede implementar un algoritmo deficiente en cualquier idioma, incluido C ++. Con eso en mente, generalmente podrá escribir el código que se ejecuta más rápido en un lenguaje que lo ayude a implementar un algoritmo más eficiente.

Los lenguajes de nivel superior lo hacen muy bien al proporcionar un acceso más fácil a muchas estructuras de datos preconstruidas eficientes y fomentar prácticas que lo ayudarán a evitar código ineficiente. Por supuesto, a veces también pueden facilitar la escritura de un montón de código realmente lento, por lo que aún debe conocer su plataforma.

Además, C ++ se está poniendo al día con características "nuevas" (tenga en cuenta las comillas) como los contenedores STL, los punteros automáticos, etc., consulte la biblioteca boost, por ejemplo. Y ocasionalmente puede encontrar que la forma más rápida de realizar alguna tarea requiere una técnica como la aritmética de punteros que está prohibida en un lenguaje de nivel superior, aunque normalmente le permiten llamar a una biblioteca escrita en un lenguaje que puede implementarla como desee. .

Lo principal es saber el lenguaje que estás usando, su API asociada, lo que puede hacer y cuáles son sus limitaciones.


5

Tampoco lo sé ... mis programas Java siempre son lentos. :-) Sin embargo, nunca he notado que los programas C # sean particularmente lentos.


4

Aquí hay otro punto de referencia interesante, que puede probar usted mismo en su propia computadora.

Compara ASM, VC ++, C #, Silverlight, subprograma Java, Javascript, Flash (AS3)

Demostración de velocidad del complemento Roozz

Tenga en cuenta que la velocidad de JavaScript varía mucho según el navegador que lo esté ejecutando. Lo mismo es cierto para Flash y Silverlight porque estos complementos se ejecutan en el mismo proceso que el navegador de alojamiento. Pero el complemento Roozz ejecuta archivos .exe estándar, que se ejecutan en su propio proceso, por lo que la velocidad no se ve afectada por el navegador de alojamiento.


4

Debe definir "rendimiento mejor que ...". Bueno, lo sé, preguntaste sobre la velocidad, pero no es todo lo que cuenta.

  • ¿Las máquinas virtuales realizan más sobrecarga de tiempo de ejecución? ¡Si!
  • ¿Comen más memoria de trabajo? ¡Si!
  • ¿Tienen mayores costos de inicio (inicialización en tiempo de ejecución y compilador JIT)? ¡Si!
  • ¿Requieren una gran biblioteca instalada? ¡Si!

Y así sucesivamente, es parcial, sí;)

Con C # y Java, paga un precio por lo que obtiene (codificación más rápida, administración automática de memoria, gran biblioteca, etc.). Pero no tienes mucho espacio para regatear los detalles: llévate el paquete completo o nada.

Incluso si esos lenguajes pueden optimizar algún código para ejecutarse más rápido que el código compilado, todo el enfoque es (en mi humilde opinión) ineficiente. ¡Imagínese conduciendo cada día 5 millas hasta su lugar de trabajo, con un camión! Es cómodo, se siente bien, estás a salvo (zona de deformación extrema) y después de pisar el acelerador durante algún tiempo, ¡incluso será tan rápido como un automóvil estándar! ¿Por qué no todos tenemos un camión para ir al trabajo? ;)

En C ++ obtienes lo que pagas, ni más ni menos.

Citando a Bjarne Stroustrup: "C ++ es mi lenguaje favorito de recolección de basura porque genera muy poca basura" .


Bueno, creo que tiene una buena idea de sus inconvenientes, también dijo: "C hace que sea fácil pegarse un tiro en el pie; C ++ lo hace más difícil, pero cuando lo haces te vuela toda la pierna";)
Frunsi

"¿Necesitan una biblioteca enorme instalada?" Creo que Java está abordando este problema con el rompecabezas del proyecto.
toc777

"En C ++ obtienes lo que pagas, ni más, ni menos". Contraejemplo: comparé una implementación de árbol RB en OCaml y C ++ (GNU GCC) que usaba una excepción para salir de la recursividad si un elemento que se estaba agregando ya estaba presente para reutilizar el conjunto existente. OCaml fue hasta 6 veces más rápido que C ++ porque no paga por buscar destructores cuando se desenrolla la pila.
JD

3
@Jon: pero en algún momento (¿más tarde?) Tiene que destruir los objetos de todos modos (al menos tiene que liberar su memoria). Y también tenga en cuenta que las excepciones son para casos excepcionales, al menos en C ++ esa regla debe respetarse. Las excepciones de C ++ pueden ser pesadas cuando ocurren excepciones, eso es una compensación.
Frunsi

@Jon: tal vez intente repetir su punto de referencia con timesun shell. Para que verifique todo el programa, no solo un aspecto. Entonces, ¿son similares los resultados?
Frunsi

3

El código ejecutable producido a partir de un compilador Java o C # no se interpreta, se compila en código nativo "justo a tiempo" (JIT). Por lo tanto, la primera vez que se encuentra un código en un programa Java / C # durante la ejecución, hay algunos gastos generales ya que el "compilador de tiempo de ejecución" (también conocido como compilador JIT) convierte el código de bytes (Java) o el código IL (C #) en instrucciones nativas de la máquina. Sin embargo, la próxima vez que se encuentre ese código mientras la aplicación aún se está ejecutando, el código nativo se ejecutará inmediatamente. Esto explica cómo algunos programas Java / C # parecen ser lentos al principio, pero luego funcionan mejor cuanto más se ejecutan. Un buen ejemplo es un sitio web ASP.Net. La primera vez que se accede al sitio web, puede ser un poco más lento, ya que el compilador JIT compila el código C # en código nativo.


3

Algunas buenas respuestas aquí sobre la pregunta específica que hizo. Me gustaría dar un paso atrás y mirar el panorama general.

Tenga en cuenta que la percepción de su usuario sobre la velocidad del software que escribe se ve afectada por muchos otros factores además de qué tan bien optimiza el codegen. Aquí hay unos ejemplos:

  • La administración manual de la memoria es difícil de hacer correctamente (sin fugas) y aún más difícil de hacer de manera eficiente (memoria libre poco después de que haya terminado). En general, es más probable que el uso de un GC produzca un programa que gestione bien la memoria. ¿Está dispuesto a trabajar muy duro y retrasar la entrega de su software, en un intento de superar al GC?

  • Mi C # es más fácil de leer y comprender que mi C ++. También tengo más formas de convencerme de que mi código C # funciona correctamente. Eso significa que puedo optimizar mis algoritmos con menos riesgo de introducir errores (y a los usuarios no les gusta el software que falla, ¡incluso si lo hace rápidamente!)

  • Puedo crear mi software más rápido en C # que en C ++. Eso libera tiempo para trabajar en el rendimiento y aún así entregar mi software a tiempo.

  • Es más fácil escribir una buena interfaz de usuario en C # que en C ++, por lo que es más probable que pueda pasar el trabajo a un segundo plano mientras la interfaz de usuario se mantiene receptiva, o proporcionar progreso o escuchar la interfaz de usuario cuando el programa tiene que bloquearse por un tiempo. Esto no hace que nada sea más rápido, pero hace que los usuarios estén más contentos de esperar.

Todo lo que dije sobre C # probablemente sea cierto para Java, simplemente no tengo la experiencia para decirlo con seguridad.


3

Si es un programador de Java / C # que está aprendiendo C ++, tendrá la tentación de seguir pensando en términos de Java / C # y traducir literalmente a la sintaxis de C ++. En ese caso, solo obtiene los beneficios mencionados anteriormente del código nativo frente al interpretado / JIT. Para obtener la mayor ganancia de rendimiento en C ++ frente a Java / C #, debe aprender a pensar en C ++ y diseñar código específicamente para aprovechar las fortalezas de C ++.

Parafraseando a Edsger Dijkstra : [su primer idioma] mutila la mente más allá de la recuperación.
Parafraseando a Jeff Atwood : puedes escribir [tu primer idioma] en cualquier idioma nuevo.


1
Sospecho que el dicho "Puedes escribir FORTRAN en cualquier idioma" es anterior a la carrera de Jeff.
David Thornley

3

Una de las optimizaciones JIT más importantes es la inserción de métodos. Java incluso puede incluir métodos virtuales en línea si puede garantizar la corrección del tiempo de ejecución. Este tipo de optimización generalmente no puede ser realizado por compiladores estáticos estándar porque necesita un análisis de todo el programa, lo cual es difícil debido a la compilación separada (en contraste, JIT tiene todo el programa disponible). La inserción de métodos mejora otras optimizaciones, proporcionando bloques de código más grandes para optimizar.

La asignación de memoria estándar en Java / C # también es más rápida y la desasignación (GC) no es mucho más lenta, sino menos determinista.


Tenga en cuenta que freey deletetampoco son deterministas y GC se puede hacer determinista sin asignar.
JD

3

Es poco probable que los lenguajes de máquina virtual superen a los lenguajes compilados, pero pueden acercarse lo suficiente como para que no importe, por (al menos) las siguientes razones (estoy hablando de Java aquí ya que nunca he hecho C #).

1 / El Java Runtime Environment generalmente puede detectar fragmentos de código que se ejecutan con frecuencia y realizar la compilación Just-In-Time (JIT) de esas secciones para que, en el futuro, se ejecuten a la velocidad de compilación completa.

2 / Grandes porciones de las bibliotecas de Java se compilan para que, cuando llame a una función de biblioteca, esté ejecutando código compilado, no interpretado. Puede ver el código (en C) descargando OpenJDK.

3 / A menos que esté haciendo cálculos masivos, la mayor parte del tiempo que su programa se está ejecutando, está esperando la entrada de un humano muy lento (relativamente hablando).

4 / Dado que gran parte de la validación del código de bytes de Java se realiza en el momento de cargar la clase, la sobrecarga normal de las comprobaciones en tiempo de ejecución se reduce considerablemente.

5 / En el peor de los casos, el código de alto rendimiento se puede extraer a un módulo compilado y llamar desde Java (ver JNI) para que se ejecute a toda velocidad.

En resumen, el código de bytes de Java nunca superará al lenguaje de máquina nativo, pero hay formas de mitigar esto. La gran ventaja de Java (como yo lo veo) es la enorme biblioteca estándar y la naturaleza multiplataforma.


1
Con respecto al elemento 2, "2 / Grandes porciones de las bibliotecas de Java se compilan de modo que, cuando llama a una función de biblioteca, está ejecutando código compilado, no interpretado": ¿Tiene una cita para eso? Si fuera realmente como lo describe, esperaría encontrarme mucho con el código nativo de mi depurador, pero no es así.
cero

Los depuradores de Re: cero a menudo utilizan rutas menos eficientes pero más expresivas y, por lo tanto, no son un buen marcador para nada relacionado con el rendimiento.
Guvante

2
Hay otra gran ganancia de rendimiento para esta biblioteca HUGH: el código de la biblioteca probablemente esté mejor escrito que lo que muchos programadores escribirán por su cuenta (dado un tiempo limitado y falta de conocimiento especializado) y en Java, debido a muchas razones, los programadores a menudo usan la biblioteca.
Liran Orevi

3

Orion Adrian , permíteme invertir tu publicación para ver cuán infundadas son tus comentarios, porque también se puede decir mucho sobre C ++. Y decir que el compilador Java / C # optimiza las funciones vacías realmente te hace sonar como si no lo estuvieras mi experto en optimización, porque a) ¿por qué un programa real debería contener funciones vacías, excepto por un código heredado realmente malo, b) que realmente no lo es optimización negra y de vanguardia.

Aparte de esa frase, despotricó descaradamente sobre punteros, pero ¿no funcionan los objetos en Java y C # básicamente como punteros de C ++? ¿No pueden superponerse? ¿No pueden ser nulos? C (y la mayoría de las implementaciones de C ++) tiene la palabra clave restrict, ambos tienen tipos de valor, C ++ tiene referencia a valor con garantía no nula. ¿Qué ofrecen Java y C #?

>>>>>>>>>>>

En general, C y C ++ pueden ser tan rápidos o más rápidos porque el compilador AOT, un compilador que compila su código antes de la implementación, de una vez por todas, en su servidor de compilación de muchos núcleos de alta memoria, puede hacer optimizaciones que un programa compilado en C # no puede porque tiene mucho tiempo para hacerlo. El compilador puede determinar si la máquina es Intel o AMD; Pentium 4, Core Solo o Core Duo; o si es compatible con SSE4, etc., y si su compilador no es compatible con el envío en tiempo de ejecución, puede resolverlo usted mismo implementando un puñado de binarios especializados.

El programa AC # se compila comúnmente al ejecutarlo para que funcione decentemente bien en todas las máquinas, pero no se optimiza tanto como podría estarlo para una sola configuración (es decir, procesador, conjunto de instrucciones, otro hardware), y debe pasar algo de tiempo primero. Las características como la fisión de bucle, la inversión de bucle, la vectorización automática, la optimización de todo el programa, la expansión de la plantilla, la salida a bolsa y muchas más, son muy difíciles de resolver todas y por completo de una manera que no moleste al usuario final.

Además, ciertas características del lenguaje permiten al compilador en C ++ o C hacer suposiciones sobre su código que le permiten optimizar ciertas partes que simplemente no son seguras para el compilador de Java / C #. Cuando no tiene acceso a la identificación de tipo completo de genéricos o un flujo de programa garantizado, hay muchas optimizaciones que simplemente no son seguras.

Además, C ++ y C realizan muchas asignaciones de pila a la vez con solo un incremento de registro, que seguramente es más eficiente que las asignaciones de Javas y C # en cuanto a la capa de abstracción entre el recolector de basura y su código.

Ahora no puedo hablar por Java en el siguiente punto, pero sé que los compiladores de C ++, por ejemplo, eliminarán métodos y llamadas a métodos cuando sepa que el cuerpo del método está vacío, eliminará subexpresiones comunes, puede intentarlo y volver a intentarlo. para encontrar el uso óptimo del registro, no impone la verificación de límites, autovectorizará los bucles y los bucles internos y se invertirá de adentro hacia afuera, mueve los condicionales fuera de los bucles, divide y deshace los bucles. Expandirá std :: vector en matrices nativas de cero sobrecarga como lo haría en C. Hará optimizaciones entre procedimientos. Construirá valores de retorno directamente en el sitio de la persona que llama. Doblará y propagará expresiones. Reordenará los datos de una manera amigable con la caché. Hará saltar hilos. Le permite escribir trazadores de rayos en tiempo de compilación sin sobrecarga de tiempo de ejecución. Hará optimizaciones basadas en gráficos muy costosas. Hará reducción de fuerza, si reemplaza ciertos códigos con código sintácticamente totalmente desigual pero semánticamente equivalente (el antiguo "xor foo, foo" es simplemente la optimización más simple, aunque obsoleta de este tipo). Si lo solicita amablemente, puede omitir los estándares de punto flotante IEEE y habilitar aún más optimizaciones, como el reordenamiento de operandos de punto flotante. Una vez que haya masajeado y masacrado su código, es posible que se repita todo el proceso, porque a menudo, ciertas optimizaciones sientan las bases para optimizaciones aún más seguras. También podría volver a intentarlo con parámetros mezclados y ver cómo puntúa la otra variante en su clasificación interna. Y utilizará este tipo de lógica en todo su código. donde reemplaza ciertos códigos con un código sintácticamente totalmente desigual pero semánticamente equivalente (el antiguo "xor foo, foo" es simplemente la optimización más simple, aunque obsoleta de este tipo). Si lo solicita amablemente, puede omitir los estándares de punto flotante IEEE y habilitar aún más optimizaciones, como el reordenamiento de operandos de punto flotante. Una vez que haya masajeado y masacrado su código, es posible que se repita todo el proceso, porque a menudo, ciertas optimizaciones sientan las bases para optimizaciones aún más seguras. También podría volver a intentarlo con parámetros mezclados y ver cómo puntúa la otra variante en su clasificación interna. Y utilizará este tipo de lógica en todo su código. donde reemplaza ciertos códigos con un código sintácticamente totalmente desigual pero semánticamente equivalente (el antiguo "xor foo, foo" es simplemente la optimización más simple, aunque obsoleta de este tipo). Si lo solicita amablemente, puede omitir los estándares de punto flotante IEEE y habilitar aún más optimizaciones, como el reordenamiento de operandos de punto flotante. Una vez que haya masajeado y masacrado su código, es posible que se repita todo el proceso, porque a menudo, ciertas optimizaciones sientan las bases para optimizaciones aún más seguras. También podría volver a intentarlo con parámetros mezclados y ver cómo puntúa la otra variante en su clasificación interna. Y utilizará este tipo de lógica en todo su código. Si lo solicita amablemente, puede omitir los estándares de punto flotante IEEE y habilitar aún más optimizaciones, como el reordenamiento de operandos de punto flotante. Una vez que haya masajeado y masacrado su código, es posible que se repita todo el proceso, porque a menudo, ciertas optimizaciones sientan las bases para optimizaciones aún más seguras. También podría volver a intentarlo con parámetros mezclados y ver cómo puntúa la otra variante en su clasificación interna. Y utilizará este tipo de lógica en todo su código. Si lo solicita amablemente, puede omitir los estándares de punto flotante IEEE y habilitar aún más optimizaciones, como el reordenamiento de operandos de punto flotante. Una vez que haya masajeado y masacrado su código, es posible que se repita todo el proceso, porque a menudo, ciertas optimizaciones sientan las bases para optimizaciones aún más seguras. También podría volver a intentarlo con parámetros mezclados y ver cómo la otra variante puntúa en su clasificación interna. Y utilizará este tipo de lógica en todo su código. También podría volver a intentarlo con parámetros mezclados y ver cómo la otra variante puntúa en su clasificación interna. Y utilizará este tipo de lógica en todo su código. También podría volver a intentarlo con parámetros mezclados y ver cómo la otra variante puntúa en su clasificación interna. Y utilizará este tipo de lógica en todo su código.

Entonces, como puede ver, hay muchas razones por las que ciertas implementaciones de C ++ o C serán más rápidas.

Dicho todo esto, se pueden hacer muchas optimizaciones en C ++ que eliminarán todo lo que pueda hacer con C #, especialmente en el ámbito de procesamiento numérico, en tiempo real y cercano al metal, pero no exclusivamente allí. Ni siquiera tiene que tocar un solo puntero para recorrer un largo camino.

Entonces, dependiendo de lo que estés escribiendo, iría con uno u otro. Pero si está escribiendo algo que no depende del hardware (controlador, videojuego, etc.), no me preocuparía por el rendimiento de C # (de nuevo, no puedo hablar de Java). Estará bien.

<<<<<<<<<<

Generalmente, ciertos argumentos generalizados pueden sonar bien en publicaciones específicas, pero generalmente no suenan ciertamente creíbles.

De todos modos, para hacer las paces: AOT es genial, al igual que JIT . La única respuesta correcta puede ser: depende. Y las personas realmente inteligentes saben que, de todos modos, puedes usar lo mejor de ambos mundos.


2

Solo sucedería si el intérprete de Java está produciendo un código de máquina que en realidad está mejor optimizado que el código de máquina que su compilador está generando para el código de C ++ que está escribiendo, hasta el punto en que el código de C ++ es más lento que el de Java y el costo de interpretación.

Sin embargo, las probabilidades de que eso suceda son bastante bajas, a menos que tal vez Java tenga una biblioteca muy bien escrita y usted tenga su propia biblioteca C ++ mal escrita.


También creo que hay un cierto peso del lenguaje, cuando se trabaja en un nivel inferior, con menos abstracción, se desarrolla un programa que es más rápido. Esto no está relacionado con los puntos sobre la ejecución del código de bytes en sí.
Brian R. Bondy

2

En realidad, C # no se ejecuta realmente en una máquina virtual como lo hace Java. IL se compila en lenguaje ensamblador, que es código completamente nativo y se ejecuta a la misma velocidad que el código nativo. Puede pre-JIT una aplicación .NET que elimina por completo el costo de JIT y luego está ejecutando código completamente nativo.

La desaceleración con .NET vendrá no porque el código .NET sea más lento, sino porque hace mucho más detrás de escena para hacer cosas como recolectar basura, verificar referencias, almacenar marcos de pila completos, etc. Esto puede ser bastante poderoso y útil cuando aplicaciones de construcción, pero también tiene un costo. Tenga en cuenta que también puede hacer todas estas cosas en un programa C ++ (gran parte de la funcionalidad básica de .NET es en realidad código .NET que puede ver en ROTOR). Sin embargo, si escribiera manualmente la misma funcionalidad, probablemente terminaría con un programa mucho más lento, ya que el tiempo de ejecución de .NET se ha optimizado y ajustado con precisión.

Dicho esto, una de las fortalezas del código administrado es que puede ser completamente verificable, es decir. puede verificar que el código nunca accederá a la memoria de otros procesos o eliminará los mensajes antes de ejecutarlo. Microsoft tiene un prototipo de investigación de un sistema operativo totalmente administrado que sorprendentemente ha demostrado que un entorno 100% administrado puede funcionar significativamente más rápido que cualquier sistema operativo moderno al aprovechar esta verificación para desactivar las funciones de seguridad que ya no son necesarias para los programas administrados. (estamos hablando como 10x en algunos casos). SE Radio tiene un gran episodio hablando de este proyecto.


1

En algunos casos, el código administrado puede ser más rápido que el código nativo. Por ejemplo, los algoritmos de recolección de basura "marcar y barrer" permiten que entornos como JRE o CLR liberen una gran cantidad de objetos de corta duración (generalmente) en una sola pasada, donde la mayoría de los objetos del montón de C / C ++ se liberan de uno en uno. un momento.

De wikipedia :

Para muchos propósitos prácticos, los algoritmos intensivos en asignación / desasignación implementados en lenguajes recolectados de basura pueden ser más rápidos que sus equivalentes usando la asignación manual del montón. Una de las principales razones de esto es que el recolector de basura permite que el sistema de tiempo de ejecución amortice las operaciones de asignación y desasignación de una manera potencialmente ventajosa.

Dicho esto, he escrito mucho C # y mucho C ++, y he ejecutado muchos puntos de referencia. En mi experiencia, C ++ es mucho más rápido que C #, de dos maneras: (1) si toma algún código que ha escrito en C #, lo transfiere a C ++, el código nativo tiende a ser más rápido. ¿Cuanto más rápido? Bueno, varía mucho, pero no es raro ver una mejora del 100% en la velocidad. (2) En algunos casos, la recolección de basura puede ralentizar enormemente una aplicación administrada. El .NET CLR hace un trabajo terrible con montones grandes (digamos,> 2GB) y puede terminar pasando mucho tiempo en GC, incluso en aplicaciones que tienen pocos, o incluso ningún, objetos de duración intermedia.

Por supuesto, en la mayoría de los casos con los que me he encontrado, los lenguajes administrados son lo suficientemente rápidos, por mucho, y la compensación de mantenimiento y codificación por el rendimiento adicional de C ++ simplemente no es buena.


1
El problema es que para procesos de larga ejecución, como un servidor web, su memoria con el tiempo se volverá tan fragmentada (en un programa escrito en C ++) que tendrá que implementar algo que se parezca a la recolección de basura (o reiniciar de vez en cuando, consulte IIS ).
Tony BenBrahim

3
No he observado eso en los grandes programas de Unix que están destinados a ejecutarse para siempre. Suelen estar escritos en C, que es incluso peor para la gestión de memoria que C ++.
David Thornley

Por supuesto, la pregunta es si estamos comparando una implementación de un programa en código administrado versus no administrado, o el rendimiento máximo teórico del lenguaje. Claramente, el código no administrado siempre puede ser al menos tan rápido como administrado, ya que en el peor de los casos podría simplemente escribir un programa no administrado que hiciera exactamente lo mismo que el código administrado. Pero la mayoría de los problemas de rendimiento son algorítmicos, no micro. Además, no optimiza el código administrado y no administrado de la misma manera, por lo que "C ++ en C #" generalmente no funcionará bien.
Kyoryu

2
En C / C ++, puede asignar objetos de corta duración en la pila, y lo hace cuando es apropiado. En el código administrado no puede , no tiene otra opción. Además, en C / C ++ se puede asignar listas de objetos en áreas contigous (new Foo [100]), en código administrado no se puede. Entonces, tu comparación no es válida. Bueno, este poder de elección supone una carga para los desarrolladores, pero de esta manera aprenden a conocer el mundo en el que viven (memoria ...).
Frunsi

@frunsi: "en C / C ++ puedes asignar listas de objetos en áreas contigo (nuevo Foo [100]), en código administrado no puedes". Eso es incorrecto. Los tipos de valores locales se asignan en pila e incluso puede asignar matrices de ellos en C #. Incluso hay sistemas de producción escritos en C # que son completamente sin asignación en el estado estacionario.
JD


1

En realidad, HotSpot JVM de Sun utiliza la ejecución en "modo mixto". Interpreta el código de bytes del método hasta que determina (generalmente a través de un contador de algún tipo) que un bloque de código en particular (método, bucle, bloque try-catch, etc.) se ejecutará mucho, luego lo compila con JIT. El tiempo requerido para compilar un método JIT a menudo es más largo que si el método fuera a ser interpretado si es un método que rara vez se ejecuta. El rendimiento suele ser mayor para el "modo mixto" porque la JVM no pierde tiempo en el código JIT que rara vez, si es que alguna vez, se ejecuta. C # y .NET no hacen esto. .NET JIT todo lo que, a menudo, es una pérdida de tiempo.


1

Ir a leer sobre Dynamo de HP Labs , un intérprete para PA-8000 que se ejecuta en PA-8000 y, a menudo, ejecuta programas más rápido que de forma nativa. ¡Entonces no parecerá nada sorprendente!

No lo considere un "paso intermedio": ejecutar un programa ya implica muchos otros pasos, en cualquier idioma.

A menudo se reduce a:

  • Los programas tienen puntos calientes, por lo que incluso si ejecuta más lentamente el 95% del cuerpo del código que tiene que ejecutar, aún puede ser competitivo en rendimiento si es más rápido en el 5% caliente

  • un HLL sabe más sobre su intención que un LLL como C / C ++, por lo que puede generar un código más optimizado (OCaml tiene aún más y, en la práctica, a menudo es incluso más rápido)

  • un compilador JIT tiene mucha información que un compilador estático no tiene (como los datos reales que tiene esta vez)

  • un compilador JIT puede hacer optimizaciones en tiempo de ejecución que los enlazadores tradicionales no pueden hacer (como reordenar las ramas para que el caso común sea plano, o llamadas de biblioteca en línea)

En general, C / C ++ son lenguajes bastante pésimos para el rendimiento: hay relativamente poca información sobre sus tipos de datos, no hay información sobre sus datos y no hay tiempo de ejecución dinámico que permita mucho en el camino de la optimización del tiempo de ejecución.


1

Es posible que obtenga ráfagas breves cuando Java o CLR es más rápido que C ++, pero en general el rendimiento es peor durante la vida útil de la aplicación: consulte www.codeproject.com/KB/dotnet/RuntimePerformance.aspx para obtener algunos resultados al respecto.



1

Tengo entendido que C / C ++ produce código nativo para ejecutarse en una arquitectura de máquina en particular. Por el contrario, lenguajes como Java y C # se ejecutan sobre una máquina virtual que abstrae la arquitectura nativa. Lógicamente, parecería imposible que Java o C # igualen la velocidad de C ++ debido a este paso intermedio, sin embargo, me han dicho que los últimos compiladores ("puntos calientes") pueden alcanzar esta velocidad o incluso superarla.

Eso es ilógico. El uso de una representación intermedia no degrada de forma inherente el rendimiento. Por ejemplo, llvm-gcc compila C y C ++ a través de LLVM IR (que es una máquina virtual de registro infinito) en código nativo y logra un rendimiento excelente (a menudo superando a GCC).

Quizás esto sea más una pregunta de compilador que una pregunta de idioma, pero ¿alguien puede explicar en inglés simple cómo es posible que uno de estos lenguajes de máquinas virtuales funcione mejor que un idioma nativo?

Aquí hay unos ejemplos:

  • Las máquinas virtuales con compilación JIT facilitan la generación de código en tiempo de ejecución (por ejemplo, System.Reflection.Emiten .NET) para que pueda compilar el código generado sobre la marcha en lenguajes como C # y F #, pero debe recurrir a escribir un intérprete relativamente lento en C o C ++. Por ejemplo, para implementar expresiones regulares.

  • Partes de la máquina virtual (por ejemplo, la barrera de escritura y el asignador) a menudo se escriben en ensamblador codificado a mano porque C y C ++ no generan código lo suficientemente rápido. Si un programa hace hincapié en estas partes de un sistema, posiblemente podría superar cualquier cosa que se pueda escribir en C o C ++.

  • La vinculación dinámica del código nativo requiere la conformidad con una ABI que puede impedir el rendimiento y obvia la optimización de todo el programa, mientras que la vinculación generalmente se difiere en las VM y puede beneficiarse de las optimizaciones de todo el programa (como los genéricos reificados de .NET).

También me gustaría abordar algunos problemas con la respuesta altamente votada de paercebal anterior (porque alguien sigue eliminando mis comentarios sobre su respuesta) que presenta una vista polarizada contraproducente:

El procesamiento del código se realizará en el momento de la compilación ...

Por lo tanto, la metaprogramación de plantillas solo funciona si el programa está disponible en tiempo de compilación, lo que a menudo no es el caso, por ejemplo, es imposible escribir una biblioteca de expresiones regulares de rendimiento competitivo en C ++ básico porque es incapaz de generar código en tiempo de ejecución (un aspecto importante de metaprogramación).

... jugar con tipos se realiza en tiempo de compilación ... el equivalente en Java o C # es, en el mejor de los casos, doloroso de escribir, y siempre será más lento y se resolverá en tiempo de ejecución incluso cuando los tipos se conozcan en tiempo de compilación.

En C #, eso solo es cierto para los tipos de referencia y no es cierto para los tipos de valor.

No importa la optimización JIT, nada funcionará tan rápido como el acceso directo del puntero a la memoria ... si tiene datos contiguos en la memoria, acceder a ellos a través de punteros C ++ (es decir, punteros C ... Démosle a Caesar lo que le corresponde) será mucho más rápido que en Java / C #.

La gente ha observado que Java supera a C ++ en la prueba SOR del punto de referencia SciMark2 precisamente porque los punteros impiden las optimizaciones relacionadas con el aliasing.

También vale la pena señalar que .NET se especializa en tipos de genéricos en bibliotecas vinculadas dinámicamente después de la vinculación, mientras que C ++ no puede porque las plantillas deben resolverse antes de vincular. Y, obviamente, la gran ventaja que tienen los genéricos sobre las plantillas son los mensajes de error comprensibles.


0

Además de lo que han dicho otros, según tengo entendido .NET y Java son mejores en la asignación de memoria. Por ejemplo, pueden compactar la memoria a medida que se fragmenta, mientras que C ++ no puede (de forma nativa, pero puede hacerlo si está utilizando un recolector de basura inteligente).


O si está utilizando un mejor asignador de C ++ y / o grupo de objetos. Esto está lejos de ser mágico, desde el punto de vista de C ++, y puede reducirse a que la "asignación de pila" se convierta en una asignación de pila tan rápida.
paercebal

Si siempre asigna todo en el montón, entonces .NET y Java pueden incluso funcionar mejor que C / C ++. Pero simplemente no hará esto en C / C ++.
Frunsi

0

Para cualquier cosa que necesite mucha velocidad, la JVM simplemente llama a una implementación de C ++, por lo que es más una cuestión de qué tan buenas son sus bibliotecas que de qué tan buena es la JVM para la mayoría de las cosas relacionadas con el sistema operativo. La recolección de basura reduce su memoria a la mitad, pero el uso de algunas de las características más sofisticadas de STL y Boost tendrá el mismo efecto pero con muchas veces el potencial de error.

Si solo está usando bibliotecas C ++ y muchas de sus características de alto nivel en un proyecto grande con muchas clases, probablemente terminará más lento que usar una JVM. Excepto mucho más propenso a errores.

Sin embargo, el beneficio de C ++ es que te permite optimizarte a ti mismo, de lo contrario, estás atascado con lo que hace el compilador / jvm. Si crea sus propios contenedores, escribe su propia administración de memoria que esté alineada, use SIMD y pase al ensamblaje aquí y allá, puede acelerar al menos 2 x 4 veces más de lo que la mayoría de los compiladores de C ++ harán por sí mismos. Para algunas operaciones, 16x-32x. Eso es usar los mismos algoritmos, si usa mejores algoritmos y paraleliza, los aumentos pueden ser dramáticos, a veces miles de veces más rápido que los métodos comúnmente usados.


0

Lo miro desde algunos puntos diferentes.

  1. Dado un tiempo y recursos infinitos, ¿el código administrado o no administrado será más rápido? Claramente, la respuesta es que el código no administrado siempre puede al menos vincular el código administrado en este aspecto, como en el peor de los casos, simplemente codificaría la solución de código administrado.
  2. Si toma un programa en un idioma y lo traduce directamente a otro, ¿cuánto peor funcionará? Probablemente mucho, por cualquier dos idiomas. La mayoría de los lenguajes requieren diferentes optimizaciones y tienen diferentes trampas. El micro rendimiento a menudo se trata de conocer estos detalles.
  3. Con un tiempo y recursos limitados, ¿cuál de los dos idiomas producirá un mejor resultado? Esta es la pregunta más interesante, ya que si bien un lenguaje administrado puede producir un código ligeramente más lento (dado un programa razonablemente escrito para ese idioma), esa versión probablemente se realizará antes, lo que permitirá dedicar más tiempo a la optimización.

0

Una respuesta muy breve: con un presupuesto fijo, obtendrá una aplicación Java con un mejor rendimiento que una aplicación C ++ (consideraciones de ROI). Además, la plataforma Java tiene perfiladores más decentes, que lo ayudarán a identificar sus puntos de acceso más rápidamente.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.