Enlace estático vs enlace dinámico


400

¿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.


59
"Con el enlace estático, el compilador puede optimizar ... el código de la biblioteca", ¡pero solo si también lo compila! Si solo se vincula a archivos de objetos precompilados, su compilador no tiene la oportunidad de optimizarlos.

3
Si eso es cierto, entonces tiene razón, pero hay algunas dudas sobre qué tan cierto es con los compiladores modernos, si alguien puede verificar esto de una forma u otra, sería genial.
Eloff

55
Con un compilador que compila código nativo (como la mayoría de los compiladores C / C ++) no hay más posibilidades de optimización de código. Si el código se compila en algún lenguaje intermedio (como .Net IL), el compilador JIT se invoca cuando la biblioteca se carga para compilarlo en código nativo. Esa compilación final puede mejorar cada vez más con el tiempo a medida que evoluciona el compilador JIT.
Tarydon

3
@Eloff: VS2008 hace exactamente eso con LTCG habilitado. (Sin embargo, los archivos lib se vuelven enormes ...) He jugado con ellos y para alguien interesado en "qué puede hacer mi compilador por mí", es sorprendente.
peterchen

Respuestas:


349
  • La vinculación dinámica puede reducir el consumo total de recursos (si más de un proceso comparte la misma biblioteca (incluida la versión en "la misma", por supuesto)). Creo que este es el argumento que impulsa su presencia en la mayoría de los entornos. Aquí "recursos" incluye espacio en disco, RAM y espacio en caché. Por supuesto, si su enlazador dinámico es insuficientemente flexible, existe el riesgo de infierno de DLL .
  • La vinculación dinámica significa que las correcciones de errores y las actualizaciones a las bibliotecas se propagan para mejorar su producto sin requerir que envíe nada.
  • Los complementos siempre requieren enlaces dinámicos .
  • La vinculación estática significa que puede saber que el código se ejecutará en entornos muy limitados (al principio del proceso de arranque o en modo de rescate).
  • La vinculación estática puede hacer que los archivos binarios sean más fáciles de distribuir a diversos entornos de usuario (a costa de enviar un programa más grande y con más recursos).
  • La vinculación estática puede permitir tiempos de inicio ligeramente más rápidos, pero esto depende en cierto grado tanto del tamaño y la complejidad de su programa como de los detalles de la estrategia de carga del sistema operativo.

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).


24
El consumo de recursos es básicamente el espacio de código, que a medida que pasa el tiempo es cada vez menos preocupante. Si se comparten 500K de biblioteca entre 5 procesos, eso es un ahorro de 2MB, que es menos del .1% de 3GB de RAM.
Mark Ransom

3
Si la biblioteca también comparte el mismo mapeo virtual (la misma dirección física y virtual en todos los procesos), ¿un enlace dinámico tampoco guarda ranuras TLB en la MMU del procesador?
Zan Lynx

66
Además, un enlace dinámico facilita la actualización del código de la biblioteca con errores con mejores versiones.
Zan Lynx

89
@Zan También facilita agregar código con errores a una versión que funcione.

66
"Los complementos siempre requieren enlaces dinámicos". Eso es incorrecto Algunos modelos de complementos como AudioUnits de Apple pueden ejecutar el complemento en un proceso separado y usar IPC. Esta es una alternativa más segura al enlace dinámico para complementos (el complemento no puede bloquear el host). Sugiera que la respuesta se actualice a "Los complementos pueden requerir enlaces dinámicos" o similar.
Taylor

68

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 gccconmutador -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é -fPICa gcc.


44
Me gusta esta respuesta, porque fue la única en abordar los puntos que planteé en la pregunta.
Eloff

Sería interesante tener referencias sobre esos tecnicismos DLL, y una comparación entre diferentes sistemas operativos.
UncleZeiv

Parece estar bien, pero la velocidad de la CPU definitivamente no ha alcanzado sus límites.
Aidiakapi

67

La vinculación dinámica es la única forma práctica de cumplir con algunos requisitos de licencia, como la LGPL .


17
Siempre y cuando el usuario final pueda volver a vincular el código LGPL'd (por ejemplo, porque proporciona su código fuente o archivos de objetos compilados con su software), entonces el enlace estático está bien . Además, si su software es para uso interno (es decir, para ser utilizado solo dentro de su organización y no distribuido), puede vincular estáticamente. Esto se aplicaría, por ejemplo, al software del servidor, donde el servidor no está distribuido.
JBentley

3
No lo entiendo ¿Podría darme más fuente (o elaborar más) para apreciar lo que escribió?
Baskaya

44
@ Thorn vea la sección de licencia LGPL 4.d + e . Necesita distribuir en un formulario que requiera que el usuario haga un enlace, o distribuir una biblioteca compartida (dinámica).
Mark Ransom

46

Estoy de acuerdo con los puntos que dnmckee menciona, más:

  • Las aplicaciones vinculadas estáticamente pueden ser más fáciles de implementar, ya que hay menos dependencias de archivos adicionales (.dll / .so) que pueden causar problemas cuando faltan o se instalan en el lugar incorrecto.

66
Vale la pena señalar que el compilador Go de Google solo compilará binarios de forma estática principalmente por este motivo.
Hut8

34

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.


1
muy buen punto, he estado tratando de hacer esto recientemente con un poco de código que tengo en el trabajo pero la compilación de todo lo estáticamente demostrado ser sorprendentemente molesto y me di por vencido
UncleZeiv

21

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.


1
Algunos sistemas operativos para procesadores que no son PIC o PIC costosos prepararán bibliotecas dinámicas para cargarse en una dirección particular en la memoria, y si pueden hacerlo, simplemente asignan una copia de la biblioteca a cada proceso que se vincula con ella. Eso reduce mucho los gastos generales de PIC. Al menos OS X y algunas distribuciones de Linux hacen esto, no estoy seguro acerca de Windows.
Andrew McGregor

Gracias Andrew, no sabía que algunas distribuciones de Linux usaban esto. ¿Tiene alguna referencia que pueda seguir o una palabra clave que pueda buscar para obtener más información? (FWIW había escuchado que Windows estaba haciendo una variante de esto, pero Windows está demasiado lejos de mi zona de competencia para que lo mencione).
Programador del

Creo que la palabra clave que está buscando es "preenlace": prepara una biblioteca para cargarse rápidamente en una determinada dirección, para acelerar el inicio del programa.
Blaisorblade

20

Este debate en gran detalle sobre las bibliotecas compartidas en Linux y las implicaciones de rendimiento.


3
+1 por vincular al tutorial DSO de Drepper, que todos los que crean bibliotecas en Linux deberían leer.
Janneb

10

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.


1
El punto LD_LIBRARY_PATHno 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/../libespecificará 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).
FooF

> para procesos con privilegios de root. Creo que está hablando de programas setuid que se ejecutan desde usuarios no root; de lo contrario, eso no tiene sentido. Y un binario setuid con bibliotecas en ubicaciones no estándar es extraño, pero dado que solo root puede instalar esos programas, también puede editar /etc/ld.so.confpara ese caso.
Blaisorblade

10

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.


1
En realidad, no he comparado la diferencia en las velocidades de compilación, pero tendría un enlace dinámico si fuera significativamente más rápido. Boost hace suficientes cosas malas en mis tiempos de compilación tal como están.
Eloff

9

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.


8

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.


1
Es importante tener en cuenta que hay una variedad de contramedidas para evitar DLL Hell.
ocodo

5

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á.


2

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).


2

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 crunchgenbinario 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 initcualquier 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.socosto 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 mi crunchgenbinario 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.


2

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.


0

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:

  • el tiempo de compilación es más largo
  • salida binaria es más grande

Dynamic linkinges un proceso en tiempo de ejecución cuando se carga un contenido vinculado. Esta técnica permite:

  • actualizar el binario vinculado sin recompilar uno primario que aumente la ABIestabilidad [Acerca de]
  • tener una sola copia compartida

Contras:

  • la hora de inicio es más lenta (el contenido vinculado debe copiarse)
  • los errores del enlazador se lanzan en tiempo de ejecución
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.