¿Por qué los lenguajes de alto nivel aparentemente nunca llegan a los de nivel inferior en términos de velocidad? Ejemplos de lenguajes de alto nivel serían Python, Haskell y Java. Los lenguajes de bajo nivel serían más difíciles de definir, pero digamos C. Las comparaciones se pueden encontrar en todo el Internet y todos están de acuerdo en que C es mucho más rápido, a veces por un factor de 10 o más.
¿Qué es lo que causa una diferencia tan grande en el rendimiento y por qué los idiomas de alto nivel no pueden ponerse al día?
Inicialmente, creía que todo esto es culpa de los compiladores y que las cosas mejorarán en el futuro, pero algunos de los lenguajes de nivel superior más populares han existido durante décadas y todavía se quedan atrás en lo que respecta a la velocidad. ¿No pueden simplemente compilar a un árbol de sintaxis similar a C y luego seguir los mismos procedimientos que generan el código de máquina? ¿O tal vez tiene algo que ver con la sintaxis misma?
Ejemplos:
Respuestas:
Desacreditando algunos mitos
No existe un lenguaje rápido. Un lenguaje generalmente puede producir código rápido, pero diferentes idiomas sobresalirán en diferentes puntos de referencia. Podemos clasificar los idiomas en un conjunto particular de puntos de referencia defectuosos, pero no podemos clasificar los idiomas en el vacío.
El código C tiende a ser más rápido porque las personas que necesitan cada pulgada de rendimiento usan C. Una estadística de que C es más rápido "por un factor de 10" podría ser inexacta, porque podría ser que a las personas que usan Python simplemente no les importa tanto sobre la velocidad y no escribió el código óptimo de Python. Esto lo vemos en particular con idiomas como Haskell. Si te esfuerzas mucho , puedes escribir Haskell que funcione a la par con C. Pero la mayoría de la gente no necesita esa actuación, por lo que tenemos un montón de comparaciones defectuosas.
A veces, es la inseguridad, no la abstracción, lo que hace que C sea rápido. Su falta de límites de matriz y controles de puntero nulo ahorran tiempo, y han sido la causa de numerosos agujeros de seguridad a lo largo de los años.
Los idiomas no son rápidos, las implementaciones son rápidas. Muchos lenguajes abstractos comienzan lentamente, porque la velocidad no es su objetivo, pero se vuelven más rápidos a medida que se agregan más y más optimizaciones.
La abstracción vs la compensación de velocidad es quizás inexacta. Sugeriría una mejor comparación:
Simplicidad, velocidad, abstracción: elige dos.
Si estamos ejecutando algoritmos idénticos en diferentes idiomas, la cuestión de la velocidad se reduce al problema de "¿Cuántas cosas debemos hacer en tiempo de ejecución para que esto funcione?"
En un lenguaje muy abstracto que es simple , como Python o JavaScript, porque no conocemos la representación de los datos hasta el tiempo de ejecución, termina habiendo muchas desreferencias de puntero y comprobaciones dinámicas en tiempo de ejecución, que son lentas.
Del mismo modo, hay muchas comprobaciones que deben hacerse para garantizar que no arruine su computadora. Cuando llama a una función en python, debe asegurarse de que el objeto que está llamando en realidad es una función, ya que de lo contrario podría terminar ejecutando código aleatorio que hace cosas terribles.
Finalmente, la mayoría de los lenguajes abstractos tienen sobrecarga de recolección de basura. La mayoría de los programadores han acordado que tener que rastrear las vidas de la memoria asignada dinámicamente es un dolor, y prefieren que un recolector de basura lo haga por ellos en tiempo de ejecución. Esto lleva tiempo, que un programa C no necesita gastar en GC.
Resumen no significa lento
Sin embargo, hay idiomas que son abstractos y rápidos. El más dominante en esta era es el óxido. Al introducir un verificador de préstamos y un sistema de tipo sofisticado, permite el código abstracto y utiliza información en tiempo de compilación para reducir la cantidad de trabajo que necesitamos hacer en tiempo de ejecución (es decir, recolección de basura).
Cualquier lenguaje escrito estáticamente nos ahorra tiempo al reducir el número de comprobaciones de tiempo de ejecución, e introduce complejidad al exigirnos que complacemos a un verificador de tipos en tiempo de compilación. Del mismo modo, si tenemos un lenguaje que codifica si un valor puede ser nulo en su sistema de tipos, podemos ahorrar tiempo con las comprobaciones de tiempo de compilación de puntero nulo.
fuente
Aquí hay algunas ideas clave sobre esto.
Una comparación fácil / estudio de caso para hacer abstracción de wrt vs velocidad es java vs c ++. Java fue diseñado para abstraer algunos de los aspectos de nivel inferior de C ++, como la gestión de la memoria. En los primeros días (alrededor de la invención del lenguaje a mediados de la década de 1990), la detección de basura de Java no fue muy rápida, pero después de algunas décadas de investigación, los recolectores de basura están extremadamente afinados / rápidos / optimizados, por lo que los recolectores de basura se eliminan en gran medida como un drenaje de rendimiento en java. por ejemplo, vea incluso este titular de 1998: las pruebas de rendimiento muestran Java tan rápido como C ++ / javaworld
los lenguajes de programación y su larga evolución tienen una "estructura piramidal / jerárquica" inherente como una especie de patrón de diseño trascendente. En la parte superior de la pirámide hay algo que controla otras secciones inferiores de la pirámide. en otras palabras, los bloques de construcción están hechos de bloques de construcción. Esto se puede ver en la estructura API también. en este sentido, una mayor abstracción siempre conduce a algún componente nuevo en la parte superior de la pirámide que controla otros componentes. en cierto sentido, no es tanto que todos los idiomas estén nivelados entre sí, sino que los idiomas recurren a rutinas en otros idiomas. por ejemplo, muchos lenguajes de script (python / ruby) a menudo llaman bibliotecas C o C ++, las rutinas numéricas o matriciales son un ejemplo típico de esto. por lo tanto, hay idiomas de nivel superior e inferior y los idiomas de nivel superior se llaman idiomas de nivel inferior, por así decirlo. en este sentido, medir la velocidad relativa no es realmente comparable.
se podría decir que se están inventando nuevos lenguajes todo el tiempo para tratar de optimizar la compensación de abstracción / velocidad, es decir, su objetivo clave de diseño. tal vez no sea tanto que una mayor abstracción siempre sacrifique la velocidad, sino que siempre se busca un mejor equilibrio con diseños más nuevos. por ejemplo, Google Go se optimizó de muchas maneras específicamente con el equilibrio en mente, para ser simultáneamente de alto nivel y de alto rendimiento. ver, por ejemplo, Google Go: por qué el lenguaje de programación de Google puede rivalizar con Java en la empresa / mundo tecnológico
fuente
La forma en que me gusta pensar sobre el rendimiento es "donde el caucho se encuentra con la carretera". La computadora ejecuta instrucciones, no abstracciones.
Lo que quiero ver es esto: ¿todas las instrucciones que se ejecutan "se ganan la vida" al contribuir sustancialmente al resultado final? Como un ejemplo demasiado simple, considere buscar una entrada en una tabla de 1024 entradas. Ese es un problema de 10 bits porque el programa tiene que "aprender" 10 bits antes de saber la respuesta. Si el algoritmo es una búsqueda binaria, cada iteración aporta 1 bit de información, ya que reduce la incertidumbre por un factor de 2. Por lo tanto, se necesitan 10 iteraciones, una para cada bit.
La búsqueda lineal, por otro lado, es inicialmente muy ineficiente porque las primeras iteraciones reducen la incertidumbre en un factor muy pequeño. Por lo tanto, no están aprendiendo mucho por el esfuerzo realizado.
OK, entonces si el compilador puede permitir al usuario envolver buenas instrucciones de una manera considerada "abstracta", está bien.
fuente
Por su naturaleza, la abstracción reduce la comunicación de información, tanto para el programador como para las capas inferiores del sistema (el compilador, las bibliotecas y el sistema de tiempo de ejecución). A favor de la abstracción, esto generalmente permite que las capas inferiores asuman que el programador no está preocupado por ningún comportamiento no especificado, proporcionando una mayor flexibilidad para suministrar el comportamiento que se especifica.
Un ejemplo de un beneficio potencial de este aspecto de "no me importa" es el diseño de datos. En C (baja abstracción), el compilador está más limitado en las optimizaciones de diseño de datos. Incluso si el compilador pudiera discernir (p. Ej., A través de la información del perfil) que las optimizaciones para evitar el intercambio de calor / frío o falsas acciones serían beneficiosas, generalmente se impide su aplicación. (Existe cierta libertad para especificar "como si", es decir, tratar la especificación de manera más abstracta, pero derivar todos los efectos secundarios potenciales agrega una carga al compilador).
Una especificación más abstracta también es más robusta frente a los cambios en las compensaciones y usos. Las capas inferiores están menos restringidas para reoptimizar el programa para nuevas características del sistema o nuevos usos. Un programador debe reescribir una especificación más concreta o las capas inferiores deben hacer un esfuerzo adicional para garantizar un comportamiento "como si".
El aspecto que obstaculiza el rendimiento de la abstracción de ocultación de información es "no se puede expresar", que las capas inferiores generalmente manejarán como "no sé". Esto significa que las capas inferiores deben discernir la información útil para la optimización de otros medios, como el uso general típico, el uso específico o la información específica del perfil.
El impacto del ocultamiento de información también funciona en la otra dirección. El programador puede ser más productivo al no tener que considerar y especificar cada detalle, pero el programador puede tener menos información sobre el impacto de las elecciones de diseño de nivel superior.
Por otro lado, cuando el código es más específico (menos abstracto), las capas más bajas del sistema pueden hacer más simplemente lo que se les pide que hagan. Si el código está bien escrito para su uso específico, se ajustará bien a su uso específico. Un lenguaje menos abstracto (o paradigma de programación) permite al programador optimizar la implementación mediante un diseño detallado y mediante el uso de información que no se comunica fácilmente en un lenguaje determinado a las capas inferiores.
Como se ha señalado, los lenguajes menos abstractos (o técnicas de programación) son atractivos cuando la habilidad y el esfuerzo adicionales del programador pueden producir resultados valiosos. Cuando se aplica más esfuerzo y habilidad del programador, los resultados generalmente serán mejores. Además, un sistema de lenguaje que se usa menos para aplicaciones críticas para el rendimiento (en lugar de enfatizar el esfuerzo de desarrollo o la confiabilidad, las verificaciones de límites y la recolección de basura no se trata solo de la productividad del programador, sino de la corrección, la abstracción que reduce la carga mental en el programador puede mejorar la confiabilidad) tendrá menos presión para mejorar el rendimiento.
La especificidad también funciona en contra del principio de no repetirse porque la optimización generalmente es posible al adaptar el código a un uso específico. Esto tiene implicaciones obvias de confiabilidad y esfuerzo de programación.
Las abstracciones proporcionadas por un lenguaje también pueden incluir trabajo no deseado o innecesario sin medios para elegir una abstracción menos pesada. Si bien a veces las capas inferiores pueden descubrir y eliminar el trabajo innecesario (por ejemplo, las comprobaciones de límites pueden extraerse del cuerpo de un bucle y eliminarse por completo en algunos casos), determinar que se trata de una optimización válida requiere más "habilidad y esfuerzo" El compilador.
La edad y la popularidad del idioma también son factores notables tanto en la disponibilidad de programadores calificados como en la calidad de las capas inferiores del sistema (incluidas bibliotecas maduras y ejemplos de código).
Otro factor de confusión en tales comparaciones es la diferencia algo ortogonal entre la compilación anticipada y la compilación justo a tiempo. Si bien la compilación justo a tiempo puede explotar más fácilmente la información de perfil (no depender del programador para proporcionar ejecuciones de perfil) y la optimización específica del sistema (la compilación anticipada puede apuntar a una compatibilidad más amplia), la sobrecarga de la optimización agresiva se contabiliza como parte del rendimiento del tiempo de ejecución. Los resultados JIT se pueden almacenar en caché, lo que reduce la sobrecarga para el código de uso común. (La alternativa de la reoptimización binaria puede proporcionar algunas ventajas de la compilación JIT, pero los formatos de distribución binarios tradicionales eliminan la mayoría de la información del código fuente, lo que puede forzar al sistema a intentar discernir la intención de una implementación específica).
(Los lenguajes de abstracción más bajos, debido a su énfasis en el control del programador, favorecen el uso de la compilación anticipada. La compilación de tiempo de instalación podría ser tolerada, aunque la selección de implementación de tiempo de enlace proporcionaría un mayor control del programador. La compilación JIT sacrifica el control significativo. )
También está la cuestión de la metodología de evaluación comparativa. Igual esfuerzo / habilidad es efectivamente imposible de establecer, pero aun así se podría lograr que los objetivos del lenguaje sesgarían los resultados. Si se requiere un tiempo de programación máximo bajo, un programa para un lenguaje menos abstracto podría fallar incluso si se escribe por completo en comparación con una expresión idiomática simple en un lenguaje más abstracto. Si se permitiera un tiempo / esfuerzo de programación máximo alto, los lenguajes de abstracción más baja tendrían una ventaja. Los puntos de referencia que presentan los mejores resultados de esfuerzo naturalmente estarían sesgados a favor de lenguajes menos abstractos.
A veces es posible programar de una manera menos idiomática en un lenguaje para obtener las ventajas de otros paradigmas de programación, pero incluso cuando el poder expresivo está disponible, las compensaciones por hacerlo pueden no ser favorables.
fuente