Liaison statique vs liaison dynamique

400

Existe-t-il des raisons de performances convaincantes de choisir la liaison statique plutôt que la liaison dynamique ou vice versa dans certaines situations? J'ai entendu ou lu ce qui suit, mais je n'en sais pas assez sur le sujet pour attester de sa véracité.

1) La différence de performances d'exécution entre la liaison statique et la liaison dynamique est généralement négligeable.

2) (1) n'est pas vrai si vous utilisez un compilateur de profilage qui utilise les données de profil pour optimiser les raccourcis du programme car avec la liaison statique, le compilateur peut optimiser à la fois votre code et le code de la bibliothèque. Avec la liaison dynamique, seul votre code peut être optimisé. Si la plupart du temps est consacré à l'exécution du code de bibliothèque, cela peut faire une grande différence. Sinon, (1) s'applique toujours.

Eloff
la source
59
"Avec la liaison statique, le compilateur peut optimiser .. le code de la bibliothèque" mais seulement s'il le compile aussi! Si vous créez simplement un lien vers des fichiers objets précompilés, votre compilateur n'a pas la possibilité de les optimiser.
3
Si c'est vrai, alors vous avez raison, mais il y a une question quant à la vérité avec les compilateurs modernes, si quelqu'un peut vérifier ceci d'une manière ou d'une autre, ce serait formidable.
Eloff
5
Avec un compilateur compilant en code natif (comme la plupart des compilateurs C / C ++), il n'y a plus de chance d'optimisation de code. Si le code est compilé dans un langage intermédiaire (comme .Net IL), le compilateur JIT est appelé lorsque la bibliothèque est chargée pour le compiler en code natif. Cette compilation finale peut devenir de mieux en mieux avec le temps à mesure que le compilateur JIT évolue.
Tarydon
3
@Eloff: VS2008 fait exactement cela avec LTCG activé. (Les fichiers lib deviennent énormes, cependant ..) Je l'ai essayé et pour quelqu'un intéressé par "ce que mon compilateur peut faire pour moi", ce n'est rien d'étonnant.
peterchen

Réponses:

349
  • La liaison dynamique peut réduire la consommation totale de ressources (si plusieurs processus partagent la même bibliothèque (y compris la version dans "la même", bien sûr)). Je crois que c'est l'argument qui motive sa présence dans la plupart des environnements. Ici, les «ressources» incluent l'espace disque, la RAM et l'espace cache. Bien sûr, si votre éditeur de liens dynamique n'est pas suffisamment flexible, il existe un risque d' enfer de DLL .
  • La liaison dynamique signifie que les corrections de bogues et les mises à niveau des bibliothèques se propagent pour améliorer votre produit sans vous obliger à expédier quoi que ce soit.
  • Les plugins demandent toujours une liaison dynamique .
  • La liaison statique signifie que vous pouvez savoir que le code s'exécutera dans des environnements très limités (au début du processus de démarrage ou en mode de secours).
  • La liaison statique peut faciliter la distribution des fichiers binaires dans divers environnements utilisateur (au prix de l'envoi d'un programme plus grand et plus gourmand en ressources).
  • La liaison statique peut permettre des temps de démarrage légèrement plus rapides , mais cela dépend dans une certaine mesure à la fois de la taille et de la complexité de votre programme et des détails de la stratégie de chargement du système d'exploitation.

Quelques modifications pour inclure les suggestions très pertinentes dans les commentaires et dans d'autres réponses. Je voudrais noter que la façon dont vous vous en sortez dépend beaucoup de l'environnement dans lequel vous prévoyez d'exécuter. Les systèmes embarqués minimaux peuvent ne pas avoir suffisamment de ressources pour prendre en charge la liaison dynamique. Les petits systèmes légèrement plus grands peuvent bien prendre en charge la liaison dynamique, car leur mémoire est suffisamment petite pour rendre les économies de RAM de la liaison dynamique très attrayantes. Comme le note Mark, les PC grand public disposent d'énormes ressources et vous pouvez probablement laisser les problèmes de commodité guider votre réflexion à ce sujet.


Pour résoudre les problèmes de performance et d'efficacité: cela dépend .

Classiquement, les bibliothèques dynamiques nécessitent une sorte de couche de colle, ce qui signifie souvent une double répartition ou une couche supplémentaire d'indirection dans l'adressage des fonctions et peut coûter un peu de vitesse (mais le temps d'appel de fonction est-il en fait une grande partie de votre temps de fonctionnement ???).

Cependant, si vous exécutez plusieurs processus qui appellent tous beaucoup la même bibliothèque, vous pouvez finir par enregistrer les lignes de cache (et ainsi gagner en performances d'exécution) lors de l'utilisation de la liaison dynamique par rapport à l'utilisation de la liaison statique. (À moins que les systèmes d'exploitation modernes ne soient assez intelligents pour remarquer des segments identiques dans des fichiers binaires liés statiquement. Cela semble difficile, quelqu'un le sait?)

Autre problème: le temps de chargement. Vous payez les frais de chargement à un moment donné. Lorsque vous payez, ce coût dépend du fonctionnement du système d'exploitation ainsi que de la liaison que vous utilisez. Peut-être préférez-vous reporter le paiement jusqu'à ce que vous sachiez que vous en avez besoin.

Notez que la liaison statique vs dynamique n'est traditionnellement pas un problème d'optimisation, car elles impliquent toutes deux une compilation séparée jusqu'aux fichiers objets. Cependant, cela n'est pas obligatoire: un compilateur peut en principe "compiler" des "bibliothèques statiques" dans un formulaire AST digéré au départ, et les "lier" en ajoutant ces AST à celles générées pour le code principal, permettant ainsi une optimisation globale. Aucun des systèmes que j'utilise ne le fait, je ne peux donc pas commenter son fonctionnement.

La façon de répondre aux questions de performances est toujours de tester (et d'utiliser un environnement de test autant que l'environnement de déploiement que possible).

dmckee --- chaton ex-modérateur
la source
24
La consommation de ressources est essentiellement l'espace de code, qui au fil du temps est de moins en moins préoccupant. Si 500 Ko de bibliothèque sont partagés entre 5 processus, cela représente une économie de 2 Mo, ce qui représente moins de 0,1% de 3 Go de RAM.
Mark Ransom
3
Si la bibliothèque partage également le même mappage virtuel (la même adresse physique et virtuelle dans tous les processus), un lien dynamique n'enregistre-t-il pas également les emplacements TLB dans la MMU du processeur?
Zan Lynx du
6
Un lien dynamique facilite également la mise à jour du code de la bibliothèque de bogues avec de meilleures versions.
Zan Lynx du
89
@Zan Il facilite également l'ajout de code bogué à une version de travail.
6
"Les plugins demandent toujours des liens dynamiques." C'est faux. Certains modèles de plugins tels que les AudioUnits d'Apple peuvent exécuter le plugin dans un processus distinct et utiliser IPC. Il s'agit d'une alternative plus sûre à la liaison dynamique pour les plugins (le plugin ne peut pas planter l'hôte). Suggérez que la réponse soit mise à jour en "Les plugins peuvent nécessiter une liaison dynamique" ou similaire.
Taylor
68

1) est basé sur le fait que l'appel d'une fonction DLL utilise toujours un saut indirect supplémentaire. Aujourd'hui, cela est généralement négligeable. À l'intérieur de la DLL, il y a un peu plus de surcharge sur les processeurs i386, car ils ne peuvent pas générer de code indépendant de la position. Sur amd64, les sauts peuvent être relatifs au compteur de programme, c'est donc une énorme amélioration.

2) C'est correct. Avec des optimisations guidées par le profilage, vous pouvez généralement gagner environ 10 à 15% de performances. Maintenant que la vitesse du processeur a atteint ses limites, cela pourrait valoir la peine.

J'ajouterais: (3) l'éditeur de liens peut organiser les fonctions dans un regroupement plus efficace du cache, de sorte que les échecs de niveau de cache coûteux soient minimisés. Cela pourrait également affecter le temps de démarrage des applications (sur la base des résultats que j'ai vus avec le compilateur Sun C ++)

Et n'oubliez pas qu'avec les DLL, aucune élimination de code mort ne peut être effectuée. Selon la langue, le code DLL peut ne pas être optimal non plus. Les fonctions virtuelles sont toujours virtuelles car le compilateur ne sait pas si un client les écrase.

Pour ces raisons, au cas où il n'y aurait pas vraiment besoin de DLL, utilisez simplement la compilation statique.

EDIT (pour répondre au commentaire, par le soulignement de l'utilisateur)

Voici une bonne ressource sur le problème de code indépendant de la position http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/

Comme expliqué, x86 ne les a pas AFAIK pour autre chose que les plages de sauts de 15 bits et non pour les sauts et appels inconditionnels. C'est pourquoi les fonctions (des générateurs) ayant plus de 32 Ko ont toujours été un problème et nécessitaient des trampolines intégrés.

Mais sur les systèmes d'exploitation x86 populaires comme Linux, vous n'avez pas besoin de vous soucier si le fichier .so / DLL n'est pas généré avec le gcccommutateur -fpic(ce qui impose l'utilisation des tables de saut indirectes). Parce que si vous ne le faites pas, le code est juste corrigé comme un éditeur de liens normal le déplacerait. Mais en faisant cela, il rend le segment de code non partageable et il aurait besoin d'un mappage complet du code du disque dans la mémoire et de le toucher avant qu'il puisse être utilisé (vider la plupart des caches, frapper les TLB), etc. Il y avait un temps quand cela a été considéré comme lent.

Vous n'auriez donc plus aucun avantage.

Je ne me rappelle pas ce système d' exploitation (Solaris ou FreeBSD) m'a donné des problèmes avec mon système de construction Unix parce que je ne faisais pas cela et demandé pourquoi il est écrasé jusqu'à ce que j'appliqué -fPICà gcc.

Lothar
la source
4
J'aime cette réponse, car elle était la seule à répondre aux points que j'ai soulevés dans la question.
Eloff
Il serait intéressant d'avoir des références sur ces aspects techniques des DLL et une comparaison entre les différents systèmes d'exploitation.
UncleZeiv
Cela semble bien, mais la vitesse du processeur n'a certainement pas atteint ses limites.
Aidiakapi
67

La liaison dynamique est le seul moyen pratique de répondre à certaines exigences de licence telles que la LGPL .

Mark Ransom
la source
17
Tant que l'utilisateur final peut se relier au code LGPL (par exemple parce que vous fournissez votre code source ou des fichiers objets compilés avec votre logiciel), la liaison statique est très bien . De plus, si votre logiciel est destiné à un usage interne (c'est-à-dire à être utilisé au sein de votre organisation uniquement et non distribué), vous pouvez établir une liaison statique. Cela s'appliquerait par exemple au logiciel serveur, où le serveur n'est pas distribué.
JBentley
3
Ne comprends pas. Pourriez-vous me donner plus de sources (ou élaborer plus) afin d'apprécier ce que vous avez écrit?
Baskaya
4
@Thorn voir la section 4.d + e de la licence LGPL . Vous devez soit distribuer sous une forme qui oblige l'utilisateur à créer un lien, soit distribuer une bibliothèque partagée (dynamique).
Mark Ransom
46

Je suis d'accord avec les points mentionnés par dnmckee, plus:

  • Les applications liées statiquement peuvent être plus faciles à déployer, car il existe moins ou pas de dépendances de fichiers supplémentaires (.dll / .so) qui peuvent causer des problèmes lorsqu'elles sont manquantes ou installées au mauvais endroit.
stakx - ne contribue plus
la source
6
Il convient de noter que le compilateur Go de Google ne compilera statiquement que des fichiers binaires principalement pour cette raison.
Hut8
34

Une raison pour effectuer une génération liée statiquement est de vérifier que la fermeture de l'exécutable est complète, c'est-à-dire que toutes les références de symboles sont résolues correctement.

Dans le cadre d'un grand système en cours de construction et de test à l'aide d'une intégration continue, les tests de régression nocturnes ont été exécutés à l'aide d'une version liée statiquement des exécutables. Parfois, nous verrions qu'un symbole ne se résoudrait pas et le lien statique échouerait même si l'exécutable lié dynamiquement se relierait avec succès.

Cela se produisait généralement lorsque les symboles profondément ancrés dans les bibliothèques partagées portaient un nom mal orthographié et n'étaient donc pas liés statiquement. L'éditeur de liens dynamique ne résout pas complètement tous les symboles, indépendamment de l'utilisation de l'évaluation en profondeur ou en largeur, de sorte que vous pouvez terminer avec un exécutable lié dynamiquement qui n'a pas de fermeture complète.

Rob Wells
la source
1
très bon point, j'ai essayé de le faire récemment avec du code que j'ai au travail mais compiler tout statiquement s'est avéré étonnamment ennuyeux et j'ai juste abandonné
UncleZeiv
21

1 / J'ai travaillé sur des projets où la liaison dynamique vs la liaison statique était comparée et la différence n'était pas suffisamment petite pour passer à la liaison dynamique (je ne faisais pas partie du test, je connais juste la conclusion)

2 / La liaison dynamique est souvent associée au PIC (Position Independent Code, code qui n'a pas besoin d'être modifié selon l'adresse à laquelle il est chargé). Selon l'architecture, le PIC peut entraîner un autre ralentissement, mais il est nécessaire pour bénéficier du partage d'une bibliothèque liée dynamiquement entre deux exécutables (et même deux processus du même exécutable si le système d'exploitation utilise la randomisation de l'adresse de chargement comme mesure de sécurité). Je ne suis pas sûr que tous les systèmes d'exploitation permettent de séparer les deux concepts, mais Solaris et Linux le font et ISTR que HP-UX fait également.

3 / J'ai participé à d'autres projets qui utilisaient la liaison dynamique pour la fonction "patch facile". Mais ce "patch facile" rend la distribution des petits correctifs un peu plus facile et compliquée un cauchemar de version. Nous avons souvent fini par avoir à tout pousser et à suivre les problèmes sur le site client car la mauvaise version était un jeton.

Ma conclusion est que j'avais utilisé des liens statiques sauf:

  • pour des choses comme les plugins qui dépendent de la liaison dynamique

  • lorsque le partage est important (grandes bibliothèques utilisées par plusieurs processus en même temps comme runtime C / C ++, bibliothèques GUI, ... qui sont souvent gérées indépendamment et pour lesquelles l'ABI est strictement défini)

Si l'on veut utiliser le "patch facile", je dirais que les bibliothèques doivent être gérées comme les grandes bibliothèques ci-dessus: elles doivent être presque indépendantes avec un ABI défini qui ne doit pas être modifié par des correctifs.

AProgrammer
la source
1
Certains systèmes d'exploitation pour les processeurs non PIC ou coûteux PIC prépareront des bibliothèques dynamiques à charger à une adresse particulière en mémoire, et s'ils peuvent le faire, ils mappent simplement dans une copie de la bibliothèque à chaque processus qui s'y relie. Cela réduit considérablement les frais généraux du PIC. Au moins OS X et certaines distributions Linux le font, je ne suis pas sûr de Windows.
Andrew McGregor
Merci Andrew, je ne savais pas que certaines distributions Linux l'utilisaient. Avez-vous une référence que je peux suivre ou un mot clé que je peux rechercher pour en savoir plus? (FWIW J'avais entendu dire que Windows faisait une variante de cela, mais Windows est trop loin de ma zone de compétence pour que je le mentionne).
Programmeur le
Je pense que le mot-clé que vous recherchez est "prelink" - il prépare une bibliothèque à charger rapidement à une certaine adresse, pour accélérer le démarrage du programme.
Blaisorblade
20

Cela explique en détail les bibliothèques partagées sur Linux et les implications en termes de performances.

non
la source
3
+1 pour le lien vers le guide DSO de Drepper, que tous ceux qui créent des bibliothèques sur Linux devraient lire.
janneb
10

Sur les systèmes de type Unix, la liaison dynamique peut rendre la vie difficile pour «root» pour utiliser une application avec les bibliothèques partagées installées dans des emplacements éloignés. Cela est dû au fait que l'éditeur de liens dynamique ne fait généralement pas attention à LD_LIBRARY_PATH ou à son équivalent pour les processus avec des privilèges root. Parfois, alors, la liaison statique sauve la mise.

Alternativement, le processus d'installation doit localiser les bibliothèques, mais cela peut rendre difficile la coexistence de plusieurs versions du logiciel sur la machine.

Jonathan Leffler
la source
1
Le point sur LD_LIBRARY_PATHn'est pas exactement un obstacle à l'utilisation des bibliothèques partagées, du moins pas dans GNU / Linux. Par exemple, si vous placez les bibliothèques partagées dans un répertoire ../lib/relatif au fichier programme, alors avec la chaîne d'outils GNU, l'option de l'éditeur de liens -rpath $ORIGIN/../libspécifiera la recherche dans la bibliothèque à partir de cet emplacement relatif. Vous pouvez ensuite facilement déplacer l'application avec toutes les bibliothèques partagées associées. En utilisant cette astuce, aucun problème d'avoir plusieurs versions de l'application et des bibliothèques non plus (en supposant qu'elles sont liées, sinon vous pourriez utiliser des liens symboliques).
FooF
> pour les processus avec privilèges root. Je pense que vous parlez de programmes setuid exécutés par des utilisateurs non root - sinon cela n'a aucun sens. Et un binaire setuid avec des bibliothèques dans des emplacements non standard est étrange - mais comme seul root peut installer ces programmes, il peut également le modifier /etc/ld.so.confdans ce cas.
Blaisorblade
10

C'est assez simple, vraiment. Lorsque vous apportez une modification à votre code source, voulez-vous attendre 10 minutes pour qu'il se compile ou 20 secondes? Vingt secondes, c'est tout ce que je peux supporter. Au-delà de cela, je sors l'épée ou commence à réfléchir à la façon dont je peux utiliser une compilation et une liaison distinctes pour la ramener dans la zone de confort.

Hans Passant
la source
1
Je n'ai pas réellement évalué la différence de vitesse de compilation, mais j'aurais un lien dynamique s'il était significativement plus rapide. Boost fait suffisamment de mauvaises choses pour mes temps de compilation.
Eloff
9

Le meilleur exemple de liaison dynamique est lorsque la bibliothèque dépend du matériel utilisé. Dans les temps anciens, la bibliothèque mathématique C était dynamique, afin que chaque plate-forme puisse utiliser toutes les capacités du processeur pour l'optimiser.

Un exemple encore meilleur pourrait être OpenGL. OpenGl est une API implémentée différemment par AMD et NVidia. Et vous ne pouvez pas utiliser une implémentation NVidia sur une carte AMD, car le matériel est différent. Vous ne pouvez pas lier OpenGL statiquement dans votre programme à cause de cela. La liaison dynamique est utilisée ici pour permettre à l'API d'être optimisée pour toutes les plateformes.

Arne
la source
8

La liaison dynamique nécessite plus de temps pour que le système d'exploitation trouve la bibliothèque dynamique et la charge. Avec la liaison statique, tout est ensemble et c'est une charge unique en mémoire.

Voir également DLL Hell . Il s'agit du scénario dans lequel la DLL que le système d'exploitation charge n'est pas celle fournie avec votre application ou la version attendue par votre application.

Thomas Matthews
la source
1
Il est important de noter qu'il existe une gamme de contre-mesures pour éviter l'enfer DLL.
ocodo
5

Un autre problème non encore discuté concerne la correction des bogues dans la bibliothèque.

Avec la liaison statique, non seulement vous devez reconstruire la bibliothèque, mais vous devrez relier et redistribuer l'exécutable. Si la bibliothèque n'est utilisée que dans un seul exécutable, ce n'est peut-être pas un problème. Mais plus il y a d'exécutables à relier et à redistribuer, plus la douleur est grande.

Avec la liaison dynamique, vous venez de reconstruire et de redistribuer la bibliothèque dynamique et vous avez terminé.

R Samuel Klatchko
la source
2

la liaison statique ne vous donne qu'un seul exe, afin de faire un changement dont vous avez besoin pour recompiler tout votre programme. Alors que dans la liaison dynamique, vous devez apporter des modifications uniquement à la DLL et lorsque vous exécutez votre exe, les modifications sont récupérées au moment de l'exécution. Il est plus facile de fournir des mises à jour et des corrections de bogues par une liaison dynamique (par exemple: Windows).

Govardhan Murali
la source
2

Il existe un nombre important et croissant de systèmes où un niveau extrême de liaison statique peut avoir un impact positif énorme sur les applications et les performances du système.

Je fais référence à ce que l'on appelle souvent les «systèmes embarqués», dont beaucoup utilisent de plus en plus de systèmes d'exploitation à usage général, et ces systèmes sont utilisés pour tout ce qui est imaginable.

Un exemple extrêmement courant est celui des périphériques utilisant des systèmes GNU / Linux utilisant Busybox . J'ai poussé cela à l'extrême avec NetBSD en créant une image système amorçable i386 (32 bits) qui inclut à la fois un noyau et son système de fichiers racine, ce dernier qui contient un seul crunchgenbinaire lié statiquement (par ) avec des liens physiques vers tous les programmes qui contiennent eux-mêmes tous (enfin au dernier compte 274) des programmes système standard complets (la plupart sauf la chaîne d'outils), et leur taille est inférieure à 20 méga- octets (et fonctionne probablement très confortablement dans un système avec seulement 64 Mo de mémoire (même avec le système de fichiers racine non compressé et entièrement en RAM), même si je n'ai pas pu en trouver un si petit pour le tester).

Il a été mentionné dans des articles précédents que le temps de démarrage d'un binaire lié statiquement est plus rapide (et il peut être beaucoup plus rapide), mais ce n'est qu'une partie de l'image, surtout lorsque tout le code objet est lié dans le même , et encore plus lorsque le système d'exploitation prend en charge la pagination du code directement à partir du fichier exécutable. Dans ce scénario idéal, le temps de démarrage des programmes est littéralement négligeable car presque toutes les pages de code seront déjà en mémoire et seront utilisées par le shell (et inittout autre processus d'arrière-plan qui pourrait être en cours d'exécution), même si le programme demandé n'a pas jamais été exécuté depuis le démarrage, car peut-être qu'une seule page de mémoire doit être chargée pour répondre aux exigences d'exécution du programme.

Mais ce n'est pas encore toute l'histoire. Je construis et utilise également généralement les installations du système d'exploitation NetBSD pour mes systèmes de développement complets en liant statiquement tous les fichiers binaires. Même si cela prend énormément d'espace disque en plus (~ 6,6 Go au total pour x86_64 avec tout, y compris la chaîne d'outils et X11 lié statiquement) (surtout si l'on garde des tables de symboles de débogage complètes disponibles pour tous les programmes ~ 2,5 Go), le résultat reste s'exécute plus rapidement dans l'ensemble et, pour certaines tâches, utilise même moins de mémoire qu'un système lié dynamique typique qui prétend partager des pages de codes de bibliothèque. Le disque est bon marché (même le disque rapide), et la mémoire pour mettre en cache les fichiers disque fréquemment utilisés est également relativement bon marché, mais les cycles CPU ne le sont vraiment pas, et payer le ld.socoût de démarrage pour chaque processus qui démarre chaquele temps qu'il démarre prendra des heures et des heures de cycles CPU loin des tâches qui nécessitent de démarrer de nombreux processus, en particulier lorsque les mêmes programmes sont utilisés à plusieurs reprises, tels que les compilateurs sur un système de développement. Statique liée toolchain programmes peuvent réduire tout-OS temps de construction multi-architecture pour mes systèmes par heure . Je n'ai pas encore intégré la chaîne d'outils dans mon crunchgenbinaire simple , mais je soupçonne que lorsque je le ferai, il y aura plus d'heures de temps de génération enregistrées en raison de la victoire pour le cache du processeur.

Greg A. Woods
la source
2

La liaison statique inclut les fichiers dont le programme a besoin dans un seul fichier exécutable.

La liaison dynamique est ce que vous considéreriez comme d'habitude, elle crée un exécutable qui nécessite toujours des DLL et autres dans le même répertoire (ou les DLL peuvent être dans le dossier système).

(DLL = bibliothèque de liens dynamiques )

Les exécutables liés dynamiquement sont compilés plus rapidement et ne sont pas aussi gourmands en ressources.

Nykal
la source
0

Static linking est un processus au moment de la compilation lorsqu'un contenu lié est copié dans le binaire principal et devient un seul binaire.

Les inconvénients:

  • le temps de compilation est plus long
  • la sortie binaire est plus grande

Dynamic linkingest un processus en cours d'exécution lorsqu'un contenu lié est chargé. Cette technique permet de:

  • mettre à niveau le binaire lié sans recompiler un binaire principal qui augmente la ABIstabilité [À propos]
  • avoir une seule copie partagée

Les inconvénients:

  • l'heure de début est plus lente (le contenu lié doit être copié)
  • des erreurs de l'éditeur de liens sont lancées lors de l'exécution
yoAlex5
la source