2011-05-28 23 views
9

Hemos estado desarrollando una gran aplicación financiera en un banco. Comenzó siendo 150k líneas de código realmente malo. Hace 1 mes, había bajado a poco más de la mitad, pero el tamaño del ejecutable aún era enorme. Esperaba que como estábamos haciendo que el código fuera más legible, pero el código de plantilla todavía generaba un gran código objeto, solo estábamos siendo más eficientes con nuestro esfuerzo.enormes ejecutables debido a los símbolos de depuración, ¿por qué?

La aplicación se divide en aproximadamente 5 objetos compartidos y una principal. Uno de los objetos compartidos más grandes era 40Mb y creció a 50 incluso mientras el código se reducía.

No me sorprendió por completo que el código comenzara a crecer, porque después de todo estamos agregando alguna funcionalidad. Pero me sorprendió que creciera un 20%. Ciertamente, nadie estuvo cerca de escribir el 20% del código, por lo que es difícil para mí imaginar cómo creció tanto. Ese módulo me resulta difícil de analizar, pero el viernes tengo nuevos puntos de datos que arrojan algo de luz.

Hay tal vez 10 feeds para servidores SOAP. El código es autogenerado, mal. Cada servicio tenía una clase analizador con exactamente el mismo código, algo así como:

#include <boost/shared_ptr.hpp> 
#include <xercesstuff...> 
class ParserService1 { 
public: 
    void parse() { 
    try { 
     Service1ContentHandler*p = new Service1ContentHandler(...); 
     parser->setContentHandler(p); 
     parser->parser(); 
    } catch (SAX ...) { 
     ... 
    } 
    } 
}; 

Estas clases eran completamente innecesario, una sola función funciona. Cada clase de ContentHandler se había generado automáticamente con las mismas 7 u 8 variables, que pude compartir con la herencia.

Así que estaba esperando que el tamaño del código baje cuando eliminé las clases de analizador y todo desde el código. Pero con solo 10 servicios, no esperaba que bajara de 38Mb a 36Mb. Esa es una cantidad escandalosa de símbolos.

Lo único que se me ocurre es que cada analizador incluía boost :: shared_ptr, algunas cosas del analizador Xerces, y que de alguna manera, el compilador y el enlazador están almacenando todos esos símbolos repetidamente para cada archivo. Tengo curiosidad por saberlo en cualquier caso.

Entonces, ¿alguien puede sugerir cómo iría a rastrear por qué una modificación simple como esta debería tener tanto impacto? Puedo usar nm en un módulo para mirar los símbolos que hay adentro, pero eso generará una gran cantidad dolorosa de material semi legible.

Además, cuando un colega ejecutó su código con mi nueva biblioteca, el tiempo del usuario pasó de 1m55 segundos a 1m25 segundos. El tiempo real es muy variable, porque estamos esperando en servidores SOAP lentos (en mi humilde opinión, SOAP es un reemplazo increíblemente pobre para CORBA ...) pero el tiempo de CPU es bastante estable. Hubiera esperado un pequeño impulso por reducir tanto el tamaño del código, pero en resumidas cuentas, en un servidor con memoria masiva, realmente me sorprendió que la velocidad se viera tan afectada, considerando que no cambié la arquitectura del Procesamiento XML en sí.

Voy a llevarlo mucho más lejos el martes, y con suerte obtendré más información, pero si alguien tiene alguna idea de cómo podría obtener esta gran mejora, me gustaría saberlo.

Actualización: Comprobé que, de hecho, tener símbolos de depuración en la tarea no parece cambiar el tiempo de ejecución en absoluto. Hice esto creando un archivo de encabezado que incluía muchas cosas, incluidas las dos que tuvieron el efecto aquí: impulsar punteros compartidos y algunos del analizador XML xerces. Parece que no hay ningún golpe de rendimiento en el tiempo de ejecución (lo verifiqué porque había diferencias de opinión entre dos respuestas). Sin embargo, también verifiqué que incluir archivos de encabezado crea símbolos de depuración para cada instancia, incluso si el tamaño del binario eliminado no se modifica. Por lo tanto, si incluye un archivo dado, incluso si ni siquiera lo usa, existe un número fijo de símbolos objetados en ese objeto que no se pliegan juntos en el tiempo del enlace, aunque presumiblemente sean idénticos.

Mi código es el siguiente:

#include "includetorture.h" 
void f1() 
{ 
    f2(); // call the function in the next file 
} 

El tamaño con mi particular, incluyen archivos estaba a punto 100k por fuente de archivo. Presumiblemente, si hubiera incluido más, sería mayor. El ejecutable total con las inclusiones era ~ 600k, sin aproximadamente 9k. Verifiqué que el crecimiento es lineal con la cantidad de archivos que lo incluyen, pero el código eliminado es del mismo tamaño independientemente, como debería ser.

Claramente, me equivoqué al pensar que esta era la razón del aumento de rendimiento. Creo que he explicado eso ahora. A pesar de que no eliminé muchos códigos, simplifiqué un gran procesamiento de cadenas de caracteres xml y reduje considerablemente el camino a través del código, y esa es, presumiblemente, la razón.

+0

gracias por la edición Magnus! – Dov

+0

En su título menciona los símbolos de depuración, pero no en el resto de su publicación. ¿Me estoy perdiendo de algo? – Bart

+0

@Bart El bloat se debe a todos los símbolos de depuración en los archivos ejecutables. Si quita las bibliotecas, el código es aproximadamente el 10% del tamaño. – Dov

Respuesta

5

Puede usar la utilidad readelf en linux, o dumpbin en windows, para encontrar la cantidad exacta de espacio utilizado por varios tipos de datos en el archivo exe. Sin embargo, no veo por qué te preocupa el tamaño del ejecutable: ¡los símbolos de depuración no utilizan ABSOLUTAMENTE memoria en el tiempo de ejecución!

+1

obviamente, solo generar ese volumen de cosas lleva tiempo en la compilación. Estoy preocupado por la tendencia. Aquí estaban, reduciendo el tamaño del código y, sin embargo, el ejecutable se hinchó un 20%. Sin embargo, es bueno saber que no se cargan en tiempo de ejecución ... – Dov

+1

Los símbolos de C++ se vuelven increíblemente largos porque el nombre cambia; esto es especialmente evidente cuando se usa boost. Un tipo aparentemente "simple", como la instanciación de alguna plantilla de impulso, ¡puede producir nombres destrozados de unos pocos kB de longitud! En tales casos, el tamaño de la información de depuración está dominado por el NÚMERO de entidades diferentes (métodos, funciones, plantillas creadas). Es un problema conocido, y algunas bibliotecas de refuerzo abordan la reducción de la longitud del nombre del símbolo en sus manuales. Pero, nuevamente, ¿por qué estás tratando de reducir el tamaño del código? – zvrba

+1

Me interesa porque noté que la racionalización de unos cientos de líneas de código que no deberían haber tenido un gran impacto en el sistema general tuvo un efecto enorme en términos del tamaño total del símbolo y del rendimiento de la aplicación. Si puedo detectar qué causa la hinchazón y cómo evitarla, la tarea se ejecutará bastante más rápido. Esto no es un problema trivial, ya que tenemos que ejecutar en producción con depuración (para que el código pueda ser depurado si hay un problema) y si puedo obtener victorias de velocidad, es en general, ventajoso tanto para las pruebas como para nuestras noches lote – Dov

2

Parece que estás usando muchas clases de C++ con métodos en línea. Si estas clases tienen una alta visibilidad, este código en línea hinchará toda la aplicación. Apuesto a que tus tiempos de enlace también han aumentado. Intente reducir la cantidad de métodos en línea y mueva el código a los archivos .cpp. Esto reducirá el tamaño de los archivos de objeto, el archivo exe y reducirá los tiempos de enlace.

La compensación en este caso es, por supuesto, el tamaño reducido de las unidades de compilación, frente al tiempo de ejecución.

+0

Si se trata de xerces e impulso, no estoy seguro de querer editar sus cosas, pero eche un vistazo. – Dov

+0

Si el código está en línea y no se llama, no debería haber ningún efecto. Sospecho que esto se debe a las variables globales y a la incorporación de todos los símbolos a los que se refieren. Pero no sé por qué el enlazador guarda múltiples copias en el ejecutable, probablemente sea demasiado caro para deshacerse de ellas. – Dov

+0

Claro, pero el código en línea producirá código en muchas unidades de ejecución (en varios archivos de objeto) que, si no se optimiza, provocará un aumento del ejecutable resultante.Recuerde que el código en línea está en línea y que la compensación (siempre hay una compensación) es un tiempo de ejecución más rápido que la sobrecarga de llamada a la función. – ralphtheninja

2

No tengo la respuesta que esperaba de su pregunta, pero permítanme compartir mi experiencia.

Es bastante común que la diferencia en el tamaño de los archivos ejecutables sea muy alta. No puedo explicar por qué en detalle, pero solo piense en todas las locuras que los depuradores modernos le permiten hacer en su código. Ya sabes, esto es gracias a los símbolos de depuración.

La diferencia de tamaño es tan grande que, si carga, por ejemplo, dinámicamente algunas bibliotecas compartidas, entonces el tiempo de carga del archivo podría explicar la diferencia en el rendimiento que encontró.

De hecho, este es un aspecto bastante "interno" de los compiladores, y solo por poner un ejemplo, hace unos años estaba bastante descontento con los enormes archivos ejecutables que GCC-4 producía en comparación con GCC-3, entonces simplemente me acostumbré (y mi HD también creció en tamaño).

En general, no me importaría, porque se supone que debes usar compilaciones con símbolos de depuración solo durante el desarrollo, donde no debería ser un problema. En la implementación, no hay ningún símbolo de depuración, y verá cuánto se reducirán los archivos.

+0

sergio, después de probar esto parece no ser cierto. El tamaño del ejecutable no tiene nada que ver con la velocidad, al menos en la pequeña prueba que hice. Y esto no fue aleatorio, puedo hacer un experimento, y es repetible, aunque desagradable. – Dov

+0

@Dov, gracias por consultar esto. En realidad, lo que quise decir es * tiempo de carga *. Si tiene varias bibliotecas compartidas, cada una de 10 MB de tamaño y llena de símbolos, y las carga dinámicamente, en el momento en que las carga, su programa se ralentizará debido a la vinculación dinámica. Si cuentas las diez ralentizaciones, podrías obtener algo notable. Pero eso fue solo una idea, ya que no sé cómo está diseñada su aplicación. De todos modos, como eliminaste los ejecutables, comprendo que el tiempo de carga tampoco afectaba tu aplicación. – sergio

+0

mi prueba fue, por supuesto, en archivos mucho más pequeños, pero estamos hablando de un gran trabajo con un tiempo de ejecución de 10 minutos, por lo que incluso si se ha cargado durante unos segundos, los 100Mb no deberían haber tenido un gran efecto como de hecho, no fue así. – Dov

Cuestiones relacionadas