Collegamento statico vs collegamento dinamico


399

Esistono validi motivi di prestazione per scegliere il collegamento statico al collegamento dinamico o viceversa in determinate situazioni? Ho sentito o letto quanto segue, ma non ne so abbastanza sull'argomento per garantire la sua veridicità.

1) La differenza nelle prestazioni di runtime tra collegamento statico e collegamento dinamico è generalmente trascurabile.

2) (1) non è vero se si utilizza un compilatore di profilatura che utilizza i dati del profilo per ottimizzare i percorsi rapidi del programma perché con il collegamento statico, il compilatore può ottimizzare sia il codice che il codice della libreria. Con il collegamento dinamico è possibile ottimizzare solo il codice. Se la maggior parte del tempo viene impiegato per eseguire il codice della libreria, ciò può fare una grande differenza. In caso contrario, si applica ancora (1).


59
"Con il collegamento statico, il compilatore può ottimizzare .. il codice della libreria" ma solo se lo compila! Se si collega semplicemente a file oggetto precompilati, il compilatore non ha la possibilità di ottimizzarli.

3
Se questo è vero, allora hai ragione, ma c'è qualche domanda su quanto sia vero con i compilatori moderni, se qualcuno può verificare in un modo o nell'altro, sarebbe fantastico.
Eloff

5
Con un compilatore che si compila in codice nativo (come la maggior parte dei compilatori C / C ++) non ci sono ulteriori possibilità di ottimizzazione del codice. Se il codice viene compilato in un linguaggio intermedio (come .Net IL), il compilatore JIT viene richiamato quando la libreria viene caricata per compilarlo in codice nativo. Quella compilazione finale può migliorare sempre più nel tempo con l'evoluzione del compilatore JIT.
Tarydon,

3
@Eloff: VS2008 fa esattamente questo con LTCG abilitato. (I file lib diventano enormi, però ...) Ci ho provato e per qualcuno interessato a "cosa può fare il mio compilatore per me", è a dir poco sorprendente.
Peter

Risposte:


348
  • Il collegamento dinamico può ridurre il consumo totale di risorse (se più di un processo condivide la stessa libreria (compresa la versione in "stesso", ovviamente)). Credo che questo sia l'argomento che guida la sua presenza nella maggior parte degli ambienti. Qui "risorse" include spazio su disco, RAM e spazio cache. Naturalmente, se il tuo linker dinamico non è sufficientemente flessibile, c'è il rischio di inferno DLL .
  • Collegamento dinamico significa che le correzioni di bug e gli aggiornamenti alle librerie si propagano per migliorare il tuo prodotto senza richiedere la spedizione di nulla.
  • I plugin richiedono sempre il collegamento dinamico .
  • Collegamento statico , significa che puoi sapere che il codice verrà eseguito in ambienti molto limitati (all'inizio del processo di avvio o in modalità di salvataggio).
  • Il collegamento statico può facilitare la distribuzione dei file binari in diversi ambienti utente (a costo di invio di un programma più grande e più affamato di risorse).
  • Il collegamento statico può consentire tempi di avvio leggermente più rapidi, ma ciò dipende in parte dalla dimensione e dalla complessità del programma e dai dettagli della strategia di caricamento del sistema operativo.

Alcune modifiche per includere i suggerimenti molto rilevanti nei commenti e in altre risposte. Vorrei sottolineare che il modo in cui si interrompe dipende molto dall'ambiente in cui si intende eseguire. I sistemi embedded minimi potrebbero non disporre di risorse sufficienti per supportare il collegamento dinamico. Piccoli sistemi leggermente più grandi possono supportare il collegamento dinamico, poiché la loro memoria è abbastanza piccola da rendere molto interessanti i risparmi di RAM dal collegamento dinamico. I PC consumer in piena regola hanno, come osserva Mark, enormi risorse e probabilmente puoi lasciare che i problemi di convenienza guidino il tuo pensiero su questo argomento.


Per affrontare i problemi di prestazioni ed efficienza: dipende .

Classicamente, le librerie dinamiche richiedono un qualche tipo di strato di colla che spesso significa doppio invio o un ulteriore livello di indiretta nell'indirizzamento delle funzioni e può costare un po 'di velocità (ma il tempo di chiamata delle funzioni è in realtà una grande parte del tempo di esecuzione ???).

Tuttavia, se si eseguono più processi che tutti chiamano molto la stessa libreria, si può finire per salvare le righe della cache (e quindi vincere sulle prestazioni in esecuzione) quando si utilizza il collegamento dinamico rispetto all'utilizzo del collegamento statico. (A meno che i moderni sistemi operativi non siano abbastanza intelligenti da notare segmenti identici nei binari collegati staticamente. Sembra difficile, qualcuno lo sa?)

Un altro problema: tempo di caricamento. A un certo punto paghi i costi di caricamento. Quando paghi questo costo dipende da come funziona il sistema operativo e dal collegamento che usi. Forse preferiresti rimandare il pagamento fino a quando non sai di averne bisogno.

Si noti che il collegamento statico vs dinamico non è tradizionalmente un problema di ottimizzazione, poiché entrambi implicano una compilazione separata fino ai file oggetto. Tuttavia, ciò non è necessario: in linea di principio un compilatore può "compilare" "librerie statiche" in un modulo AST digerito inizialmente e "collegarle" aggiungendo tali AST a quelli generati per il codice principale, potenziando così l'ottimizzazione globale. Nessuno dei sistemi che uso lo fa, quindi non posso commentare su come funziona.

Il modo per rispondere alle domande sulle prestazioni è sempre testando (e utilizzando un ambiente di test il più possibile simile all'ambiente di distribuzione).


24
Il consumo di risorse è fondamentalmente lo spazio del codice, che col passare del tempo è sempre meno preoccupante. Se 500 KB di libreria sono condivisi tra 5 processi, questo significa un risparmio di 2 MB, ovvero meno dell'1% di 3 GB di RAM.
Mark Ransom,

3
Se la libreria condivide anche la stessa mappatura virtuale (lo stesso indirizzo fisico e virtuale in tutti i processi), un collegamento dinamico non salva anche gli slot TLB nella MMU del processore?
Zan Lynx,

6
Inoltre, un collegamento dinamico semplifica l'aggiornamento del codice della libreria con errori con versioni migliori.
Zan Lynx,

89
@Zan Inoltre semplifica l'aggiunta del codice buggy a una versione funzionante.

6
"I plugin richiedono sempre un collegamento dinamico." Non è corretto Alcuni modelli di plug-in come AudioUnit di Apple possono eseguire il plug-in in un processo separato e utilizzare IPC. Questa è un'alternativa più sicura al collegamento dinamico per i plug-in (il plug-in non può arrestare l'host). Suggerisci di aggiornare la risposta a "I plug-in potrebbero richiedere un collegamento dinamico" o simile.
Taylor,

68

1) si basa sul fatto che chiamare una funzione DLL utilizza sempre un salto indiretto aggiuntivo. Oggi, questo è generalmente trascurabile. All'interno della DLL c'è un po 'più di sovraccarico sulle CPU i386, perché non possono generare codice indipendente dalla posizione. Su amd64, i salti possono essere relativi al contatore del programma, quindi questo è un enorme miglioramento.

2) Questo è corretto. Con le ottimizzazioni guidate dalla profilazione, di solito puoi ottenere prestazioni del 10-15 percento circa. Ora che la velocità della CPU ha raggiunto i suoi limiti, varrebbe la pena farlo.

Vorrei aggiungere: (3) il linker può organizzare le funzioni in un raggruppamento più efficiente della cache, in modo da ridurre al minimo i costosi errori a livello di cache. Potrebbe anche influire in particolare sul tempo di avvio delle applicazioni (in base ai risultati che ho visto con il compilatore Sun C ++)

E non dimenticare che con le DLL non è possibile eseguire l'eliminazione del codice morto. A seconda della lingua, il codice DLL potrebbe non essere ottimale. Le funzioni virtuali sono sempre virtuali perché il compilatore non sa se un client lo sta sovrascrivendo.

Per questi motivi, nel caso in cui non vi sia alcuna reale necessità di DLL, utilizzare semplicemente la compilazione statica.

MODIFICA (per rispondere al commento, per sottolineatura dell'utente)

Ecco una buona risorsa sul problema del codice indipendente dalla posizione http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/

Come spiegato x86 non li ha AFAIK per nient'altro che intervalli di salto a 15 bit e non per salti e chiamate incondizionati. Ecco perché le funzioni (dai generatori) con più di 32 KB sono sempre state un problema e avevano bisogno di trampolini incorporati.

Ma su sistemi operativi x86 popolari come Linux non è necessario preoccuparsi se il file .so / DLL non viene generato con lo gccswitch -fpic(che impone l'uso delle tabelle di salto indiretto). Perché se non lo fai, il codice viene semplicemente corretto come un normale linker lo riposizionerebbe. Ma mentre fa questo rende il segmento di codice non condivisibile e avrebbe bisogno di una mappatura completa del codice dal disco alla memoria e toccarlo tutto prima che possa essere utilizzato (svuotare la maggior parte delle cache, colpire i TLB) ecc. quando questo era considerato lento.

Quindi non avresti più alcun vantaggio.

Non ricordo quale sistema operativo (Solaris o FreeBSD) mi ha dato problemi con il mio sistema di compilazione Unix perché ho appena non stavo facendo questo e mi chiedevo perché si è arrestato fino a quando ho fatto domanda -fPICper gcc.


4
Mi piace questa risposta, perché è stata l'unica a rispondere ai punti sollevati nella domanda.
Eloff,

Sarebbe interessante avere riferimenti su quei tecnicismi DLL e un confronto tra diversi sistemi operativi.
ZioZeiv,

Sembra buono, ma la velocità della CPU non ha sicuramente raggiunto i suoi limiti.
Aidiakapi,

67

Il collegamento dinamico è l'unico modo pratico per soddisfare alcuni requisiti di licenza come LGPL .


17
Fintanto che l'utente finale può ricollegarsi al codice LGPL'd (ad es. Perché si fornisce il proprio codice sorgente o file di oggetti compilati con il proprio software), il collegamento statico va bene . Inoltre, se il tuo software è per uso interno (cioè deve essere utilizzato solo all'interno della tua organizzazione e non distribuito), puoi collegarti staticamente. Ciò si applica ad esempio al software server, in cui il server non è distribuito.
JBentley,

3
Non capirlo. Potresti darmi più risorse (o elaborarne altre) per apprezzare ciò che hai scritto?
Baskaya,

4
@Thorn consultare la sezione 4.d + e della licenza LGPL . È necessario distribuire in un modulo che richiede all'utente di fare un collegamento o distribuire una libreria condivisa (dinamica).
Mark Ransom,

46

Sono d'accordo con i punti menzionati da dnmckee, oltre a:

  • Le applicazioni collegate staticamente potrebbero essere più facili da distribuire, poiché vi sono meno o nessuna dipendenza aggiuntiva dei file (.dll / .so) che potrebbero causare problemi quando mancano o sono installati nel posto sbagliato.

6
Vale la pena di notare che il compilatore Go da Google solo compilare staticamente binari per principalmente questo motivo.
Hut8

34

Un motivo per eseguire una build collegata staticamente è verificare la completa chiusura dell'eseguibile, ovvero che tutti i riferimenti ai simboli siano risolti correttamente.

Come parte di un grande sistema che veniva costruito e testato mediante l'integrazione continua, i test di regressione notturna venivano eseguiti usando una versione staticamente collegata degli eseguibili. Occasionalmente, vedremmo che un simbolo non si risolve e il collegamento statico fallisce anche se l'eseguibile collegato dinamicamente si collega correttamente.

Questo di solito si verificava quando i simboli profondamente radicati nelle librerie condivise avevano un nome errato e quindi non si collegavano staticamente. Il linker dinamico non risolve completamente tutti i simboli, indipendentemente dall'utilizzo della valutazione approfondita o approfondita, quindi puoi finire con un eseguibile collegato dinamicamente che non ha la chiusura completa.


1
ottimo punto, di recente ho provato a farlo con un po 'di codice che ho al lavoro, ma la compilazione di tutto staticamente si è rivelata sorprendentemente fastidiosa e ho appena rinunciato
UncleZeiv

21

1 / Ho partecipato a progetti in cui il collegamento dinamico rispetto al collegamento statico è stato confrontato e la differenza non è stata determinata abbastanza piccola da passare al collegamento dinamico (non facevo parte del test, conosco solo la conclusione)

2 / Il collegamento dinamico è spesso associato al PIC (codice di posizione indipendente, codice che non deve essere modificato a seconda dell'indirizzo in cui è caricato). A seconda dell'architettura, il PIC può causare un altro rallentamento, ma è necessario per trarre vantaggio dalla condivisione di una libreria collegata dinamicamente tra due eseguibili (e anche due processi dello stesso eseguibile se il sistema operativo utilizza la randomizzazione dell'indirizzo di carico come misura di sicurezza). Non sono sicuro che tutti i sistemi operativi consentano di separare i due concetti, ma Solaris e Linux fanno e ISTR anche HP-UX.

3 / Ho partecipato ad altri progetti che utilizzavano il collegamento dinamico per la funzione "Easy Patch". Ma questa "patch semplice" rende la distribuzione di piccole correzioni un po 'più facile e di quella complicata un incubo per il controllo delle versioni. Spesso finivamo per dover spingere tutto oltre a dover tenere traccia dei problemi sul sito del cliente perché era stata assegnata la versione errata.

La mia conclusione è che avevo usato il collegamento statico tranne:

  • per cose come i plugin che dipendono dal collegamento dinamico

  • quando la condivisione è importante (grandi librerie utilizzate da più processi contemporaneamente come runtime C / C ++, librerie GUI, ... che spesso sono gestite in modo indipendente e per le quali l'ABI è strettamente definito)

Se si desidera utilizzare la "patch semplice", direi che le librerie devono essere gestite come le grandi librerie sopra: devono essere quasi indipendenti con un ABI definito che non deve essere modificato dalle correzioni.


1
Alcuni sistemi operativi per processori non PIC o PIC costosi prepareranno le librerie dinamiche da caricare in un determinato indirizzo in memoria e, se riescono a farlo, mappano semplicemente una copia della libreria su ogni processo che vi si collega. Ciò riduce molto il sovraccarico di PIC. Almeno OS X e alcune distribuzioni Linux fanno questo, non sono sicuro di Windows.
Andrew McGregor,

Grazie Andrew, non sapevo che alcune distribuzioni Linux usassero questo. Hai un riferimento che posso seguire o una parola chiave che posso cercare per saperne di più? (FWIW avevo sentito che Windows stava facendo una variante di questo, ma Windows è troppo lontano dalla mia area di competenza per me menzionarlo).
Approgrammatore

Penso che la parola chiave che stai cercando sia "prelink": prepara una libreria da caricare rapidamente a un determinato indirizzo, per accelerare l'avvio del programma.
Blaisorblade,

20

Questo discute in dettaglio sulle librerie condivise su Linux e sulle implicazioni delle prestazioni.


3
+1 per il collegamento al howto DSO di Drepper, che tutti coloro che creano librerie su Linux dovrebbero leggere.
janneb,

10

Su sistemi simili a Unix, il collegamento dinamico può rendere la vita difficile a "root" di utilizzare un'applicazione con le librerie condivise installate in posizioni remote. Questo perché il linker dinamico generalmente non presta attenzione a LD_LIBRARY_PATH o al suo equivalente per i processi con privilegi di root. A volte, quindi, il collegamento statico salva la giornata.

In alternativa, il processo di installazione deve individuare le librerie, ma ciò può rendere difficile la coesistenza di più versioni del software sulla macchina.


1
Il punto su LD_LIBRARY_PATHnon è esattamente un ostacolo per l'utilizzo di librerie condivise, almeno non in GNU / Linux. Ad esempio, se si inseriscono le librerie condivise nella directory ../lib/relativa al file di programma, quindi con la catena di strumenti GNU l'opzione del linker -rpath $ORIGIN/../libspecificherà la ricerca della libreria da quella posizione relativa. È quindi possibile riposizionare facilmente l'applicazione insieme a tutte le librerie condivise associate. Utilizzando questo trucco, nessun problema con la versione multipla dell'applicazione e delle librerie (supponendo che siano correlate, in caso contrario è possibile utilizzare collegamenti simbolici).
FooF,

> per processi con privilegi di root. Penso che tu stia parlando di programmi setuid eseguiti da utenti non root - altrimenti non ha senso. E un binario setuid con librerie in posizioni non standard è strano - ma poiché solo root può installare quei programmi, può anche modificare /etc/ld.so.confper quel caso.
Blaisorblade,

10

È piuttosto semplice, davvero. Quando si modifica il codice sorgente, si desidera attendere 10 minuti per la compilazione o 20 secondi? Venti secondi sono tutto ciò che posso sopportare. Oltre a ciò, tiro fuori la spada o comincio a pensare a come usare la compilazione e il collegamento separati per riportarla nella zona di comfort.


1
In realtà non ho confrontato la differenza nelle velocità di compilazione, ma vorrei collegare dinamicamente se fosse significativamente più veloce. Boost fa abbastanza cose cattive per i miei tempi di compilazione così com'è.
Eloff,

9

Il miglior esempio di collegamento dinamico è quando la libreria dipende dall'hardware utilizzato. Nell'antichità la libreria matematica C era considerata dinamica, in modo che ogni piattaforma potesse utilizzare tutte le funzionalità del processore per ottimizzarla.

Un esempio ancora migliore potrebbe essere OpenGL. OpenGl è un'API implementata in modo diverso da AMD e NVidia. E non è possibile utilizzare un'implementazione NVidia su una scheda AMD, poiché l'hardware è diverso. Non è possibile collegare OpenGL staticamente al programma, per questo motivo. Il collegamento dinamico viene utilizzato qui per consentire l'ottimizzazione dell'API per tutte le piattaforme.


8

Il collegamento dinamico richiede più tempo per il sistema operativo per trovare la libreria dinamica e caricarla. Con il collegamento statico, tutto è insieme ed è un carico di un colpo in memoria.

Vedi anche inferno DLL . Questo è lo scenario in cui la DLL caricata dal sistema operativo non è quella fornita con l'applicazione o la versione prevista dall'applicazione.


1
È importante notare che esiste una serie di contromisure per evitare l'inferno DLL.
ocodo,

5

Un altro problema non ancora discusso è la correzione di bug nella libreria.

Con il collegamento statico, non devi solo ricostruire la libreria, ma dovrai ricollegare e ridistribuire l'eseguibile. Se la libreria viene utilizzata solo in un eseguibile, questo potrebbe non essere un problema. Ma più eseguibili è necessario ricollegare e ridistribuire, maggiore è il dolore.

Con il collegamento dinamico, è sufficiente ricostruire e ridistribuire la libreria dinamica e il gioco è fatto.


2

il collegamento statico ti dà un solo exe, in ordine per apportare una modifica che devi ricompilare l'intero programma. Mentre nel collegamento dinamico è necessario apportare modifiche solo alla dll e quando si esegue exe, le modifiche verrebbero rilevate in fase di esecuzione. È più semplice fornire aggiornamenti e correzioni di errori tramite il collegamento dinamico (ad esempio: windows).


2

Esiste un numero crescente e crescente di sistemi in cui un livello estremo di collegamento statico può avere un impatto positivo enorme sulle applicazioni e sulle prestazioni del sistema.

Mi riferisco a quelli che spesso vengono chiamati "sistemi integrati", molti dei quali ora utilizzano sempre più sistemi operativi generici e questi sistemi vengono utilizzati per tutto ciò che si può immaginare.

Un esempio estremamente comune sono i dispositivi che utilizzano sistemi GNU / Linux che utilizzano Busybox . L'ho portato all'estremo con NetBSD costruendo un'immagine di sistema i386 (32-bit) avviabile che include sia un kernel che il suo filesystem di root, quest'ultimo che contiene un singolo crunchgenbinario con collegamento statico (by ) con collegamenti diretti a tutti i programmi che a sua volta contengono tutti (ben almeno conteggio 274) dei programmi di sistema standard completi (la maggior parte tranne la toolchain), ed è inferiore a 20 mega byte (e probabilmente funziona molto comodamente in un sistema con solo 64 MB di memoria (anche con il filesystem di root non compresso e interamente in RAM), anche se non sono riuscito a trovarne uno così piccolo su cui testarlo).

In precedenti post è stato menzionato che il tempo di avvio di un binario a collegamento statico è più veloce (e può essere molto più veloce), ma è solo una parte dell'immagine, specialmente quando tutto il codice oggetto è collegato nello stesso file, e ancora di più soprattutto quando il sistema operativo supporta il paging della domanda di codice direttamente dal file eseguibile. In questo scenario ideale il tempo di avvio dei programmi è letteralmente trascurabile poiché quasi tutte le pagine di codice saranno già in memoria e saranno utilizzate dalla shell (einit qualsiasi altro processo in background che potrebbe essere in esecuzione), anche se il programma richiesto non ha mai eseguito dal boot poiché forse è necessario caricare solo una pagina di memoria per soddisfare i requisiti di runtime del programma.

Tuttavia, questa non è ancora l'intera storia. Di solito costruisco e utilizzo anche le installazioni del sistema operativo NetBSD per i miei sistemi di sviluppo completi collegando staticamente tutti i file binari. Anche se questo richiede un'enorme quantità di spazio su disco (~ 6,6 GB in totale per x86_64 con tutto, inclusi toolchain e X11 collegati staticamente) (specialmente se si mantengono disponibili tabelle di simboli di debug complete per tutti i programmi un altro ~ 2,5 GB), il risultato rimane comunque viene eseguito nel complesso più velocemente e per alcune attività utilizza persino meno memoria rispetto a un tipico sistema a collegamento dinamico che pretende di condividere le code page delle librerie. Il disco è economico (anche disco veloce) e anche la memoria per memorizzare nella cache i file del disco utilizzati di frequente è relativamente economica, ma i cicli della CPU non lo sono e paganold.so costo di avvio per ogni processo che avvia ogniil tempo di avvio richiederà ore e ore di cicli della CPU lontani dalle attività che richiedono l'avvio di molti processi, soprattutto quando gli stessi programmi vengono utilizzati più e più volte, come i compilatori su un sistema di sviluppo. I programmi di toolchain collegati in modo statico possono ridurre di ore il tempo di creazione di più architetture dell'intero sistema operativo per i miei sistemi . Devo ancora compilare la toolchain nel mio crunchgenbinario singolo , ma sospetto che quando lo faccio ci saranno più ore di tempo di costruzione risparmiate a causa della vincita per la cache della CPU.


2

Il collegamento statico include i file necessari al programma in un singolo file eseguibile.

Il collegamento dinamico è quello che considereresti il ​​solito, crea un eseguibile che richiede ancora DLL e che si trovano nella stessa directory (o che le DLL potrebbero trovarsi nella cartella di sistema).

(DLL = collegamento dinamico libreria a )

Gli eseguibili collegati dinamicamente vengono compilati più velocemente e non sono così ricchi di risorse.


0

Static linking è un processo in fase di compilazione quando un contenuto collegato viene copiato nel file binario primario e diventa un singolo file binario.

Contro:

  • il tempo di compilazione è più lungo
  • l'output binario è più grande

Dynamic linkingè un processo in fase di esecuzione quando viene caricato un contenuto collegato. Questa tecnica consente di:

  • aggiorna binario collegato senza ricompilare uno primario che aumenta una ABIstabilità [Informazioni]
  • avere una singola copia condivisa

Contro:

  • l'ora di inizio è più lenta (il contenuto collegato deve essere copiato)
  • gli errori del linker vengono generati in fase di esecuzione
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.