2010-02-01 9 views
26

Personalmente, me gustan bastante las bibliotecas de solo encabezado, pero hay afirmaciones que provocan la saturación del código debido a la sobreinflación (así como el otro problema obvio de tiempos de compilación más largos).¿Cuándo son aceptables las bibliotecas de encabezado único?

Me preguntaba, ¿cuánto de verdad hay en estas afirmaciones (la de la hinchazón)?

Además, ¿los costos están 'justificados'? (Obviamente hay casos inevitables, como cuando se trata de una biblioteca implementada pura o principalmente con plantillas; sin embargo, estoy más interesado en el caso donde realmente hay una opción disponible.)

Sé que no hay una regla rígida, directriz , etc. en lo que se refiere a cosas como esta, pero solo trato de tener una idea de lo que otros piensan sobre el tema.

P.S. Sí, esta es una pregunta muy vaga y subjetiva, lo sé, así que la he etiquetado como tal.

Respuesta

6

Trabajo para una empresa que tiene un departamento de "middleware" propio para mantener unos pocos cientos de bibliotecas que son comúnmente utilizadas por un gran número de equipos.

A pesar de estar en la misma compañía, nos rehuimos del enfoque de encabezado único y preferimos favorecer la compatibilidad binaria sobre el rendimiento debido a la facilidad de mantenimiento.

El consenso general es que la ganancia de rendimiento (si hay alguna) no valdría la pena.

Además, el llamado "blob de código" puede tener un impacto negativo en el rendimiento ya que más código que se cargará en la caché implica más falta de memoria caché, y esos son los asesinos de rendimiento.

En un mundo ideal, supongo que el compilador y el enlazador podrían ser lo suficientemente inteligente como para no generar esas reglas "múltiples definiciones", pero siempre y cuando no es el caso, lo que favorecerá (personalmente):

  • compatibilidad binaria
  • no inlining (por métodos que son más de un par de líneas)

¿por qué no probar? Prepare las dos bibliotecas (un encabezado solo y el otro sin incluir métodos en un par de líneas) y verifique su desempeño respectivo en SU ​​CASO.

EDIT:

Se ha señalado por 'JALF' (gracias) que debería precisa lo que significa exactamente la compatibilidad binaria.

Se dice que 2 versiones de una biblioteca determinada son compatibles con binario si puede (normalmente) vincularse una u otra sin ningún cambio de su propia biblioteca.

Como solo se puede vincular con una versión de una biblioteca determinada Target, todas las bibliotecas cargadas que usan Target utilizarán efectivamente la misma versión ... y aquí está la causa de la transitividad de esta propiedad.

MyLib --> Lib1 (v1), Lib2 (v1) 
Lib1 (v1) --> Target (v1) 
Lib2 (v1) --> Target (v1) 

Ahora, decir que necesitamos una solución en Target para una característica única utilizada por Lib2, entregamos una nueva versión (v2).Si (v2) es compatible a nivel binario con (v1), entonces no podemos hacer:

Lib1 (v1) --> Target (v2) 
Lib2 (v1) --> Target (v2) 

Sin embargo, si no es el caso, entonces tendremos:

Lib1 (v2) --> Target (v2) 
Lib2 (v2) --> Target (v2) 

Sí, has leído bien, a pesar de que Lib1 hicieron no se requiere la revisión, se dirige a reconstruirlo en contra de una nueva versión de Target ya que esta versión es obligatorio para el Lib2 actualizada y Executable sólo puede enlazar con una versión de Target.

Con una biblioteca sólo de encabezado, ya que no cuenta con una biblioteca, no son efectivamente compatible a nivel binario. Por lo tanto, cada vez que realice una reparación (seguridad, error crítico, etc.) deberá entregar una nueva versión y todas las bibliotecas que dependan de usted (incluso de forma indirecta) tendrán que reconstruirse con esta nueva versión.

+3

¿Cómo Sólo encabezado libs implica código hinchazón? El compilador generalmente no se alineará si significa un código significativamente más grande. Y, para el caso, ¿cómo se ve afectada la compatibilidad binaria por libs de solo encabezado? – jalf

+0

@Matthieu: Las pruebas siempre son buenas para determinar cuál arrojaría los mejores resultados. – DrYak

+1

@Jalf: *** 1. Escenario solo de encabezado: para cada cambio en la biblioteca, una actualización de la biblioteca significa recompilar cada último proyecto que tiene dependencia de él. (Piense en la situación de Matthieu: cientos de bibliotecas, muchos equipos, muchas recompilaciones involucradas). *** 2. escenario binario único: para muchos cambios diferentes, una actualización simplemente implicará descartar un nuevo archivo .SO o .DLL, siempre que el ABI siga siendo el mismo. Aunque la nueva característica podría cambiar las firmas de métodos y estructuras de datos, las actualizaciones críticas (seguridad o estabilidad) raramente lo hacen. – DrYak

1

Sobreinflar es probablemente algo que debería abordar el llamador, ajustando sus opciones de compilación, en lugar de que el destinatario intente controlarlo a través de los instrumentos muy contundentes de la palabra clave inline y las definiciones en los encabezados. Por ejemplo, GCC tiene -finline-limit y amigos, por lo que puede usar diferentes reglas de redacción para diferentes unidades de traducción. Lo que está sobreincrustando para usted puede no ser demasiado para mí, dependiendo de la arquitectura, tamaño y velocidad de la caché de instrucciones, cómo se usa la función, etc. No es que alguna vez haya necesitado hacer esta afinación: en la práctica cuando tiene valió la pena preocuparse por eso, valió la pena reescribir, pero eso podría ser una coincidencia. De cualquier manera, si soy el usuario de una biblioteca y todo lo demás es igual, preferiría tener la opción de alinear (sujeto a mi compilador, y que podría no tomar) que no pueda alinear.

creo que el terror de código de la hinchazón de las bibliotecas de cabecera de sólo proviene más de la preocupación de que el enlazador no será capaz de eliminar las copias redundantes de código. Entonces, independientemente de si la función está realmente en línea en los sitios de llamadas o no, la preocupación es que termine con una copia invocable de la función (o clase) por archivo de objeto que la usa. No puedo recordar si las direcciones tomadas para las funciones en línea en diferentes unidades de traducción en C++ tienen que ser iguales, pero incluso suponiendo que lo hagan, de modo que haya una copia "canónica" de la función en el código vinculado, no necesariamente significa que el enlazador realmente eliminará las funciones duplicadas muertas. Si la función está definida en una sola unidad de traducción, puede estar razonablemente seguro de que solo habrá una copia independiente por biblioteca estática o ejecutable que la use.

Sinceramente, no sé qué tan bien fundamentada este miedo es. Todo en lo que he trabajado ha sido tan limitado por la memoria que hemos usado inline solo como static inline funciones tan pequeñas que no esperamos que la versión impresa sea notablemente más grande que el código para hacer una llamada, y no lo hagamos mente duplicados, o bien tan vagamente restringidos que no nos importan los duplicados en ningún lado. Nunca he llegado al punto medio de buscar y contar duplicados en varios compiladores diferentes. Ocasionalmente escuché de otros que ha habido un problema con el código de la plantilla, así que creo que hay verdad en los reclamos.

inventando a medida que avanzo ahora, creo que si usted envía una biblioteca sólo de encabezado, el usuario siempre puede meterse con él si no les gusta. Escriba un nuevo encabezado que declare todas las funciones y una nueva unidad de traducción que incluya las definiciones.Las funciones definidas en las clases tendrán que ser trasladado a definiciones externas, por lo que si quieres apoyar este uso sin que el usuario que bifurcar el código, podría evitar hacer eso y el suministro de dos cabeceras:

// declare.h 
inline int myfunc(int); 

class myclass { 
    inline int mymemberfunc(int); 
}; 

// define.h 
#include "declare.h" 
int myfunc(int a) { return a; } 

int myclass::mymemberfunc(int a) { return myfunc(a); } 

personas que llaman que están preocupados hinchazón sobre el código, probablemente, puede ser más listo que su compilador mediante la inclusión de declare.h en todos sus archivos, a continuación, escribir:

// define.cpp 
#include "define.h" 

probablemente también tienen que evitar la optimización de todo el programa para asegurarse de que el código no ser inline, pero entonces no puede estar seguro de que incluso una función que no esté en línea no estará incluida en la optimización de todo el programa.

Las personas que llaman que no están preocupadas por la función de código inflado pueden usar define.h en todos sus archivos.

6

En mi experiencia hinchazón no ha sido un problema:

  • Sólo encabezado bibliotecas dan compiladores mayor capacidad de línea, pero no obligar a los compiladores inline - muchos compiladores tratan la palabra inline como nada más que un comando para ignorar múltiples definiciones idénticas.

  • Los compiladores generalmente tienen opciones para optimizar para controlar la cantidad de alineación;/Os en los compiladores de Microsoft.

  • Por lo general, es mejor permitir que el compilador administre los problemas de velocidad vs. tamaño. Solo verá la hinchazón de las llamadas que han sido en línea, y el compilador solo las alineará si su heurística indica que la línea interna mejorará el rendimiento.

no consideraría código hincha como una razón para permanecer lejos de cabecera únicamente librerías - pero yo le pido a considerar la cantidad de un encabezado único enfoque aumentará compilar veces por.

3

Acepto, las bibliotecas en línea son mucho más fáciles de consumir.

La hinchazón en línea depende principalmente de la plataforma de desarrollo con la que esté trabajando, específicamente, de las capacidades del compilador/enlazador. No esperaría que fuera un problema importante con VC9 excepto en algunos casos de esquina.

He visto algunos cambios notables en el tamaño final en algunos lugares de un gran proyecto VC6, pero es difícil dar un "aceptable, si ..." específico. Probablemente deberás probar con tu código en tu devenv.

Un segundo problema pueden ser los tiempos de compilación, incluso cuando se utiliza el encabezado precompilado (también hay compensaciones).

En tercer lugar, algunos constructos son problemáticos, p. miembros de datos estáticos compartidos entre unidades de traducción, o evitando tener una instancia separada en cada unidad de traducción.


he visto el siguiente mecanismo para dar al usuario una opción:

// foo.h 
#ifdef MYLIB_USE_INLINE_HEADER 
#define MYLIB_INLINE inline 
#else 
#define MYLIB_INLINE 
#endif 

void Foo(); // a gazillion of declarations 

#ifdef MYLIB_USE_INLINE_HEADER 
#include "foo.cpp" 
#endif 

// foo.cpp 
#include "foo.h" 
MYLIB_INLINE void Foo() { ... } 
Cuestiones relacionadas