c++ - ¿El uso de grandes bibliotecas inherentemente hace que el código sea más lento?
performance boost (17)
Incluso si solo uso una o dos características de una biblioteca grande, al vincularme a esa biblioteca ¿incurriré en gastos generales de rendimiento en tiempo de ejecución?
En general, no.
Si la biblioteca en cuestión no tiene un montón de código independiente de la posición, entonces habrá un costo inicial mientras que el vinculador dinámico realiza las reubicaciones en la biblioteca cuando se solicita. Por lo general, eso es parte de la puesta en marcha del programa. No hay un efecto de rendimiento en tiempo de ejecución más allá de eso.
Los enlazadores también son buenos para eliminar el "código muerto" de las bibliotecas vinculadas estáticamente en el momento de la compilación, por lo que cualquier biblioteca estática que utilice tendrá una sobrecarga de tamaño mínimo. El rendimiento ni siquiera entra en él.
Francamente, te estás preocupando por las cosas equivocadas.
Tengo un tic psicológico que me hace reacio a usar grandes bibliotecas (como GLib o Boost ) en lenguajes de bajo nivel como C y C ++. En mi mente, pienso:
Bueno, esta biblioteca tiene miles de horas hombre puestas en ella, y ha sido creada por personas que saben mucho más sobre el lenguaje que yo. Sus autores y admiradores dicen que las bibliotecas son rápidas y confiables, y la funcionalidad parece realmente útil, y ciertamente me impedirá reinventar las ruedas (mal).
Pero maldita sea, nunca voy a usar todas las funciones en esa biblioteca. Es demasiado grande y probablemente se ha hinchado a lo largo de los años; es otra bola y cadena que mi programa necesita para arrastrarse.
La diatriba de Torvalds (aunque es controvertida) tampoco tranquiliza mucho mi corazón.
¿Hay alguna base para mi pensamiento, o soy simplemente irracional y / o ignorante? Incluso si solo uso una o dos características de una biblioteca grande, al vincularme a esa biblioteca ¿incurriré en gastos generales de rendimiento en tiempo de ejecución?
Estoy seguro de que también depende de la biblioteca específica, pero en general me interesa saber si las grandes bibliotecas, a nivel técnico, introducirán inherentemente ineficiencias.
Estoy cansado de obsesionarme, murmurar y preocuparme por esto, cuando no tengo los conocimientos técnicos para saber si estoy en lo cierto o no.
¡Por favor sácame de mi miseria!
Lo que tiene que ver con las preocupaciones sobre el rendimiento, en general, no es entretenerlos, porque hacerlo es adivinar que son un problema, porque si no sabes que lo son, estás adivinando, y adivinar es lo central. concepto detrás de la "optimización prematura". Lo que tiene que ver con los problemas de rendimiento es, cuando los tiene, y no antes , diagnosticarlos. Los problemas casi nunca son algo que hubieras imaginado. Aquí hay un ejemplo extendido.
Si hace una buena cantidad, reconocerá los enfoques de diseño que tienden a causar problemas de rendimiento, ya sea en su código o en una biblioteca. (Las bibliotecas ciertamente pueden tener problemas de rendimiento.) Cuando aprende eso y lo aplica a proyectos, en cierto sentido está optimizando prematuramente, pero de todos modos tiene el efecto deseado de evitar problemas. Si puedo resumir lo que probablemente aprenderá, es que demasiadas capas de abstracción y jerarquías de clase exageradas (especialmente las que están llenas de actualización de estilo de notificación) son a menudo las razones de los problemas de rendimiento.
Al mismo tiempo, comparto su circunspección acerca de las bibliotecas de terceros y demás. Demasiadas veces he trabajado en proyectos en los que un paquete de terceros fue "aprovechado" para la "sinergia", y luego el vendedor o se esfumó o abandonó el producto o se quedó obsoleto porque Microsoft cambió las cosas en el sistema operativo. Entonces, nuestro producto que se apoyó fuertemente en el paquete de terceros comenzó a no funcionar, requiriendo un gran gasto de nuestra parte mientras los programadores originales desaparecieron hace mucho tiempo.
"otra bola y cadena". De Verdad?
¿O es una plataforma estable y confiable que permite su aplicación en primer lugar?
Considere que a algunas personas les puede gustar una biblioteca "demasiado grande y ... hinchada" porque la usan para otros proyectos y realmente confían en ella.
De hecho, pueden negarse a meterse con su software específicamente porque evitó usar la obvia biblioteca "demasiado grande y ... hinchada".
Boost no es una gran biblioteca.
Es una colección de muchas pequeñas bibliotecas. La mayoría de ellos son tan pequeños que están contenidos en un encabezado o dos. Usar boost::noncopyable
no arrastra boost::regex
o boost::thread
en su código. Son bibliotecas diferentes. Simplemente se distribuyen como parte de la misma colección de la biblioteca. Pero solo pagas por los que usas.
Pero hablando en términos generales, porque existen grandes bibliotecas, incluso si Boost no es una de ellas:
¿Hay alguna base para mi pensamiento, o soy simplemente irracional y / o ignorante? Incluso si solo uso una o dos características de una biblioteca grande, al vincularme a esa biblioteca ¿incurriré en gastos generales de rendimiento en tiempo de ejecución?
Sin base, más o menos . Puedes probarlo tú mismo.
Escribir un pequeño programa en C ++ y compilarlo. Ahora agréguele una nueva función, una que nunca se llama, pero que está definida. Compila el programa nuevamente. Suponiendo que las optimizaciones están habilitadas, el enlazador las elimina porque no están en uso. Entonces, el costo de incluir código adicional no utilizado es cero.
Por supuesto, hay excepciones. Si el código instancia cualquier objeto global, es posible que no se eliminen (es por eso que incluir el encabezado iostream
aumenta el tamaño del ejecutable), pero en general, puede incluir tantos encabezados y enlaces a tantas bibliotecas como desee, y ganó '' t afecta el tamaño, el rendimiento o el uso de la memoria de su programa * siempre que no utilice ninguno de los códigos agregados.
Otra excepción es que si vincula dinámicamente a un archivo .dll o .so, debe distribuirse toda la biblioteca y, por lo tanto, no puede quitarse el código no utilizado. Pero las bibliotecas que están compiladas estáticamente en su archivo ejecutable (ya sea como bibliotecas estáticas (.lib o .a) o simplemente como archivos de encabezado incluidos generalmente pueden ser recortadas por el vinculador, eliminando los símbolos no utilizados.
Como han dicho otros, hay algunos gastos generales al agregar una biblioteca dinámica. Cuando la biblioteca se carga por primera vez, se deben realizar reubicaciones, aunque esto debería ser un costo menor si la biblioteca se compila correctamente. El costo de buscar símbolos individuales también aumenta, ya que aumenta el número de bibliotecas que se deben buscar.
El costo en memoria de agregar otra biblioteca dinámica depende en gran medida de la cantidad que realmente usa. Una página de código no se cargará desde el disco hasta que se ejecute algo en ella. Sin embargo, se cargarán otros datos, como encabezados, tablas de símbolos y tablas hash integradas en el archivo de la biblioteca, que generalmente son proporcionales al tamaño de la biblioteca.
Existe un gran documento de Ulrich Drepper, el principal contribuidor de glibc, que describe el proceso y la sobrecarga de las bibliotecas dinámicas.
Depende de cómo funciona el enlazador. Algunos vinculadores son flojos e incluirán todo el código en la biblioteca. Los vinculadores más eficientes solo extraerán el código necesario de una biblioteca. He tenido experiencia con ambos tipos.
Las bibliotecas más pequeñas tendrán menos preocupaciones con cualquier tipo de enlazador. El peor caso con una pequeña biblioteca es pequeñas cantidades de código no utilizado. Muchas bibliotecas pequeñas pueden aumentar el tiempo de compilación. El intercambio sería el tiempo de compilación contra el espacio de código.
Una prueba interesante del enlazador es el clásico programa Hello World :
#include <stdio>
#include <stdlib>
int main(void)
{
printf("Hello World/n");
return EXIT_SUCCESS;
}
La función printf
tiene muchas dependencias debido a todo el formato que pueda necesitar. Un enlazador lento pero rápido puede incluir una "biblioteca estándar" para resolver todos los símbolos. Una biblioteca más eficiente solo incluirá printf
y sus dependencias. Esto hace que el enlazador sea más lento.
El programa anterior se puede comparar con este usando puts
:
#include <stdio>
#include <stdlib>
int main(void)
{
puts("Hello World/n");
return EXIT_SUCCESS;
}
En general, la versión de puts
debe ser más pequeña que la versión de printf
, porque puts
no tiene necesidades de formateo y, por lo tanto, menos dependencias. Los enlazadores perezosos generarán el mismo tamaño de código que el programa printf
.
En resumen, las decisiones de tamaño de biblioteca tienen más dependencias en el vinculador. Específicamente, la eficiencia del enlazador. En caso de duda, muchas bibliotecas pequeñas dependerán menos de la eficacia del vinculador, pero harán que el proceso de compilación sea más complicado y más lento.
El código excedente no hace mágicamente que el procesador funcione más despacio. Todo lo que hace es sentarse allí ocupando un poco de memoria.
Si está enlazando estáticamente y su enlazador es razonable, entonces solo incluirá las funciones que realmente usa de todos modos.
El término que me gusta para los marcos, conjuntos de bibliotecas y algunos tipos de herramientas de desarrollo son las tecnologías de plataforma. Las tecnologías de plataforma tienen costos más allá del impacto en el tamaño y el rendimiento del código.
Si su proyecto está destinado a ser utilizado como una biblioteca o marco, puede terminar presionando las opciones de tecnología de plataforma en los desarrolladores que usan su biblioteca.
Si distribuye su proyecto en forma de fuente, puede terminar presionando opciones de tecnología de plataforma en sus usuarios finales.
Si no enlaza de forma estática todos los marcos y librerías elegidos, puede terminar sobrecargando a los usuarios finales con problemas de versiones de la biblioteca.
Compilar la productividad del desarrollador de efectos de tiempo. El enlace incremental, los encabezados precompilados, la administración adecuada de la dependencia del encabezado, etc., pueden ayudar a administrar los tiempos de compilación, pero no eliminan los problemas de rendimiento del compilador asociados con las enormes cantidades de código en línea que presentan algunas tecnologías de plataforma.
Para los proyectos que se distribuyen como fuente, el tiempo de compilación afecta a los usuarios finales del proyecto.
Muchas tecnologías de plataforma tienen sus propios requisitos de entorno de desarrollo. Estos requisitos pueden acumularse, lo que dificulta y consume mucho tiempo para que los nuevos desarrolladores en un proyecto puedan replicar el entorno necesario para permitir la compilación y la depuración.
El uso de algunas tecnologías de plataforma en efecto crea un nuevo lenguaje de programación para el proyecto. Esto hace que sea más difícil para los nuevos desarrolladores contribuir.
Todos los proyectos tienen dependencias de tecnología de plataforma, pero para muchos proyectos hay beneficios reales para mantener estas dependencias al mínimo.
FFTW y ATLAS son dos bibliotecas bastante grandes. Por extraño que parezca, juegan un papel importante en el software más rápido del mundo, las aplicaciones optimizadas para ejecutarse en superordenadores. No, usar grandes bibliotecas no hace que tu código sea lento, especialmente cuando la alternativa es implementar las rutinas FFT o BLAS por ti mismo.
La gran biblioteca lo hará, desde la perspectiva del rendimiento del código :
- ocupan más memoria , si tiene un binario en tiempo de ejecución (la mayoría de las partes de
boost
no requieren binarios en tiempo de ejecución, son "solo encabezado"). Mientras que el sistema operativo cargará solo las partes realmente usadas de la biblioteca a la RAM, aún puede cargar más de lo que necesita, porque la granularidad de lo que se carga es igual al tamaño de la página (4 Kb solo en mi sistema, sin embargo). tome más tiempo para cargar mediante el enlazador dinámico, si, una vez más, necesita binarios en tiempo de ejecución. Cada vez que se carga el programa, el vinculador dinámico debe coincidir con cada función que necesita que contenga la biblioteca externa con su dirección real en la memoria. Lleva algo de tiempo, pero solo un poco (sin embargo, importa a una escala de carga de muchos programas, como el inicio del entorno de escritorio, pero no tiene opción).
Y sí, hará falta un salto adicional y un par de ajustes de puntero en el tiempo de ejecución cada vez que llame a la función externa de una biblioteca compartida (vinculada dinámicamente)
desde la perspectiva de rendimiento de un desarrollador :
agregar una dependencia externa . Usted dependerá de otra persona . Incluso si el software gratuito de esa biblioteca, necesitará un gasto adicional para modificarlo. Algunos desarrolladores de programas de bajo nivel (estoy hablando de núcleos de sistema operativo) odian confiar en cualquiera: esa es su ventaja profesional. Por lo tanto, las estridencias.
Sin embargo, eso se puede considerar un beneficio. Si otras personas se acostumbran a
boost
, encontrarán conceptos y términos familiares en su programa y serán más efectivos entendiéndolo y modificándolo.Las bibliotecas más grandes generalmente contienen conceptos específicos de la biblioteca que toman tiempo para comprender. Considera Qt. Contiene señales y ranuras e infraestructura relacionada con
moc
. Comparado con el tamaño del Qt completo, aprenderlo lleva una pequeña fracción de tiempo. Pero si usa una pequeña parte de una biblioteca tan grande, eso puede ser un problema.
Más grande no implica intrínsecamente más lento. Contrariamente a algunas de las otras respuestas, tampoco existe una diferencia inherente entre bibliotecas almacenadas por completo en encabezados y bibliotecas almacenadas en archivos de objeto.
Las bibliotecas de solo encabezado pueden tener una ventaja indirecta. La mayoría de las bibliotecas basadas en plantillas tienen que ser solo de encabezado (o una gran parte del código termina en encabezados de todos modos), y las plantillas ofrecen muchas oportunidades para la optimización. Sin embargo, tomar el código en una biblioteca típica de archivos de objeto y moverlo todo a los encabezados no tendrá muchos buenos efectos (y podría llevar a la saturación del código).
La respuesta real para una biblioteca en particular generalmente dependerá de su estructura general. Es fácil pensar en "Boost" como algo enorme. De hecho, es una gran colección de bibliotecas, la mayoría de las cuales son individualmente bastante pequeñas. No se puede decir mucho (significativamente) sobre Boost como un todo, porque las bibliotecas individuales están escritas por diferentes personas, con diferentes técnicas, objetivos, etc. Algunos de ellos (por ejemplo, Formato, Asignar) son realmente más lentos que cualquier cosa es muy probable que lo haga por su cuenta. Otros (por ejemplo, Pool) ofrecen cosas que podría hacer usted mismo, pero probablemente no, para obtener al menos pequeñas mejoras de velocidad. Algunos (p. Ej., UBlas) usan magia de plantillas de alta resistencia para correr más rápido que cualquiera, pero un pequeño porcentaje de nosotros puede aspirar a lograrlo solo.
Hay, por supuesto, bastantes bibliotecas que realmente son grandes bibliotecas individualmente. En bastantes casos, estos realmente son más lentos que lo que usted mismo escribiría. En particular, muchos (¿la mayoría?) De ellos intentan ser mucho más generales que casi cualquier cosa que probablemente escribirías por tu cuenta. Si bien eso no necesariamente lleva a un código más lento, definitivamente hay una fuerte tendencia en esa dirección. Al igual que con muchos otros códigos, cuando desarrolla bibliotecas comercialmente, los clientes tienden a estar mucho más interesados en las funciones que en el tamaño de la velocidad.
Algunas bibliotecas también dedican mucho espacio, código (y, a menudo, al menos un poco de tiempo) para resolver problemas que muy bien podrían no importarles en absoluto. Solo por ejemplo, hace años utilicé una biblioteca de procesamiento de imágenes. Su soporte para más de 200 formatos de imagen sonaba realmente impresionante (y en cierto modo lo era) pero estoy bastante seguro de que nunca lo usé para tratar con más de una docena de formatos (y probablemente podría haberlo conseguido al respaldar solo la mitad muchos). OTOH, incluso con todo eso, todavía era bastante rápido. Apoyar un menor número de mercados podría haber restringido su mercado hasta el punto de que el código habría sido más lento (solo por ejemplo, manejó archivos JPEG más rápido que IJG).
No puedo comentar sobre GLib, pero tenga en cuenta que gran parte del código en Boost es solo encabezado y dado el principio C ++ del usuario que solo paga por lo que está usando, las bibliotecas son bastante eficientes. Hay varias bibliotecas que requieren que establezcas un vínculo en contra de ellas (me vienen a la mente regex, sistema de archivos, etc.) pero son bibliotecas separadas. Con Boost no se vincula con una biblioteca monolítica grande, sino solo con los componentes más pequeños que se usan.
Por supuesto, la otra pregunta es: ¿cuál es la alternativa? ¿Desea implementar la funcionalidad que está en Boost usted mismo cuando lo necesita? Dado que muchas personas muy competentes han trabajado en este código y se han asegurado de que funcione a través de una multitud de compiladores y aún así sea eficiente, esta podría no ser una tarea sencilla. Además, estás reinventando la rueda, al menos hasta cierto punto. En mi humilde opinión puede pasar este tiempo de manera más productiva.
Pregúntate cuál es tu objetivo. ¿Es una estación de trabajo de mitad de tiempo de hoy? No hay problema. Es hardware antiguo o incluso un sistema integrado limitado, entonces podría ser.
Como han dicho los carteles anteriores, el solo hecho de tener el código allí no le cuesta mucho en rendimiento (puede reducir la localidad de las memorias caché y aumentar los tiempos de carga).
Puede haber una pequeña sobrecarga al cargar estas bibliotecas si están vinculadas dinámicamente. Normalmente, esta será una pequeña fracción del tiempo que su programa pasa corriendo.
Sin embargo, no habrá gastos generales una vez que todo esté cargado.
Si no quieres usar todo el impulso, entonces no lo hagas. Es modular, por lo que puedes usar las partes que quieras e ignorar el resto.
Técnicamente, la respuesta es que sí, lo hacen. Sin embargo, estas ineficiencias rara vez son prácticamente importantes. Voy a asumir un lenguaje estáticamente compilado como C, C ++ o D aquí.
Cuando un archivo ejecutable se carga en la memoria en un sistema operativo moderno, el espacio de direcciones simplemente se asigna a él. Esto significa que, sin importar cuán grande sea el objeto ejecutable, si hay bloques enteros de código de tamaño de página que no se usan, nunca tocarán la memoria física. Sin embargo, perderá espacio de direcciones, y ocasionalmente esto puede importar un poco en sistemas de 32 bits.
Cuando se vincula a una biblioteca, un buen enlazador arrojará el exceso de material que no use, aunque especialmente en el caso de las instancias de plantillas esto no siempre sucede. Por lo tanto, sus binarios pueden ser un poco más grandes de lo estrictamente necesario.
Si tiene un código que no usa fuertemente entrelazado con el código que utiliza, puede terminar desperdiciando espacio en su caché de CPU. Sin embargo, como las líneas de caché son pequeñas (generalmente 64 bytes), esto rara vez ocurrirá en un grado prácticamente importante.
Tienes razón en estar preocupado, especialmente cuando se trata de potenciar. No se debe tanto a que alguien los haya escrito como incompetentes sino por dos problemas.
- Las plantillas son solo código inherentemente hinchado. Esto no importó hace 10 años, pero hoy en día la CPU es mucho más rápida que el acceso a la memoria y esta tendencia continúa. Casi diría que las plantillas son una característica obsoleta.
No es tan malo para el código de usuario, que suele ser algo práctico, pero en muchas bibliotecas todo se define en términos de otras plantillas o plantillas en varios elementos (es decir, explosiones de código de plantilla exponencial).
Simplemente agregando iostream agrega alrededor de 3 mb (!!!) a su código. Ahora agregue algo de tonterías de impulso y tenga 30 mb de código si declara claramente un par de estructuras de datos particularmente extrañas.
Peor aún, ni siquiera puedes describir fácilmente esto. Puedo decirte que la diferencia entre el código escrito por mí y el código de las bibliotecas de plantillas es DRAMÁTICO, pero para un enfoque más ingenuo puedes pensar que estás empeorando por una simple prueba, pero el costo en el código hinchado llevará su herramienta a un gran mundo real. aplicación
- Complejidad. Cuando miras las cosas en Boost, son todas cosas que complican tu código en gran medida. Cosas como punteros inteligentes, funtores, todo tipo de cosas complicadas. Ahora, no diré que nunca es una buena idea usar estas cosas, pero casi todo tiene un gran costo de algún tipo. Especialmente si no entiendes exactamente, quiero decir exactamente, qué está haciendo.
Pero la gente se jacta de ello y finge que tiene algo que ver con el "diseño" para que la gente tenga la impresión de que es la forma en que debería hacer todo, no solo algunas herramientas extremadamente especializadas que deberían usarse con poca frecuencia. Si alguna vez.
fwiw, trabajo en Microsoft Windows y cuando construimos Windows; compilación compilada para SIZE son más rápidas que compilaciones compiladas para SPEED porque toma menos aciertos de página.