etiquetas ejemplos c performance low-level assembly

c - ejemplos - ¿Por qué programa en asamblea?



etiquetas meta html5 (29)

"Sí". Pero, entiendo que en su mayor parte los beneficios de escribir código en ensamblador no valen la pena. El rendimiento recibido por escribirlo en asamblea tiende a ser más pequeño que simplemente centrarse en pensar más en el problema y pasar el tiempo pensando en una mejor manera de hacerlo.

John Carmack y Michael Abrash, quienes fueron en gran parte responsables de escribir Quake y todos los códigos de alto rendimiento que se aplicaron a los motores de juegos de ID, entran en este detallado detalle en este book .

También estoy de acuerdo con Ólafur Waage en que, hoy en día, los compiladores son bastante inteligentes ya menudo emplean muchas técnicas que aprovechan los impulsos arquitectónicos ocultos.

Tengo una pregunta para todos los hackers de bajo nivel hardcore que hay. Me encontré con esta frase en un blog. Realmente no creo que la fuente importe (es Haack si realmente te importa) porque parece ser una declaración común.

Por ejemplo, muchos juegos 3-D modernos tienen su motor central de alto rendimiento escrito en C ++ y ensamblado.

En lo que respecta al ensamblaje, ¿el código está escrito en ensamblaje porque no desea que un compilador emita instrucciones adicionales o utilice bytes excesivos, o está utilizando mejores algoritmos que no puede expresar en C (o no puede expresar sin el compilador los molesta)?

Entiendo completamente que es importante entender las cosas de bajo nivel. Solo quiero entender el por qué del programa en ensamblaje después de que lo entiendes.


Actualmente, para los códigos secuenciales al menos, un compilador decente casi siempre supera incluso a un programador en lenguaje ensamblador muy experimentado. Pero para los códigos vectoriales es otra historia. Los compiladores ampliamente desplegados no hacen un gran trabajo explotando las capacidades vector-paralelo de la unidad x86 SSE, por ejemplo. Soy un escritor de compiladores, y la explotación de SSE encabeza mi lista de razones para ir por su cuenta en lugar de confiar en el compilador.


Además de otras cosas mencionadas, todos los idiomas superiores tienen ciertas limitaciones. Es por eso que algunas personas optan por programar en ASM, para tener un control total sobre su código.

Otros disfrutan de ejecutables muy pequeños, en el rango de 20-60KB, por ejemplo, comprueban HiEditor , que es implementado por el autor del control HiEdit, excelente control de edición poderoso para Windows con resaltado de sintaxis y pestañas en solo ~ 50kb). En mi colección tengo más de 20 de esos controles de oro de Excell, como ssheets a html.


Algunas instrucciones / indicadores / control simplemente no están ahí en el nivel C.

Por ejemplo, la comprobación del desbordamiento en x86 es el indicador de desbordamiento simple. Esta opción no está disponible en C.


Aparte de los proyectos muy pequeños en CPU muy pequeñas, no me gustaría programar un proyecto completo en conjunto. Sin embargo, es común encontrar que un cuello de botella de rendimiento se puede aliviar con la codificación manual estratégica de algunos bucles internos.

En algunos casos, todo lo que realmente se requiere es reemplazar algún constructo de lenguaje con una instrucción que no se pueda esperar que el optimizador descubra cómo usarlo. Un ejemplo típico es en aplicaciones DSP donde las operaciones vectoriales y las operaciones de acumulación múltiple son difíciles de descubrir para un optimizador, pero fáciles de codificar a mano.

Por ejemplo, ciertos modelos de SH4 contienen matriz 4x4 y 4 instrucciones vectoriales. Vi una gran mejora en el rendimiento de un algoritmo de corrección de color al reemplazar las operaciones C equivalentes en una matriz de 3x3 con las instrucciones adecuadas, con el pequeño costo de ampliar la matriz de corrección a 4x4 para que coincida con la suposición de hardware. Eso se logró al escribir no más de una docena de líneas de ensamblaje, y llevar a cabo ajustes de coincidencia con los tipos de datos relacionados y el almacenamiento en un puñado de lugares en el código C circundante.


Casi todos los motores de juegos medianos o grandes o la biblioteca que he visto hasta la fecha tienen algunas versiones de ensamblaje optimizadas a mano disponibles para operaciones de matriz como la concatenación de matrices 4x4. Parece que los compiladores inevitablemente pasan por alto algunas de las optimizaciones inteligentes (reutilizando registros, desenrollando bucles de una manera máximamente eficiente, aprovechando las instrucciones específicas de la máquina, etc.) cuando se trabaja con matrices grandes. Estas funciones de manipulación de matriz casi siempre son "puntos de acceso" en el perfil, también.

También he visto que el montaje codificado a mano se usa mucho para el despacho personalizado, cosas como FastDelegate, pero compilador y máquina específica.

Finalmente, si tiene rutinas de servicio de interrupción, asm puede marcar la diferencia en el mundo: hay ciertas operaciones que no desea que ocurran bajo interrupción, y desea que sus manejadores de interrupción "entren y salgan rápidamente". .. sabes casi exactamente lo que va a pasar en tu ISR si está en asm, y te alienta a mantener cortas las cosas sangrientas (lo cual es una buena práctica de todos modos).


Comencé la programación profesional en lenguaje ensamblador en mi primer trabajo (años 80). Para sistemas integrados, las demandas de memoria - RAM y EPROM - fueron bajas. Podrías escribir un código estricto que fuera fácil de usar.

A fines de los años 80, cambié a C. El código era más fácil de escribir, depurar y mantener. Se crearon fragmentos de código muy pequeños en el ensamblador, para mí fue cuando estaba escribiendo el cambio de contexto en un RTOS en rollo propio. (Algo que no deberías hacer más a menos que sea un "proyecto de ciencia").

Verá fragmentos de ensamblador en algunos códigos de kernel de Linux. Más recientemente lo he buscado en spinlocks y otros códigos de sincronización. Estas piezas de código necesitan tener acceso a operaciones atómicas de prueba y configuración, manipulación de cachés, etc.

Creo que sería difícil optimizar los compiladores de C modernos para la mayoría de la programación general.

Estoy de acuerdo con @altCognito en que probablemente le conviene dedicar más tiempo a pensar más sobre el problema y a hacer las cosas mejor. Por alguna razón, los programadores suelen centrarse en las microeficiencias y descuidar las macroeficiencias. El lenguaje ensamblador para mejorar el rendimiento es una microeficiencia. Retroceder para una vista más amplia del sistema puede exponer los problemas macro en un sistema. Resolver los problemas macro a menudo puede producir mejores ganancias de rendimiento. Una vez que los problemas macro se resuelven, se colapsan al nivel micro.

Supongo que los microproblemas están bajo el control de un solo programador y en un dominio más pequeño. Alterar el comportamiento en el nivel macro requiere la comunicación con más personas, algo que algunos programadores evitan. Todo el vaquero vs lo del equipo.


Creo que a muchos desarrolladores de juegos les sorprendería esta información.

La mayoría de los juegos que conozco usan tan poco ensamblaje como sea posible. En algunos casos ninguno y en el peor, uno o dos bucles o funciones.

Esa cita es demasiado generalizada, y no es tan cierta como lo fue hace una década.

Pero bueno, los simples hechos no deberían obstaculizar la verdadera cruzada de un hacker a favor del ensamblaje. ;)


Creo que estás malinterpretando esta afirmación:

Por ejemplo, muchos juegos 3-D modernos tienen su motor central de alto rendimiento escrito en C ++ y ensamblado.

Los juegos (y la mayoría de los programas en estos días) no están "escritos en ensamblaje" de la misma manera que están "escritos en C ++". Ese blog no dice que una fracción significativa del juego esté diseñado en ensamblaje, o que un equipo de programadores se siente y se desarrolle en conjunto como su idioma principal.

Lo que esto realmente significa es que los desarrolladores primero escriben el juego y lo hacen funcionar en C ++. Luego lo perfilan, descubren cuáles son los cuellos de botella, y si vale la pena, los optimizan al armarlos. O, si ya tienen experiencia, saben qué partes van a ser cuellos de botella, y tienen piezas optimizadas de otros juegos que han creado.

El punto de programación en el montaje es el mismo que siempre: velocidad . Sería ridículo escribir una gran cantidad de código en ensamblador, pero hay algunas optimizaciones que el compilador no conoce, y para una ventana de código lo suficientemente pequeña, a un humano le va a ir mejor.

Por ejemplo, para coma flotante, los compiladores tienden a ser bastante conservadores y pueden no estar al tanto de algunas de las características más avanzadas de su arquitectura. Si está dispuesto a aceptar algún error, generalmente puede hacerlo mejor que el compilador, y vale la pena escribir ese pequeño código en el ensamblado si encuentra que se le dedica mucho tiempo.

Aquí hay algunos ejemplos más relevantes:

Ejemplos de juegos

  • Artículo de Intel sobre la optimización de un motor de juego utilizando intrínsecamente SSE. El código final utiliza intrínsecos (no ensamblador en línea), por lo que la cantidad de ensamblaje puro es muy pequeña. Pero miran la salida del ensamblador por el compilador para descubrir exactamente qué optimizar.

  • Raíz cuadrada inversa rápida de Quake. Nuevamente, la rutina no tiene ensamblador, pero necesita saber algo sobre arquitectura para hacer este tipo de optimización. Los autores saben qué operaciones son rápidas (multiplicar, cambiar) y cuáles son lentas (dividir, cuadrar). De modo que se les ocurre una implementación muy complicada de raíz cuadrada que evita por completo las operaciones lentas.

Computación de alto rendimiento

  • Fuera del dominio de los juegos, las personas en informática científica a menudo optimizan las cosas para que funcionen rápidamente con el último hardware. Piense en esto como juegos en los que no puede engañar a la física.

    Un gran ejemplo reciente de esto es Lattice Quantum Chromodynamics (Lattice QCD) . Este documento describe cómo el problema se reduce a un kernel computacional muy pequeño, que fue optimizado en gran medida para PowerPC 440 en un IBM Blue Gene / L. Cada 440 tiene dos FPU, y admiten algunas operaciones ternarias especiales que son difíciles de explotar para los compiladores. Sin estas optimizaciones, Lattice QCD habría corrido mucho más lento, lo cual es costoso cuando su problema requiere millones de horas de CPU en máquinas costosas.

    Si se pregunta por qué esto es importante, consulte el artículo en Science que surgió de este trabajo. Usando Lattice QCD, estos tipos calcularon la masa de un protón a partir de los primeros principios, y mostraron el año pasado que el 90% de la masa proviene de una fuerte energía de unión forzada y el resto de los quarks. Eso es E=mc2 en acción. Aquí hay un resumen .

Por todo lo anterior, las aplicaciones no están diseñadas ni escritas al 100% en ensamblaje, ni siquiera están cerca. Pero cuando las personas realmente necesitan velocidad, se enfocan en escribir las partes clave de su código para volar en un hardware específico.


El código SSE funciona mejor en ensamblaje que los intrínsecos del compilador, al menos en MSVC. (es decir, no crea copias adicionales de los datos)


Hace unos años que no escribo en ensamblaje, pero las dos razones por las que solía ser eran:

  • El desafío de la cosa! Pasé por un período de varios meses hace años cuando escribía todo en ensamblaje x86 (los días de DOS y Windows 3.1). Básicamente, me enseñó un conjunto de operaciones de bajo nivel, I/O hardware, etc.
  • Para algunas cosas, mantuvo el tamaño pequeño (de nuevo DOS y Windows 3.1 al escribir TSR )

Sigo mirando el ensamblaje de codificación de nuevo, y no es más que el desafío y la alegría de la cosa. No tengo ninguna otra razón para hacerlo :-)


Hay un aspecto de la programación ensamblador que otros no han mencionado: la sensación de satisfacción que obtiene al saber que cada byte en una aplicación es el resultado de su propio esfuerzo, no del compilador. Por un segundo, no quisiera volver a escribir aplicaciones completas en ensamblador como solía hacer a principios de los 80, pero echo de menos esa sensación a veces ...


La última vez que escribí en ensamblador fue cuando no pude convencer al compilador de que generara código libre de posición libre de libc.

La próxima vez será por la misma razón.

Por supuesto, solía tener otras razones .


La única codificación de ensamblador que sigo haciendo es para hardware incrustado con escasos recursos. Como lo menciona Leander, el montaje aún es muy adecuado para los ISR , donde el código debe ser rápido y bien comprendido.

Una razón secundaria para mí es mantener mi conocimiento de ensamblaje funcional. Ser capaz de examinar y comprender los pasos que está tomando la CPU para hacer mi oferta simplemente me hace sentir bien.


La máquina virtual Dalvik que interpreta el bytecode para aplicaciones Java en teléfonos Android usa ensamblador para el despachador. Esta movie (aproximadamente 31 minutos, pero vale la pena ver toda la película!) Explica cómo

"Todavía hay casos en que un ser humano puede hacer mejor que un compilador".


Los defectos tienden a correr por línea (declaración, punto de código, etc.); Si bien es cierto que para la mayoría de los problemas, el ensamblaje usaría muchas más líneas que los lenguajes de nivel superior, ocasionalmente hay casos en los que es el mejor (más conciso, menos líneas) del problema en cuestión. La mayoría de estos casos involucran a los sospechosos habituales, como los controladores y los ataques de bits en los sistemas integrados.


Los juegos tienen bastante rendimiento y, aunque mientras tanto, los optimizadores son bastante buenos, un "programador maestro" aún puede obtener más rendimiento codificando a mano las piezas correctas en el ensamblaje.

Nunca comience a optimizar su programa sin primero perfilarlo. Después de la creación de perfiles debería ser capaz de identificar los cuellos de botella y si la búsqueda de mejores algoritmos y cosas por el estilo ya no es suficiente, puede intentar codificar algunas cosas en el ensamblaje.


No he codificado en lenguaje ensamblador durante muchos años, pero puedo dar varias razones que vi con frecuencia:

  • No todos los compiladores pueden hacer uso de ciertas optimizaciones de CPU y conjunto de instrucciones (por ejemplo, los nuevos conjuntos de instrucciones que Intel agrega de vez en cuando). Esperar a que los compiladores se pongan al día significa perder una ventaja competitiva.

  • Es más fácil hacer coincidir el código real con la arquitectura y optimización conocidas de la CPU. Por ejemplo, cosas que usted sabe sobre el mecanismo de búsqueda, almacenamiento en caché, etc. Esto se supone que es transparente para el desarrollador, pero el hecho es que no lo es, es por eso que los escritores de compiladores pueden optimizar.

  • Ciertos accesos a nivel de hardware solo son posibles / prácticos a través del lenguaje ensamblador (por ejemplo, al escribir el controlador del dispositivo).

  • En ocasiones, el razonamiento formal es más fácil para el lenguaje ensamblador que para el lenguaje de alto nivel, ya que ya sabe cuál es el diseño final o casi final del código.

  • La programación de ciertas tarjetas gráficas 3D (hacia finales de la década de 1990) en ausencia de API a menudo era más práctica y eficiente en lenguaje ensamblador, y algunas veces no era posible en otros lenguajes. Pero nuevamente, esto implicó juegos a nivel de expertos basados ​​en la arquitectura del acelerador, como el movimiento manual de datos dentro y fuera en cierto orden.

Dudo que muchas personas usen el lenguaje ensamblador cuando lo haría un lenguaje de nivel superior, especialmente cuando ese lenguaje es C. La optimización manual de grandes cantidades de código de propósito general no es práctica.


No lo hago, pero al menos me he esforzado por intentarlo, y lo intento mucho en algún momento de la furture (pronto con suerte). No puede ser malo conocer más cosas de bajo nivel y cómo funcionan las cosas detrás de escena cuando estoy programando en un lenguaje de alto nivel. Lamentablemente, es difícil conseguir tiempo con un trabajo de tiempo completo como desarrollador / consultor y como padre. Pero lo haré en el momento oportuno, eso es seguro.


No más velocidad, pero Control . La velocidad a veces vendrá del control, pero es la única razón para codificar en el ensamblaje. Cualquier otra razón se reduce al control (es decir, SSE y otras optimizaciones manuales, controladores de dispositivos y códigos dependientes del dispositivo, etc.).


No parece ser mencionado, así que pensé en agregarlo: en el desarrollo de juegos modernos, creo que al menos parte del ensamblaje que se está escribiendo no es para la CPU en absoluto. Es para la GPU, en forma de programas de sombreado .

Esto podría ser necesario por todo tipo de razones, a veces simplemente porque cualquier lenguaje de sombreado de nivel superior utilizado no permite que la operación exacta se exprese en el número exacto de instrucciones deseadas, para ajustarse a alguna restricción de tamaño, velocidad o cualquier combinación . Como siempre, con la programación en lenguaje ensamblador, supongo.


Otra razón podría ser cuando el compilador disponible no es lo suficientemente bueno para una arquitectura y la cantidad de código necesario en el programa no es tan larga o compleja como para que el programador se pierda en ella. Intente programar un microcontrolador para un sistema integrado, generalmente el ensamblaje será mucho más fácil.


Por lo general, el montaje de un profano es más lento que C (debido a la optimización de C), pero muchos juegos (recuerdo claramente Doom ) tenían que tener secciones específicas del juego en Assembly para que funcionara sin problemas en las máquinas normales.

Este es el ejemplo al que me refiero.


Si está programando un microcontrolador de 8 bits de baja potencia con 128 bytes de RAM y 4K de memoria de programa, no tiene muchas opciones sobre el uso del ensamblaje. Sin embargo, a veces, cuando se usa un microcontrolador más poderoso, se necesita una determinada acción en un momento exacto. El lenguaje de ensamblaje resulta útil, ya que puede contar las instrucciones y medir los ciclos de reloj utilizados por su código.


Si estuvieras cerca de todos los esfuerzos de remediación del año 2000, podrías haber ganado mucho dinero si supieras Asamblea. Todavía hay mucho código heredado escrito en él, y ese código ocasionalmente necesita mantenimiento.


Si soy capaz de superar a GCC y Visual C ++ 2008 (también conocido como Visual C ++ 9.0), las personas estarán interesadas en entrevistarme sobre cómo es posible.

Esta es la razón por la que por el momento solo leo cosas en ensamblaje y solo escribo __asm ​​int 3 cuando es necesario.

Espero que esta ayuda ...


Solo he hablado personalmente con un desarrollador sobre su uso del ensamblaje. Estaba trabajando en el firmware que trataba con los controles para un reproductor portátil de mp3. Hacer el trabajo en conjunto tuvo 2 propósitos:

  1. Velocidad: los retrasos deben ser mínimos.
  2. Costo: al ser mínimo con el código, el hardware necesario para ejecutarlo podría ser un poco menos potente. Cuando se producen en masa millones de unidades, esto puede sumarse.

Tengo tres o cuatro rutinas de ensamblador (en aproximadamente 20 MB de fuente) en mis fuentes en el trabajo. Todos ellos son SSE(2) y están relacionados con las operaciones en imágenes (bastante grandes, piense 2400x2048 y más grandes).

Para hobby, trabajo en un compilador, y allí tienes más ensamblador. Las bibliotecas de tiempo de ejecución a menudo están llenas de ellas, la mayoría de ellas tienen que ver con cosas que desafían el régimen de procedimiento normal (como ayudantes para excepciones, etc.)

No tengo ningún ensamblador para mi microcontrolador. La mayoría de los microcontroladores modernos tienen tanto hardware periférico (interrupción de contadores controlados, incluso codificadores de cuadratura completos y bloques de construcción en serie) que el ensamblador para optimizar los bucles a menudo ya no es necesario. Con los precios de flash actuales, lo mismo aplica para la memoria de código. Además, a menudo existen rangos de dispositivos compatibles con pin, por lo que si se agota sistemáticamente la potencia de la CPU o el espacio de flash no suele ser un problema.

A menos que envíe realmente 100000 dispositivos y el ensamblador de programación hace que sea realmente importante ahorrar al colocar en un chip flash una categoría más pequeña. Pero no estoy en esa categoría.

Mucha gente piensa que incrustado es una excusa para el ensamblador, pero sus controladores tienen más poder de CPU que las máquinas en las que se desarrolló Unix . (Microchip viene con 40 y 60 microcontroladores MIPS por menos de USD 10).

Sin embargo, mucha gente está atascada con el legado, ya que cambiar la arquitectura de microchip no es fácil. Además, el código HLL depende mucho de la arquitectura (porque usa la periferia del hardware, registros para controlar la E / S, etc.). Entonces, a veces hay buenas razones para seguir manteniendo un proyecto en ensamblador (tuve la suerte de poder configurar los asuntos en una nueva arquitectura desde cero). Pero a menudo las personas se engañan a sí mismas de que realmente necesitan al ensamblador.


Una vez me hice cargo de un proyecto DSP que el programador anterior había escrito principalmente en código ensamblador, a excepción de la lógica de detección de tonos que se había escrito en C, utilizando punto flotante (en un DSP de punto fijo). La lógica de detección de tono se ejecutó a aproximadamente 1/20 de tiempo real.

Terminé reescribiendo casi todo desde cero. Casi todo estaba en C excepto por algunos pequeños manejadores de interrupciones y unas pocas docenas de líneas de código relacionadas con el manejo de interrupciones y la detección de frecuencias de bajo nivel, que se ejecuta más de 100 veces más rápido que el código anterior.

Creo que es importante tener en cuenta que, en muchos casos, habrá mayores oportunidades para mejorar la velocidad con rutinas pequeñas que las grandes, especialmente si el ensamblador escrito a mano puede ajustarse a todo en los registros, pero un compilador no lo haría. bastante administrar. Si un bucle es lo suficientemente grande como para que no pueda mantener todo en registros de todos modos, hay muchas menos oportunidades de mejora.