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 finally
clá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:
- 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 )
- RAII sigue siendo incomparable ( GC todavía puede tener fugas (sí, tuve que manejar ese problema) y solo manejará la memoria. Incluso C #
using
no es tan fácil y poderoso porque escribir una implementación correcta de Dispose es difícil )
- C #
readonly
y Java final
no 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 using
palabra 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: