2010-12-02 8 views
15

Supongamos que he creado un conjunto de clases para abstraer algo y ahora me preocupa si mi compilador C++ podrá to peel off those wrappings and emit really clean, concise and fast code. ¿Cómo averiguo qué hizo el compilador?¿Cómo encuentro cómo el compilador de C++ implementa algo, excepto la inspección del código máquina emitido?

La única forma que conozco es to inspect the disassembly. Esto funciona bien para código simple, pero hay dos inconvenientes: the compiler might do it different when it compiles the same code again y también el análisis de código de máquina no es trivial, por lo que requiere un gran esfuerzo.

¿De qué otra manera puedo encontrar cómo el compilador decidió implementar lo que codifiqué en C++?

+0

Quiero saber la respuesta a esto también, pero realmente no puedo ver cómo la respuesta puede ser otra cosa que "Espero que el compilador lo mencione en alguna parte". –

+0

+1. sin embargo, no veo cómo se puede saber. además de desmontar, o conocer la fuente del compilador (y simularlo en la cabeza). – lijie

Respuesta

7

Me temo que no tienes suerte en este caso. Estás tratando de averiguar "qué hizo el compilador". Lo que hizo el compilador fue producir código de máquina. El desmontaje es simplemente una forma más legible del código máquina, pero no puede agregar información que no está allí. No se puede entender cómo funciona una picadora de carne mirando una hamburguesa.

3

Al sincronizar el código, se mide directamente su velocidad y puede evitar mirar el desmontaje por completo. Esto detectará cuándo el compilador, las modificaciones de código o los cambios de configuración sutiles han afectado el rendimiento (ya sea para bien o para mal). De esa forma, es mejor que el desmontaje, que es solo una medida indirecta.

Las cosas como el tamaño del código también pueden servir como posibles indicadores de problemas. Por lo menos, sugieren que algo ha cambiado. También puede indicar un aumento de código inesperado cuando el compilador debería haber reducido un montón de plantillas (o lo que sea) en una serie concisa de instrucciones.

Por supuesto, observar el desmontaje es una técnica excelente para desarrollar el código y ayudar a decidir si el compilador está haciendo una traducción lo suficientemente buena. Puede ver si está recibiendo el valor de su dinero, por así decirlo.

En otras palabras, mida lo que espera y luego sumérjase si cree que el compilador lo "engaña".

+1

En términos más generales, primero responda la pregunta "¿por qué le importa?" Luego verifique si lo que le interesa es lo suficientemente bueno. Si se mueve, si no; perfil para encontrar lo que está ocupando la mayor cantidad de recursos. A menudo, las respuestas a los tres te sorprenderán. – BCS

1

Karl respondió la respuesta a su pregunta. Si quiere ver lo que hizo el compilador, debe comenzar a revisar el código de ensamblaje que produjo: se requiere grasa en el codo. En cuanto a descubrir el "por qué" detrás del "cómo" de cómo implementó su código ... cada compilador (y cada compilación, potencialmente), como mencionó, es diferente. Existen diferentes enfoques, diferentes optimizaciones, etc. Sin embargo, no me preocuparía si está emitiendo un código de máquina limpio y conciso; la limpieza y la concisión deberían dejarse en el código fuente. La velocidad, por otro lado, es más o menos la responsabilidad del programador (perfil ftw). Las preocupaciones más interesantes son la corrección, el mantenimiento, la legibilidad, etc. Si desea ver si se realizó una optimización específica, los documentos del compilador pueden ser útiles (si están disponibles para su compilador). También puede intentar buscar para ver si el compilador implementa una técnica conocida para optimizar lo que sea. Sin embargo, si esos enfoques fallan, ya vuelves a leer el código ensamblador. Tenga en cuenta que el código que está revisando puede tener poco o ningún impacto en el rendimiento o el tamaño del archivo ejecutable: obtenga algunos datos duros antes de sumergirse en cualquiera de estas cosas.

+0

¿Entonces ahora no solo los desarrolladores deberían emitir código legible, los compiladores también deberían hacerlo? Puede obtenerlo si lo desea si usa -O0, casi un mapeo 1: 1 en todas partes. Creo que el OP significaba estar "limpio" sin derrames de registros innecesarios, sin llamadas a los constructores que no hacen nada, eliminando una función de contenedor llamando directamente a la función interna, etc. – hirschhornsalz

+0

@drhirsch Bonito comentario negativo :) Pero en serio, entendí en lo que estaba tratando, no quise decir que el compilador tenía que emitir código limpio (limpieza humana). Estaba hablando de eso, sin embargo, porque el OP quería entender lo que estaba sucediendo en el compilador mirando el desmontaje. – Gemini14

4

En realidad, me lo estaba preguntando.

He estado bastante interesado, durante los últimos meses, en el proyecto Clang.

Uno de los intereses particulares de Clang, la optimización wrt, es que puede emitir el código LLVM IR optimizado en lugar del código de máquina. El IR es un lenguaje ensamblador de alto nivel, con la noción de estructura y tipo. La mayoría de las optimizaciones pasadas en el juego de compilador Clang se realizan en el IR (la última ronda es por supuesto específica de la arquitectura y el backend lo realiza según las operaciones disponibles), esto significa que realmente se puede ver en el IR, si la creación del objeto (como en su pregunta vinculada) se optimizó o no.

Sé que es aún montaje (aunque de nivel superior), pero parece más fácil de leer para mí:

  • mucho menos códigos de operación
  • mecanografiadas objetos/punteros
  • no "registrar" las cosas o se requiere conocimiento "mágico"

¿Te conviene? :)?

+0

Por lo tanto, este lenguaje ensamblador IR de alto nivel ... ¡se parece mucho a "C" de su descripción! Ciertamente suena interesante ... ¿tiene un enlace a algunos ejemplos? – timday

+0

@timday: la referencia está allí http://llvm.org/docs/LangRef.html Puede encontrar un tutorial rápido aquí http://llvm.org/releases/2.6/docs/tutorial/JITTutorial1.html Es un ensamblaje de alto nivel y no parecido a C (especialmente porque está expresado en forma de SSA). –

+0

¡Gracias, muy interesante! – timday

1

En realidad, hay una forma de obtener lo que desea, si puede obtener su compilador , obtenga información de depuración DWARF. Habrá una descripción DWARF para cada función fuera de línea y dentro de esa descripción habrá (afortunadamente) entradas para cada función en línea. No es trivial leer DWARF, y en ocasiones los compiladores no producen DWARF completo o preciso, pero puede ser una fuente de información útil sobre lo que realmente hizo el compilador, que no está vinculado a ningún compilador o CPU. Una vez que tenga una biblioteca de lectura DWARF, encontrará todo tipo de herramientas útiles que puede construir a su alrededor .

No espere usarlo con Visual C++ ya que utiliza un formato de depuración diferente. (Pero es posible que pueda hacer consultas similares a través de la biblioteca de depuración ayudante que viene con él.)

2

Es posible encontrar un compilador que tenía una opción para volcar un post-optimización AST/representación - la forma legible sería ser es otro asunto. Si está utilizando GCC, existe la posibilidad de que no sea demasiado difícil, y que alguien ya lo haya hecho, GCCXML hace algo vagamente similar. De poco uso si el compilador en el que desea construir su código de producción no puede hacerlo.

Después de eso, algunos compiladores (por ejemplo, gcc con -S) pueden generar lenguaje de ensamblaje, que podría ser útilmente más claro que leer un desensamblador: por ejemplo, algunos compiladores alternan fuentes de alto nivel como comentarios y ensamblaje correspondiente.

En cuanto a los inconvenientes que mencionar:

el compilador podría hacerlo diferente cuando se compila el mismo código de nuevo

absolutamente, sólo la documentación del compilador y/o el código fuente puede decir que la posibilidad de eso, aunque puedes poner algunas verificaciones de rendimiento en las pruebas nocturnas para que te avise si el rendimiento cambia repentinamente

y también el código de máquina a nalysis no es trivial, por lo que requiere un esfuerzo.

Lo que plantea la pregunta: qué sería mejor.Puedo visualizar algún proceso donde ejecutas el compilador sobre tu código y registra cuando las variables se almacenan en caché en registros en puntos de uso, qué llamadas de función están enlineadas, incluso la cantidad máxima de ciclos de CPU que puede tomar una instrucción (cuando se puede conocer en tiempo de compilación)) etc. y produce algún registro del mismo, luego un visor/editor fuente que codifica y codifica el color de la fuente correspondiente. ¿Es ese el tipo de cosa que tienes en mente? ¿Sería útil? Quizás algunos más que otros, p. la información en blanco y negro sobre el uso del registro ignora la utilidad de los diversos niveles de caché de la CPU (y la utilización en tiempo de ejecución); el compilador probablemente ni siquiera intenta modelar eso de todos modos. Saber dónde se estaba haciendo realmente la incrustación me daría una sensación cálida y confusa. Pero, el perfilado parece ser más prometedor y útil en general. Me temo que los beneficios son más intuitivamente reales que en realidad, y los escritores de compiladores deberían buscar características de C++ 0x, instrumentación en tiempo de ejecución, introspección o escribir D "en el lado" ;-).

2

Quiere saber si el compilador produjo "código limpio, conciso y rápido".

"Limpiar" tiene poco significado aquí. El código limpio es un código que promueve la legibilidad y el mantenimiento, por parte de los seres humanos. Por lo tanto, esta propiedad se relaciona con lo que ve el programador, es decir, el código fuente. No hay ninguna noción de limpieza para el código binario producido por un compilador que solo será examinado por la CPU. Si escribió un buen conjunto de clases para resumir su problema, entonces su código es tan limpio como pueda.

"Código conciso" tiene dos significados. Para el código fuente, se trata de guardar los escasos recursos de ojo y cerebro del programador, pero, como señalé anteriormente, esto no se aplica a la salida del compilador, ya que no hay ningún humano involucrado en ese punto. El otro significado es sobre el código que es compacto, por lo que tiene un menor costo de almacenamiento. Esto puede tener un impacto en la velocidad de ejecución, porque la RAM es lenta y, por lo tanto, realmente desea que los bucles más internos de su código encajen en la memoria caché de nivel 1 de la CPU. El tamaño de las funciones producidas por el compilador se puede obtener con algunas herramientas de desarrollador; en sistemas que usan binutils de GNU, puede usar el comando size para obtener el código total y los tamaños de datos en un archivo de objeto (un compilado .o) y objdump para obtener más información. En particular, objdump -x dará el tamaño de cada función individual.

"Rápido" es algo que debe medirse. Si desea saber si su código es rápido o no, haga una evaluación comparativa. Si el código resulta demasiado lento para su problema (esto no ocurre con frecuencia) y tiene alguna razón teórica convincente para creer que el hardware podría funcionar mucho mejor (por ejemplo, porque calculó el número de personas involucradas) operaciones, profundizar en los manuales de CPU y dominar todos los problemas de ancho de banda de memoria y caché), y luego (y solo entonces) es hora de echar un vistazo a lo que el compilador hizo con su código. Salvo estas condiciones, la limpieza del código fuente es un problema mucho más importante.

Dicho todo esto, puede ser útil si tienes a priori nociones de lo que puede hacer un compilador. Esto requiere un poco de entrenamiento. Sugiero que eche un vistazo al clásico dragon book; pero, de lo contrario, tendrá que dedicar un tiempo a compilar un código de ejemplo y observar el resultado de ensamblaje. C++ no es el lenguaje más fácil para eso, es posible que desee comenzar con C. simple. Idealmente, una vez que sepa lo suficiente como para poder escribir su propio compilador, entonces sabrá lo que puede hacer un compilador, y puede adivinar lo que hacer en un código dado.

1

Si su compilador logra traducir sus "envoltorios y emitir un código realmente limpio, conciso y rápido", el esfuerzo de seguimiento del código emitido debe ser razonable.

Contrariamente a otra respuesta, creo que el código de ensamblaje emitido puede ser "limpio" si es (relativamente) fácil de mapear al código fuente original, si no consiste en llamadas por todo el lugar y que el sistema de saltos no es demasiado complejo. Con programación de código y reordenamiento de un código de máquina optimizado que también es legible es, por desgracia, una cosa del pasado.

Cuestiones relacionadas