¿Existen razones de rendimiento convincentes para elegir el enlace estático en lugar del enlace dinámico o viceversa en ciertas situaciones? He escuchado o leído lo siguiente, pero no sé lo suficiente sobre el tema para dar fe de su veracidad.
1) La diferencia en el rendimiento del tiempo de ejecución entre el enlace estático y el enlace dinámico suele ser insignificante.
2) (1) no es cierto si se utiliza un compilador de creación de perfiles que utiliza datos de perfil para optimizar las rutas de acceso del programa porque con el enlace estático, el compilador puede optimizar tanto su código como el código de la biblioteca. Con la vinculación dinámica solo se puede optimizar su código. Si pasa la mayor parte del tiempo ejecutando código de biblioteca, esto puede hacer una gran diferencia. De lo contrario, (1) aún se aplica.
Respuestas:
Algunas ediciones para incluir sugerencias muy relevantes en los comentarios y en otras respuestas. Me gustaría señalar que la forma en que rompes esto depende en gran medida del entorno en el que planeas ejecutar. Es posible que los sistemas integrados mínimos no tengan suficientes recursos para admitir la vinculación dinámica. Los sistemas pequeños un poco más grandes pueden admitir la vinculación dinámica, porque su memoria es lo suficientemente pequeña como para que los ahorros de RAM de la vinculación dinámica sean muy atractivos. Las PC de consumo completas tienen, como señala Mark, enormes recursos, y probablemente pueda dejar que los problemas de conveniencia lo lleven a pensar sobre este asunto.
Para abordar los problemas de rendimiento y eficiencia: depende .
Clásicamente, las bibliotecas dinámicas requieren algún tipo de capa de pegamento que a menudo significa un doble despacho o una capa adicional de indirección en el direccionamiento de funciones y puede costar un poco de velocidad (¿pero el tiempo de llamada a la función en realidad es una gran parte de su tiempo de ejecución?).
Sin embargo, si está ejecutando múltiples procesos que todos llaman mucho a la misma biblioteca, puede terminar guardando líneas de caché (y, por lo tanto, ganando en el rendimiento de la ejecución) al usar el enlace dinámico en relación con el uso del enlace estático. (A menos que los SO modernos sean lo suficientemente inteligentes como para notar segmentos idénticos en binarios enlazados estáticamente. Parece difícil, ¿alguien lo sabe?)
Otro problema: tiempo de carga. Usted paga los costos de carga en algún momento. Cuando paga este costo depende de cómo funciona el sistema operativo, así como de qué enlace utiliza. Tal vez prefiera posponer el pago hasta que sepa que lo necesita.
Tenga en cuenta que el enlace estático vs dinámico no es tradicionalmente un problema de optimización, porque ambos implican una compilación separada en archivos de objetos. Sin embargo, esto no es obligatorio: un compilador puede, en principio, "compilar" "bibliotecas estáticas" en un formulario AST digerido inicialmente y "vincularlas" agregando esos AST a los generados para el código principal, lo que permite la optimización global. Ninguno de los sistemas que uso hace esto, así que no puedo comentar qué tan bien funciona.
La forma de responder preguntas de rendimiento es siempre mediante pruebas (y usar un entorno de prueba lo más parecido posible al entorno de implementación).
fuente
1) se basa en el hecho de que llamar a una función DLL siempre está utilizando un salto indirecto adicional. Hoy, esto suele ser insignificante. Dentro de la DLL hay algo más de sobrecarga en las CPU i386, porque no pueden generar código independiente de la posición. En amd64, los saltos pueden ser relativos al contador del programa, por lo que esta es una gran mejora.
2) Esto es correcto. Con las optimizaciones guiadas por la creación de perfiles, generalmente puede obtener un rendimiento de aproximadamente 10-15 por ciento. Ahora que la velocidad de la CPU ha alcanzado sus límites, puede valer la pena hacerlo.
Yo agregaría: (3) el enlazador puede organizar las funciones en una agrupación más eficiente de caché, de modo que se minimizan las costosas pérdidas de nivel de caché. También podría afectar especialmente el tiempo de inicio de las aplicaciones (según los resultados que he visto con el compilador Sun C ++)
Y no olvide que con las DLL no se puede eliminar el código muerto. Dependiendo del idioma, el código DLL podría no ser óptimo tampoco. Las funciones virtuales siempre son virtuales porque el compilador no sabe si un cliente lo sobrescribe.
Por estas razones, en caso de que no haya una necesidad real de archivos DLL, simplemente use la compilación estática.
EDITAR (para responder el comentario, por usuario subrayado)
Aquí hay un buen recurso sobre el problema de código de posición independiente http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/
Como se explicó, x86 no los tiene AFAIK para nada más que rangos de salto de 15 bits y no para saltos y llamadas incondicionales. Es por eso que las funciones (de los generadores) que tienen más de 32K siempre han sido un problema y necesitaban trampolines integrados.
Pero en sistemas operativos x86 populares como Linux, no necesita preocuparse si el archivo .so / DLL no se genera con el
gcc
conmutador-fpic
(que impone el uso de las tablas de salto indirectas). Porque si no lo hace, el código solo se arregla como un enlazador normal lo reubicaría. Pero al hacerlo, hace que el segmento de código no se pueda compartir y necesitaría una asignación completa del código del disco a la memoria y tocarlo todo antes de que pueda usarse (vaciar la mayoría de las cachés, presionar TLB), etc. Hubo un tiempo cuando esto se consideró lento.Entonces ya no tendría ningún beneficio.
No recuerdo qué sistema operativo (Solaris o FreeBSD) me dio problemas con mi sistema de construcción de Unix porque simplemente no estaba haciendo esto y se preguntó por qué se estrelló hasta que me presenté
-fPIC
agcc
.fuente
La vinculación dinámica es la única forma práctica de cumplir con algunos requisitos de licencia, como la LGPL .
fuente
Estoy de acuerdo con los puntos que dnmckee menciona, más:
fuente
Una razón para hacer una compilación estáticamente vinculada es verificar que tenga un cierre completo para el ejecutable, es decir, que todas las referencias de símbolos se resuelvan correctamente.
Como parte de un gran sistema que se estaba creando y probando utilizando una integración continua, las pruebas de regresión nocturna se ejecutaron utilizando una versión estática de los ejecutables. Ocasionalmente, veríamos que un símbolo no se resolvería y el enlace estático fallaría aunque el ejecutable vinculado dinámicamente se vincularía con éxito.
Esto generalmente ocurría cuando los símbolos que estaban asentados en las bibliotecas compartidas tenían un nombre mal escrito y, por lo tanto, no se vinculaban estáticamente. El vinculador dinámico no resuelve completamente todos los símbolos, independientemente del uso de la evaluación de profundidad o amplitud, por lo que puede terminar con un ejecutable vinculado dinámicamente que no tiene cierre completo.
fuente
1 / He estado en proyectos donde el enlace dinámico vs enlace estático fue comparado y la diferencia no se determinó lo suficientemente pequeña como para cambiar al enlace dinámico (no fui parte de la prueba, solo sé la conclusión)
2 / El enlace dinámico a menudo se asocia con PIC (código independiente de posición, código que no necesita modificarse según la dirección en la que se carga). Dependiendo de la arquitectura, el PIC puede provocar otra desaceleración, pero es necesario para obtener el beneficio de compartir una biblioteca vinculada dinámicamente entre dos ejecutables (e incluso dos procesos del mismo ejecutable si el sistema operativo utiliza la asignación al azar de la dirección de carga como medida de seguridad). No estoy seguro de que todos los sistemas operativos permitan separar los dos conceptos, pero Solaris y Linux sí lo hacen e ISTR que HP-UX también.
3 / He estado en otros proyectos que utilizan enlaces dinámicos para la función "parche fácil". Pero este "parche fácil" hace que la distribución de pequeños arreglos sea un poco más fácil y complicada una pesadilla de versiones. A menudo terminamos teniendo que empujar todo más tener que rastrear los problemas en el sitio del cliente porque la versión incorrecta era un token.
Mi conclusión es que usé enlaces estáticos excepto:
para cosas como complementos que dependen de enlaces dinámicos
cuando compartir es importante (grandes bibliotecas utilizadas por múltiples procesos al mismo tiempo, como tiempo de ejecución C / C ++, bibliotecas GUI, ... que a menudo se administran de forma independiente y para las cuales el ABI está estrictamente definido)
Si uno quiere usar el "parche fácil", diría que las bibliotecas deben administrarse como las grandes bibliotecas anteriores: deben ser casi independientes con un ABI definido que no debe ser modificado por arreglos.
fuente
Este debate en gran detalle sobre las bibliotecas compartidas en Linux y las implicaciones de rendimiento.
fuente
En sistemas similares a Unix, la vinculación dinámica puede dificultar la vida de 'root' al usar una aplicación con las bibliotecas compartidas instaladas en lugares apartados. Esto se debe a que el vinculador dinámico generalmente no prestará atención a LD_LIBRARY_PATH o su equivalente para procesos con privilegios de root. A veces, entonces, el enlace estático salva el día.
Alternativamente, el proceso de instalación tiene que localizar las bibliotecas, pero eso puede dificultar la coexistencia de múltiples versiones del software en la máquina.
fuente
LD_LIBRARY_PATH
no es exactamente un obstáculo para usar bibliotecas compartidas, al menos no en GNU / Linux. Por ejemplo, si coloca las bibliotecas compartidas en el directorio../lib/
relativo al archivo del programa, entonces, con la cadena de herramientas GNU, la opción del vinculador-rpath $ORIGIN/../lib
especificará la búsqueda en la biblioteca desde esa ubicación relativa. Luego puede reubicar fácilmente la aplicación junto con todas las bibliotecas compartidas asociadas. Usando este truco, no hay problema para tener versiones múltiples de la aplicación y las bibliotecas (suponiendo que estén relacionadas, si no, podría usar enlaces simbólicos)./etc/ld.so.conf
para ese caso.Es bastante simple, de verdad. Cuando realiza un cambio en su código fuente, ¿desea esperar 10 minutos para que se genere o 20 segundos? Veinte segundos es todo lo que puedo soportar. Más allá de eso, saco la espada o empiezo a pensar en cómo puedo usar una compilación y un enlace por separado para devolverlo a la zona de confort.
fuente
El mejor ejemplo para la vinculación dinámica es cuando la biblioteca depende del hardware utilizado. En la antigüedad, se decidió que la biblioteca matemática C era dinámica, de modo que cada plataforma puede utilizar todas las capacidades del procesador para optimizarla.
Un ejemplo aún mejor podría ser OpenGL. OpenGl es una API que AMD y NVidia implementan de manera diferente. Y no puede utilizar una implementación de NVidia en una tarjeta AMD, porque el hardware es diferente. No puede vincular estáticamente OpenGL a su programa, por eso. El enlace dinámico se usa aquí para permitir que la API se optimice para todas las plataformas.
fuente
La vinculación dinámica requiere tiempo adicional para que el sistema operativo encuentre la biblioteca dinámica y la cargue. Con la vinculación estática, todo está unido y es una carga de un solo disparo en la memoria.
Además, vea DLL Hell . Este es el escenario donde la DLL que carga el sistema operativo no es la que vino con su aplicación, o la versión que su aplicación espera.
fuente
Otro tema que aún no se discute es la corrección de errores en la biblioteca.
Con el enlace estático, no solo tiene que reconstruir la biblioteca, sino que deberá volver a vincular y redistribuir el ejecutable. Si la biblioteca solo se usa en un ejecutable, esto puede no ser un problema. Pero cuantos más ejecutables necesiten ser reenlazados y redistribuidos, mayor será el dolor.
Con la vinculación dinámica, simplemente reconstruye y redistribuye la biblioteca dinámica y ya está.
fuente
la vinculación estática le brinda un solo exe, para realizar un cambio que necesita para recompilar todo su programa. Mientras que en el enlace dinámico solo debe realizar cambios en el dll y cuando ejecuta su exe, los cambios se recogerían en tiempo de ejecución. Es más fácil proporcionar actualizaciones y correcciones de errores mediante el enlace dinámico (por ejemplo: windows).
fuente
Hay un número enorme y creciente de sistemas donde un nivel extremo de enlace estático puede tener un enorme impacto positivo en las aplicaciones y el rendimiento del sistema.
Me refiero a lo que a menudo se llaman "sistemas integrados", muchos de los cuales ahora utilizan cada vez más sistemas operativos de propósito general, y estos sistemas se utilizan para todo lo imaginable.
Un ejemplo extremadamente común son los dispositivos que usan sistemas GNU / Linux que usan Busybox . Llevé esto al extremo con NetBSD al construir una imagen de sistema de arranque i386 (32 bits) que incluye tanto un kernel como su sistema de archivos raíz, este último que contiene un único
crunchgen
binario enlazado estático (por ) con enlaces duros a todos los programas que en sí contienen todos (bueno, hasta el último recuento 274) de los programas estándar del sistema con todas las funciones (la mayoría excepto la cadena de herramientas), y tiene un tamaño de menos de 20 megabytes (y probablemente se ejecuta muy cómodamente en un sistema con solo 64 MB de memoria (incluso con el sistema de archivos raíz sin comprimir y completamente en RAM), aunque no he podido encontrar uno tan pequeño para probarlo).Se ha mencionado en publicaciones anteriores que el tiempo de inicio de un binario enlazado estático es más rápido (y puede ser mucho más rápido), pero eso es solo una parte de la imagen, especialmente cuando todo el código objeto está vinculado al mismo archivo, y aún más especialmente cuando el sistema operativo admite la paginación de demanda de código directo desde el archivo ejecutable. En este escenario ideal, el tiempo de inicio de los programas es literalmente insignificante ya que casi todas las páginas de código ya estarán en la memoria y estarán en uso por el shell (y
init
cualquier otro proceso en segundo plano que pueda estar ejecutándose), incluso si el programa solicitado no lo ha hecho. alguna vez se ejecutó desde el inicio, ya que quizás solo se deba cargar una página de memoria para cumplir con los requisitos de tiempo de ejecución del programa.Sin embargo, esa no es toda la historia. También suelo compilar y utilizar las instalaciones del sistema operativo NetBSD para mis sistemas de desarrollo completos mediante la vinculación estática de todos los archivos binarios. A pesar de que esto requiere una gran cantidad de espacio en disco (~ 6.6GB en total para x86_64 con todo, incluida la cadena de herramientas y X11 con enlace estático) (especialmente si uno mantiene las tablas de símbolos de depuración completas disponibles para todos los programas otros ~ 2.5GB), el resultado aún se ejecuta más rápido en general, y para algunas tareas incluso usa menos memoria que un sistema de enlace dinámico típico que pretende compartir páginas de códigos de la biblioteca. El disco es barato (incluso el disco rápido), y la memoria para almacenar en caché los archivos de disco de uso frecuente también es relativamente barato, pero los ciclos de CPU realmente no lo son, y pagar el
ld.so
costo de inicio de cada proceso que comienza cadael tiempo que comienza tomará horas y horas de ciclos de CPU lejos de las tareas que requieren iniciar muchos procesos, especialmente cuando los mismos programas se usan una y otra vez, como los compiladores en un sistema de desarrollo. Los programas de cadena de herramientas con enlaces estáticos pueden reducir en horas los tiempos de compilación de arquitectura múltiple de todo el sistema operativo para mis sistemas . Todavía tengo que construir la cadena de herramientas en micrunchgen
binario single 'ed, pero sospecho que cuando lo haga habrá más horas de tiempo de construcción ahorradas debido a la ganancia para el caché de la CPU.fuente
La vinculación estática incluye los archivos que el programa necesita en un solo archivo ejecutable.
La vinculación dinámica es lo que consideraría lo habitual, hace que un ejecutable que todavía requiera DLL y esté en el mismo directorio (o los DLL podrían estar en la carpeta del sistema).
(DLL = biblioteca de enlaces dinámicos )
Los ejecutables vinculados dinámicamente se compilan más rápido y no requieren tantos recursos.
fuente
Static linking
es un proceso en tiempo de compilación cuando un contenido vinculado se copia en el binario primario y se convierte en un solo binario.Contras:
Dynamic linking
es un proceso en tiempo de ejecución cuando se carga un contenido vinculado. Esta técnica permite:ABI
estabilidad [Acerca de]Contras:
fuente