Si C / C ++ es más rápido, ¿por qué otros lenguajes no se compilan primero en C / C ++ y solo después en el código de máquina?

En realidad, muchos escriben código C que se compila. Los que conozco: GHC Haskell, Genie & Vala, Gambit / Chicken / Bigloo Scheme, ECL (Common Lisp) y Facebook tienen HipHop que convierte PHP a C ++. Incluso Perl tiene un compilador que puede crear código C (también muy grande). Pero este es el lenguaje nativo de C para no bytear código y luego para C.

Pero se compilan en C / C ++ como una forma de crear un binario ejecutable nativo, lo que lo hace más rápido (ya no se interpreta) o se ejecuta en una VM. De aquí viene el aumento de velocidad. No es cierto que C y C ++ sean siempre la forma más rápida para cada implementación, pero generalmente es cierto que un programador de C experimentado que conoce muy bien el hardware nativo puede escribir C de tal manera que se aproxime al ensamblaje. Por eso se creó C en primer lugar. C ++ es un animal diferente donde el programador tiene mucho menos control sobre la salida final, pero nuevamente un programador experimentado puede escribir código extremadamente eficiente que el optimizador mejora aún más.

El código de bytes es simplemente lenguaje ensamblador para una “computadora de software”, conocida como máquina virtual. Esta es una capa de código que ejecuta instrucciones similares a ensamblajes y realiza las operaciones de esas instrucciones utilizando el sistema operativo nativo (o hardware nativo directamente). Esto permite que un fragmento de código (como un programa Java) se ejecute en cualquier hardware. Puede pensar en ellos como un emulador para otra computadora (principalmente uno que nunca ha sido diseñado como hardware) en otra computadora.

La conversión a C primero, luego al código de bytes realmente no logra nada. Ese sería un compilador de C que escribe código de bytes en lugar de instrucciones nativas de la CPU. CLang para LLVM, en cierto modo, primero hace una especie de código de bytes, pero no directamente, pero este es un caso especial diseñado para permitir que LLVM se programe en C. Tiene mucho más sentido convertir el código de bytes en C para convertir en un ejecutable nativo, y si puede hacerlo, generalmente es tan fácil hacer que el compilador convierta el código de bytes en ensamblado nativo, como lo hace un compilador justo a tiempo para Java para navegadores o Limbo para Inferno.

Algunos lo hacen. El compilador original de CFront compiló C ++ en C. Uno de los Pascals originales compilados en C. Estoy seguro de que ha habido muchos otros, aunque es más probable que los nuevos lenguajes compilen en código de bytes.

Otros idiomas tienen cosas como administración de memoria y escritura dinámica. Esas llamadas a la biblioteca son parte de lo que es costoso y pueden requerir diferentes optimizaciones.

Para su información, el lenguaje de máquina es lo que finalmente se ejecuta en la máquina física. código de bytes es el nombre de un idioma intermedio cuando luego se genera en lenguaje de máquina. Java y C # compilan en diferentes versiones de código de bytes que luego se compilan en lenguaje máquina por un paso separado.

Los lenguajes modernos tienen más probabilidades de compilarse en los códigos de bytes Java o .Net.

Muchas buenas respuestas ya, solo mencionaré un par de aspectos que encuentro importantes. Traducir un lenguaje a C / C ++ sin cambiar la semántica y el lenguaje en sí mismo a menudo no traerá ninguna velocidad. Imagina que tenemos

clase A {
int n;
B x;
}
clase B {
int a, b, c;
}

En muchos idiomas significa que x es solo una referencia a un objeto del tipo B asignado por separado. Lo que significa que acceder a él implica una indirección más, una posible falla de caché más, una fuente de lentitud. Por otro lado, copiar significa solo copiar una referencia, 4–8 bytes en lugar de un objeto completo. En muchos de estos idiomas, los tipos de datos tienen esta semántica de referencia. Mientras que en C y C ++ dicho escenario significaría que todo el objeto de tipo B está incrustado en A, almacenado en la misma línea de caché, por lo que acceder a él es generalmente más rápido pero la copia es más lenta. También significa que en C / C ++ A y B pueden vivir en la pila donde la asignación y la desasignación son esencialmente gratuitas, mientras que en otros idiomas alguien tendrá que preocuparse por la desasignación B (a menudo esto es lo que hace GC). Verá, la semántica es diferente entre C / C ++ idiomático y lenguajes idiomáticos de alto nivel como Java, OCaml, Ruby, etc. Si solo traduce a C / C ++ manteniendo la semántica original, tendrá que mantener B como un objeto asignado por separado y acceder implicará los mismos errores de caché y lentitud, no gana nada al traducir a C. Y traducir a C / C ++ idiomático con semántica de valor en lugar de semántica de referencia no suele ser posible sin cambiar su idioma original, necesitará agregue muchas cosas como destructores y constructores de copias y todos los dolores de movimiento frente a copiar cabeza, básicamente tendrá un lenguaje completamente diferente para entonces.

Luego viene la gestión de la memoria. C / C ++ a menudo son rápidos porque el programador puede controlar cómo se asigna la memoria y cuando se libera, uno puede usar la pila y las arenas para hacer que estas cosas sean casi gratuitas. Significa más trabajo para el programador pero programas más rápidos si el programador hizo todo bien. Por el contrario, muchos lenguajes de alto nivel solo confían en el recolector de basura para recuperar la memoria no utilizada. E incluso si genera algún código C / C ++, ya que su fuente original no se preocupa por la desasignación de memoria, deberá seguir confiando en el mismo GC, por lo que nuevamente no habrá ninguna mejora en la velocidad. GC aún tendrá que recorrer el montón buscando objetos vivos (arruinando sus cachés) y copiarlos entre generaciones (desperdiciando ciclos de CPU).

Otro aspecto, es que C / C ++ no son adecuados para ser objetivos de generación de código, a menudo no son tan flexibles y expresivos como algunos compiladores necesitan. Por ejemplo, para implementar eficientemente la coincidencia de patrones, algo en lo que la mayoría de los lenguajes funcionales dependen mucho, necesita algunas construcciones de bajo nivel que estén fácilmente disponibles si usted (el compilador) genera el ensamblaje usted mismo pero no lo proporciona C / C ++. GCC tiene esos “gotos computados” que se pueden usar aquí, pero son cosas específicas de GCC, que en realidad no forman parte de C o C ++. No hay buenas herramientas en C / C ++ para controlar el diseño de los marcos de la pila e implementar un escaneo preciso de la pila para hacer un buen recolector de basura. No hay soporte para continuaciones de primera clase (algo disponible en otros lenguajes) en C / C ++, por lo que es muy difícil implementarlo si transpilas a C / C ++ en lugar de ensamblarlo. No hay buenas maneras de decirle al compilador C / C ++ qué partes de los datos no pueden solaparse, por lo que puede generar un código realmente rápido que funcione con varias matrices multidimensionales, por ejemplo, o algunas otras estructuras de datos. Por defecto, C / C ++ será demasiado conservador y cauteloso, perdiendo la oportunidad de vectorizar y optimizar mejor. En otras palabras, C / C ++ no es lo suficientemente expresivo como para ser un buen compilador objetivo, es por eso que la gente de Haskell hizo “C—” (c menos menos) para ellos.

¿Qué tan rápido es C / C ++ que Java / C #? Depende en gran medida del HW. En una PC típica basada en x86 no hay mucha diferencia. Tenemos un núcleo de CPU extremadamente rápido, el rendimiento está limitado por las operaciones de memoria, que son órdenes de magnitud más lentas. El procesamiento interno es gratis. Los programas escritos en diferentes idiomas pero que generan el mismo tráfico de memoria tienen un rendimiento muy similar. Desafortunadamente, Java tiende a generar un tráfico de memoria significativamente mayor, pero las grandes memorias caché ayudan mucho aquí.

En los sistemas embebidos, la situación es exactamente la opuesta: el uso de la memoria es gratuito / económico, cuesta lo mismo que el procesamiento interno. El rendimiento es proporcional a las instrucciones del lenguaje ensamblador ejecutadas para realizar una tarea en particular. Java no tendrá ningún sentido en sistemas embebidos de gama baja, microcontroladores. Tampoco está disponible. Este dominio está absolutamente dominado por C. (Es por eso que es el segundo lenguaje más popular del mundo). En los microcontroladores más avanzados, C ++ es otra opción, aunque significativamente menos popular.

C / C ++ es rápido, ya que NO se compilan en bytecode. Se compilan en el lenguaje ensamblador / máquina nativo del hardware. Por lo tanto, no hay necesidad de intérpretes, compiladores JIT, entornos de tiempo de ejecución, máquinas virtuales que contribuyan al menor rendimiento y a los horrendos requisitos de memoria de los sistemas basados ​​en bytecode.

En teoría, Java / C # podría compilarse en código nativo y podría lograr un rendimiento similar al de C / C ++. Sin embargo, para lograr esto en realidad, los programas Java tendrían que estructurarse de una manera diferente. En C / C ++, generalmente hay mucha optimización manual. El código no es necesario en una variante de configuración particular, no se compila en la imagen. En Java es normal que todas las decisiones de configuración se tomen en tiempo de ejecución, y se espera que la máquina virtual se encargue de optimizar lo que no es necesario. Esto no puede compilarse directa y eficientemente en código ensamblador, la ganancia de rendimiento sería menor de lo esperado.

Su idea original funciona hasta cierto punto. En el desarrollo basado en modelos, el código C / C ++ se genera a partir del modelo y luego se compila para el sistema de destino. UML no es realmente un lenguaje de programación, pero lo mismo podría hacerse con los lenguajes de programación también.

Algunos idiomas lo hacen, pero en general el paso de traducción adicional es lento, engorroso e inconveniente. Además, la calidad del código C generado puede ser ineficiente en la medida en que la excelente generación de código C no puede compensar. Finalmente, con el rápido hardware actual, la ventaja de rendimiento de C / C ++ no es tan significativa como solía ser. La pregunta sobre el rendimiento es, ¿cuándo es su aplicación lo suficientemente rápida? ¿Es lo suficientemente rápido?

Para cosas como los juegos de consola (donde la capacidad de respuesta máxima es crucial) y la computación numérica (donde puede ahorrar horas de cálculo numérico), la ventaja de velocidad de C ++ es muy apreciada. Para casi todo lo demás, Java y Python son “lo suficientemente rápidos”.

(También lo es Smalltalk, que es el lenguaje de programación más productivo del mundo. Elegiré Smalltalk sobre C ++ para la mayoría de las aplicaciones. Afortunadamente, Smalltalk es un lenguaje casi tan versátil como C ++, bueno para IA, móvil, robótica, IoT, VR , web, escritorio, servidor, etc.)

Problemas al hacer esas cosas.

  1. Se requerirá un transpilador (traductor utilizado para convertir un idioma de alto nivel a otro idioma de alto nivel) para cada caso específico para convertir su lenguaje XYZ a c ++.
  2. Luego, necesita un compilador de versión c / c ++ completo para convertir c ++ en código máquina.
  • Pero la introducción de un middleware seguramente aumentará el tiempo de ejecución.
  • Facebook estaba usando el mismo concepto en su tiempo intermedio, estaba convirtiendo First php a c ++ antes de ejecutar el código en la máquina del servidor con la ayuda de HipHop.
  • Pero estaba creando un obstáculo, ya que la introducción de un middleware de alguna manera aumenta el tiempo de ejecución. Entonces, el rendimiento general fue pobre.
  • Más tarde, al presentar la máquina virtual HipHop, tres personas reconstruyeron todos los sitios web de Facebook donde el código php ahora se convierte primero en código de bytes (código de máquina de la máquina virtual HipHop) y luego este código de bytes se ejecuta directamente en la máquina del servidor, de alguna manera este mecanismo de trabajo es similar a Java.

C y C ++ son lenguajes diferentes, por lo que no tiene sentido hablar de C / C ++

Entonces, un lenguaje no es más rápido (dado que un lenguaje de programación es una especificación, escrito en algún informe), es una implementación de un lenguaje (por ejemplo, un compilador de C dado) que es (o no) rápido. En la práctica, debe pedirle a su compilador de C que optimice (por ejemplo, pasando -O2 a gcc o clang ) para obtener un ejecutable rápido. Algunos compiladores de C (por ejemplo, tinycc) no se optimizan en absoluto y producen ejecutables muy lentos.

Muchas implementaciones de lenguaje son compiladores de C (a veces llamados compiladores de origen a origen o transpiladores). Di aquí varios ejemplos. Conozco pocos compiladores para código C ++ genuino , pero algunas implementaciones (como MELT) están emitiendo algo de C ++ sin usar todas las facilidades (como contenedores estándar) de C ++.

La ventaja de traducir a C es que el implementador no se preocupa por los detalles de generación de código de bajo nivel y puede dejar la optimización de bajo nivel al compilador de C que procesa el código de C emitido, y el código de C emitido probablemente puede ser portátil a varias computadoras de destino (sin embargo, con bastante frecuencia, el código C emitido supone un tamaño de palabra …). Por lo tanto, el implementador del lenguaje puede centrarse en otras características (por ejemplo, expresividad).

La desventaja de emitir código C es que está agregando la sobrecarga adicional de ejecutar el compilador de C (por lo que el proceso de traducción general puede ser lento). Una alternativa podría ser utilizar alguna biblioteca de generación de código como GCCJIT o LLVM (ambas con capacidad de optimización). Además, C no garantiza llamadas recursivas de cola. Por fin, muchos compiladores (por ejemplo, Ocaml o SBCL) están implementando convenciones de llamadas incompatibles con las del compilador de C y ABI. Por último, el código C emitido podría no ser amigable para la recolección de basura (a menos que use un GC conservador como el GC de Boehm, que es bastante lento), ya que las implementaciones rápidas de GC requieren una estrecha cooperación con el compilador y convenciones de llamadas específicas. Consulte el Manual de recolección de basura para obtener más información.

Una gran ventaja de estos llamados lenguajes “más lentos” es que son mucho más rápidos de desarrollar. C y C ++ tienen una larga fase de compilación y enlace que interrumpe los ciclos de desarrollo y prueba. Si difiere esto hasta el punto en que el código realmente necesita convertirse en lenguaje de máquina (por ejemplo, el compilador justo a tiempo para los lenguajes JVM), puede iterar su ciclo de vida de desarrollo mucho más rápido.

Y, para muchas organizaciones, resulta que el tiempo humano es más valioso que el tiempo de máquina.

Bueno, primero no es posible. En segundo lugar, supongamos hipotéticamente que es posible, entonces el lenguaje será aún más lento.

Analicemos

Lenguajes como C y C ++ se compilan directamente en código objeto. Algo que el hardware de la máquina puede entender.

En el caso de Java, para que sea independiente de la plataforma, genera un código intermedio llamado código de bytes que luego se traduce a código dependiente de la máquina para que pueda ejecutar este código en cualquier plataforma, sea PC de 32 o 64 bits.

Ahora, como saben, el código Java se cumple primero con el código de bytes y el código de bytes se interpreta, si entra otra capa para convertir el código en código puro dependiente de la máquina, se pierde todo el concepto de dependencia de la plataforma y, en segundo lugar, es extremadamente lento porque necesita convertir a código de byte y luego código de máquina compilado como ha mencionado.

Espero que esto ayude.

Actualmente, muchos idiomas se interpretan o se compilan en bytecode, que se ejecuta en una máquina virtual, y posiblemente “justo a tiempo” (JIT) compilado por la VM a medida que se ejecuta, para que se ejecute más rápido.

El problema con la compilación a través de C / C ++ es que operan en un modelo de memoria diferente que los lenguajes originales. Podría hacerse, pero no sé si habría una mejora significativa de la velocidad, ya que parte de la idea de usar los idiomas originales es que no tiene que preocuparse por asignar su propia memoria o tener desbordamientos del búfer. Esta configuración, en esencia, fusionaría el código VM con el código de la aplicación para cada programa, y ​​eso no sería un uso eficiente del espacio en disco o la memoria.

Una mejor respuesta para programas de ejecución más rápida en estos otros idiomas sería tener un estándar de código de bytes común para ellos y luego implementar lo que ahora es una VM de software en un nuevo procesador de hardware o coprocesador.

Si C / C ++ es más rápido, ¿por qué otros lenguajes no se compilan primero en C / C ++ y solo después en el código de máquina?

No ayudaría en teoría. Aunque puede ayudar en la práctica.

Estoy respondiendo esto porque creo que las otras respuestas están perdiendo el punto.

La pregunta es si la compilación a través de C / C ++ en el código de la máquina dará como resultado un ejecutable más rápido que directamente en el código de la máquina.

En un nivel teórico puro, si un programa P en Lenguaje L y compila al código máquina M a través de C (o C ++), entonces podemos escribir otro compilador que compila P a M directamente. C y C ++ no abren el rango de posibles representaciones de código de máquina de P.

En un nivel más práctico, en muchos casos, los compiladores de C / C ++ disponibles podrían ser los mejores compiladores de optimización disponibles en una plataforma dada y superar a cualquier cosa que los implementadores del lenguaje L puedan producir. Esa es solo una característica de la popularidad (por lo tanto, la inversión) y que las personas que usan C & C ++ a menudo tienen razones de rendimiento para elegir esos lenguajes y el mercado de C & C ++ favorece buenos compiladores de optimización.

Vale la pena notar que C & C ++ (tienen el potencial de) producir código más rápido porque le permiten al programador acceso de bajo nivel a la memoria sin la sobrecarga de cosas como la verificación de rango o la violación de acceso. Si el lenguaje L no permite tales construcciones, entonces el código intermediario de C & C ++ no las explotará (necesariamente) y es probable que una inspección del código intermediario revele lo que parece ser un programa C (o C ++) cauteloso / ineficiente que no aprovecha el acceso de bajo nivel de esos idiomas.

Eso es a menos que el compilador de L a C (o C ++) sea lo suficientemente inteligente como para detectar dónde puede descartar la verificación de rango. Por ejemplo, si identifica un bucle sobre el rango de una matriz, podría darse cuenta de que puede omitir los límites de verificación en el bucle, pero si hay acceso aleatorio a una matriz, podría dejarlo en su lugar y así sucesivamente.

Sin embargo, nada de eso contradice el punto de que podríamos (al menos teóricamente) construir un compilador que aprovechó esas mismas ventajas sin pasar por C (o C ++).

En el caso de Java hay más que una compilación JIT de bytecode. El punto de acceso de Java también optimiza las cosas durante la ejecución de un programa. Por ejemplo, puede optar por incluir métodos frecuentes en línea (por lo que realmente ya no vale la pena declarar diligentemente los métodos con la esperanza de que se pongan en línea ).

En los lenguajes compilados, todas las optimizaciones se realizan por adelantado sin tener en cuenta la dinámica de tiempo de ejecución de un programa. No hay forma de recompilar piezas porque todo lo que hay es solo código de máquina y ninguna fuente de representación de un nivel superior de un programa como bytecode. Sin mencionar que no hay un punto de acceso que haga la optimización, solo una CPU masticando el código ejecutable. Sospecho que sin la inteligencia de JVM, Java sería un poco más lento debido a que es un lenguaje de nivel superior.

En los primeros días, si Java, una idea que tal vez se usara en un servidor sonaba como una blasfemia. Nadie lo tomó muy en serio. Pero, con el tiempo, con los avances de JVM, evolucionó en un cambio de juego y empujó a C a un nicho donde se requiere una huella súper baja y en niveles más bajos si cosas como el sistema operativo, los controladores y demás.

No puede obtener mágicamente rendimiento o eficiencia usando C / C ++ (el rendimiento es más una propiedad de diseño / arquitectura). ¡ Definitivamente no puede obtener mágicamente rendimiento o eficiencia al usarlo como un paso de traducción! C no es esencial de ninguna manera; de hecho, en cuanto al rendimiento, fácilmente podría empeorar introduciendo este paso.

La ventaja de apuntar a C / C ++ es que puede ser más fácil que generar código de máquina usted mismo, y tampoco tiene que preocuparse por la generación de código para arquitecturas múltiples, por lo que puede “ejecutarse en todas partes” en cierto sentido sin invertir mucho trabajo.

Necesitaría un traductor de fuente a fuente (*) para generar código C / C ++ a partir de programas escritos en otros idiomas.

El uso de dicho traductor viene con una sobrecarga de traducción y, por no mencionar, puede perder parte de su optimización para algoritmos compartidos compatibles con caché diseñados para computación de alto rendimiento, por ejemplo.

Estoy seguro de que puede haber ocasiones en que hacerlo pueda parecer razonable y ventajoso, pero sería un poco exagerado cuando su base de código no sea significativamente masiva y computacionalmente intensiva.

Por otra parte, si su trabajo es computacionalmente intensivo, entonces no codificaría mal en un lenguaje mediocre y lo traduciría a un idioma mejor más adelante. Hacerlo sería como viajar de Maryland a Washington, DC a través de Texas.

Si valora el rendimiento de su trabajo, primero encontrará y aprenderá el mejor idioma posible para escribir un mejor código en él.

(*) Un traductor de fuente a fuente es un compilador que convierte un programa escrito en un lenguaje de alto nivel en un programa escrito en otro lenguaje de alto nivel.

Porque no lo es: imagina que estás en un lenguaje altamente dinámico y quieres poder mutar los tipos de objetos en tiempo de ejecución. Si primero quisieras compilar en C, ¡no tendrías más remedio que usar una tabla hash para representar objetos! Pero con un compilador JIT, como los de los tiempos de ejecución modernos del lenguaje dinámico de alto rendimiento, esto se puede compilar en código de máquina que utiliza compensaciones directas: si el tipo cambia, no hay problema, el código se desecha y se genera un nuevo código.

Si intentas hacer lo mismo que estás haciendo en esos lenguajes en C, verías que es mucho, mucho más lento: la compilación estática de un lenguaje dinámico simplemente no funciona muy bien (hay excepciones, pero son estirando la definición de “compilación estática”).

Porque es más barato traducir el código en una representación intermedia en su lugar. Esto es exactamente lo que hace LLVM: traduce cualquier lenguaje frontend a “bytecode” de LLVM que está cerca del ensamblaje (LLVM significa “Máquina virtual de bajo nivel”)

C y C ++ son más rápidos, pero eso solo si comienzas a codificar en esos idiomas. C en particular está cerca de la máquina, por lo que puede obtener alta velocidad más fácilmente.

Si comienza en, por ejemplo, Python, todavía queda toda la sobrecarga de Python de verificar los tipos de datos, la conversión y mucho más, antes de que la operación real se pueda hacer en C.

More Interesting

¿El algoritmo de Bellman-Ford es pseudo polinomial?

¿Cuáles son algunas de las aplicaciones más elegantes de la teoría de grafos?

Si un ciclo se ejecuta infinitas veces, ¿por qué recibimos un error de tiempo de ejecución en lugar de un error de límite de tiempo excedido? Además, ¿cuál es el valor de infinito para los compiladores en línea?

¿Para qué sirve la función explotar ()?

No puedo encontrar el máximo / mínimo de este problema del multiplicador de Lagrange sin obtener un número complejo cerca del final. ¿Qué estoy haciendo mal?

¿Qué ocupa más bytes: un DVD de Windows 7 o el índice del primer decimal en pi en el que se encuentra un DVD de Windows 7?

Cómo calcular (la 11ma potencia 10) / (la 10ma potencia 10) sin usar una calculadora

¿Ser bueno en matemáticas ayuda en la programación?

¿Es esto cierto? "Repetir es humano, repetir, divino". En caso afirmativo o no, ¿por qué?

Teoría de la complejidad computacional: ¿Hay conjeturas famosas que alguna vez se creyeron firmemente que eran ciertas pero que luego se demostraron falsas?

¿Qué cantidad de cosas de matemáticas que caen en matemáticas discretas necesitas?

¿Qué es la relajación en las matemáticas?

Sea m una máquina de turing y sea w una corriente de entrada de m. ¿Cómo puedo definir el tiempo de ejecución tm (w) de m en la entrada w?

Cómo escribir un programa en C para imprimir todas las permutaciones posibles de un número dado

¿Travel seles man proplem es np o np completo o np difícil?