Statisch koppelen versus dynamisch koppelen

Zijn er dwingende prestatieredenen om in bepaalde situaties statisch linken te verkiezen boven dynamisch linken of omgekeerd? Ik heb het volgende gehoord of gelezen, maar ik weet niet genoeg over het onderwerp om in te staan ​​voor de juistheid ervan.

1) Het verschil in runtime-prestaties tussen statische koppeling en dynamische koppeling is meestal verwaarloosbaar.

2) (1) is niet waar als u een profileringscompiler gebruikt die profielgegevens gebruikt om de hotpaths van programma’s te optimaliseren, omdat met statische koppeling de compiler zowel uw code als de bibliotheekcode kan optimaliseren. Met dynamisch koppelen kan alleen uw code worden geoptimaliseerd. Als de meeste tijd wordt besteed aan het uitvoeren van bibliotheekcode, kan dit een groot verschil maken. Anders geldt (1) nog steeds.


Antwoord 1, autoriteit 100%

  • Dynamische koppeling kan het totale verbruik van bronnen verminderen (als meer dan één proces dezelfde bibliotheek deelt (inclusief de versie in “dezelfde”, natuurlijk)). Ik geloof dat dit het argument is dat het zijn aanwezigheid in de meeste omgevingen drijft. Hier omvatten “bronnen” schijfruimte, RAM en cacheruimte. Als uw dynamische linker onvoldoende flexibel is, bestaat natuurlijk het risico van DLL-hel.
  • Dynamische koppeling betekent dat bugfixes en upgrades naar bibliotheken verspreiden om uw product te verbeteren zonder dat u iets hoeft te verzenden.
  • Plug-ins vragen altijd om dynamische koppelingen.
  • Statische koppeling betekent dat u weet dat de code in zeer beperkte omgevingen zal worden uitgevoerd (vroeg in het opstartproces of in de reddingsmodus).
  • Statische koppelingen kunnen binaire bestanden gemakkelijker te distribueren naar verschillende gebruikersomgevingen maken (ten koste van het verzenden van een groter en meer bronnen hongerig programma).
  • Statische koppelingen kunnen iets snellere opstarttijden mogelijk maken, maar dit hangt tot op zekere hoogte af van zowel de grootte als de complexiteit van uw programma en van de details van de laadstrategie van het besturingssysteem.

Enkele bewerkingen om de zeer relevante suggesties op te nemen in de opmerkingen en in andere antwoorden. Ik wil graag opmerken dat de manier waarop je dit doorbreekt sterk afhangt van de omgeving waarin je van plan bent te draaien. Minimale embedded systemen hebben mogelijk niet genoeg middelen om dynamische koppeling te ondersteunen. Iets grotere kleine systemen ondersteunen mogelijk dynamisch koppelen, omdat hun geheugen klein genoeg is om de RAM-besparingen door dynamisch koppelen zeer aantrekkelijk te maken. Volwaardige pc’s voor consumenten hebben, zoals Mark opmerkt, enorme middelen, en u kunt zich hierover waarschijnlijk laten leiden door gemakskwesties.


Om de prestatie- en efficiëntieproblemen aan te pakken: het hangt ervan af.

Klassiek hebben dynamische bibliotheken een soort lijmlaag nodig, wat vaak dubbele verzending of een extra laag indirecte functie-adressering betekent en een beetje snelheid kan kosten (maar is de aanroeptijd van een functie eigenlijk een groot deel van uw looptijd?? ?).

Als u echter meerdere processen uitvoert die allemaal dezelfde bibliotheek vaak aanroepen, kunt u uiteindelijk cacheregels besparen (en dus winnen op prestaties) wanneer u dynamische koppeling gebruikt in vergelijking met statische koppeling. (Tenzij moderne besturingssystemen slim genoeg zijn om identieke segmenten op te merken in statisch gekoppelde binaire bestanden. Lijkt moeilijk, weet iemand dat?)

Een ander probleem: laadtijd. Je betaalt op een gegeven moment laadkosten. Wanneer u deze kosten betaalt, hangt af van hoe het besturingssysteem werkt en welke koppeling u gebruikt. Misschien stel je het betalen liever uit tot je weet dat je het nodig hebt.

Houd er rekening mee dat statisch-vs-dynamisch koppelen traditioneel geen een optimalisatieprobleem is, omdat beide afzonderlijke compilaties tot objectbestanden omvatten. Dit is echter niet vereist: een compiler kan in principe “statische bibliotheken” in eerste instantie “compileren” naar een verteerd AST-formulier en deze “linken” door die AST’s toe te voegen aan de AST’s die voor de hoofdcode zijn gegenereerd, waardoor globale optimalisatie mogelijk wordt. Geen van de systemen die ik gebruik, doet dit, dus ik kan niet beoordelen hoe goed het werkt.

De manier om prestatievragen te beantwoorden is altijd door te testen (en een testomgeving te gebruiken die zoveel mogelijk lijkt op de implementatieomgeving).


Antwoord 2, autoriteit 19%

1) is gebaseerd op het feit dat het aanroepen van een DLL-functie altijd een extra indirecte sprong is. Tegenwoordig is dit meestal te verwaarlozen. Binnen de DLL is er wat meer overhead op i386 CPU’s, omdat ze geen positie-onafhankelijke code kunnen genereren. Op amd64 kunnen sprongen relatief zijn ten opzichte van de programmateller, dus dit is een enorme verbetering.

2) Dit is correct. Met optimalisaties op basis van profilering kunt u meestal ongeveer 10-15 procent prestaties winnen. Nu de CPU-snelheid zijn limiet heeft bereikt, is het misschien de moeite waard om het te doen.

Ik zou willen toevoegen: (3) de linker kan functies rangschikken in een meer cache-efficiënte groepering, zodat dure missers op cacheniveau worden geminimaliseerd. Het kan ook met name de opstarttijd van applicaties beïnvloeden (gebaseerd op resultaten die ik heb gezien met de Sun C++-compiler)

En vergeet niet dat met DLL’s geen eliminatie van dode code kan worden uitgevoerd. Afhankelijk van de taal is de DLL-code mogelijk ook niet optimaal. Virtuele functies zijn altijd virtueel omdat de compiler niet weet of een client deze overschrijft.

Om deze redenen, als er geen echte behoefte aan DLL’s is, gebruik dan gewoon statische compilatie.

BEWERKEN (om de opmerking te beantwoorden, op onderstrepingsteken van de gebruiker)

Hier is een goede bron over het positie-onafhankelijke codeprobleem http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/

Zoals uitgelegd heeft x86 ze niet AFAIK voor iets anders dan 15-bits sprongbereiken en niet voor onvoorwaardelijke sprongen en oproepen. Dat is de reden waarom functies (van generatoren) met meer dan 32K altijd een probleem waren en ingebedde trampolines nodig hadden.

Maar op populaire x86-besturingssystemen zoals Linux hoeft u zich geen zorgen te maken als het .so/DLL-bestand niet wordt gegenereerd met de gcc-schakelaar -fPIC (die de gebruik van de indirecte sprongtabellen). Want als je dat niet doet, is de code gewoon vast zoals een normale linker hem zou verplaatsen. Maar terwijl je dit doet, wordt het codesegment niet deelbaar en zou het een volledige toewijzing van de code van de schijf naar het geheugen nodig hebben en alles aanraken voordat het kan worden gebruikt (de meeste caches legen, TLB’s gebruiken) enz. Er was een tijd wanneer dit als traag werd beschouwd.

Je zou dus geen voordeel meer hebben.

Ik kan me niet herinneren welk besturingssysteem (Solaris of FreeBSD) me problemen gaf met mijn Unix-buildsysteem omdat ik dit gewoon niet deed en me afvroeg waarom het crashte totdat ik -fPIC toepaste op gcc.


Antwoord 3, autoriteit 19%

Dynamisch koppelen is de enige praktische manier om aan bepaalde licentievereisten te voldoen, zoals de LGPL .


Antwoord 4, autoriteit 12%

Ik ga akkoord met de punten die dnmckee noemt, plus:

  • Statisch gekoppelde applicaties zijn wellicht gemakkelijker te implementeren, omdat er minder of geen extra bestandsafhankelijkheden (.dll / .so) zijn die problemen kunnen veroorzaken als ze ontbreken of op de verkeerde plaats zijn geïnstalleerd.

Antwoord 5, autoriteit 9%

Een reden om een ​​statisch gekoppelde build uit te voeren, is om te controleren of het uitvoerbare bestand volledig is afgesloten, d.w.z. dat alle symboolverwijzingen correct zijn opgelost.

Als onderdeel van een groot systeem dat werd gebouwd en getest met behulp van continue integratie, werden de nachtelijke regressietests uitgevoerd met behulp van een statisch gekoppelde versie van de uitvoerbare bestanden. Af en toe zagen we dat een symbool niet zou worden opgelost en dat de statische koppeling zou mislukken, ook al zou het dynamisch gekoppelde uitvoerbare bestand succesvol worden gekoppeld.

Dit gebeurde meestal wanneer symbolen die diep in de gedeelde bibliotheken zaten een verkeerd gespelde naam hadden en dus niet statisch zouden linken. De dynamische linker lost niet alle symbolen volledig op, ongeacht het gebruik van diepte-eerst of breedte-eerst evaluatie, dus u kunt eindigen met een dynamisch gekoppeld uitvoerbaar bestand dat geen volledige afsluiting heeft.


Antwoord 6, autoriteit 6%

1/ Ik heb aan projecten deelgenomen waar dynamisch koppelen versus statisch koppelen werd gebenchmarkt en het verschil niet klein genoeg was bepaald om over te schakelen naar dynamisch koppelen (ik maakte geen deel uit van de test, ik ken alleen de conclusie)

2/ Dynamische koppeling wordt vaak geassocieerd met PIC (Position Independent Code, code die niet hoeft te worden gewijzigd afhankelijk van het adres waarop deze is geladen). Afhankelijk van de architectuur kan PIC nog een vertraging veroorzaken, maar dit is nodig om te profiteren van het delen van een dynamisch gekoppelde bibliotheek tussen twee uitvoerbare bestanden (en zelfs twee processen van hetzelfde uitvoerbare bestand als het besturingssysteem randomisatie van het laadadres als beveiligingsmaatregel gebruikt). Ik weet niet zeker of alle besturingssystemen het mogelijk maken om de twee concepten te scheiden, maar Solaris en Linux doen dat en ISTR dat HP-UX ook doet.

3/ Ik ben bij andere projecten geweest die dynamische koppeling gebruikten voor de “gemakkelijke patch”-functie. Maar deze “gemakkelijke patch” maakt de distributie van kleine reparaties een beetje makkelijker en van gecompliceerde een versie-nachtmerrie. We eindigden vaak door alles te pushen en problemen op de site van de klant op te sporen omdat de verkeerde versie een token was.

Mijn conclusie is dat ik statisch linken had gebruikt, uitgezonderd:

  • voor zaken als plug-ins die afhankelijk zijn van dynamische koppeling

  • wanneer delen belangrijk is (grote bibliotheken die door meerdere processen tegelijkertijd worden gebruikt, zoals C/C++ runtime, GUI-bibliotheken, … die vaak onafhankelijk worden beheerd en waarvoor de ABI strikt is gedefinieerd)

Als je de “eenvoudige patch” wilt gebruiken, zou ik zeggen dat de bibliotheken moeten worden beheerd zoals de grote bibliotheken hierboven: ze moeten bijna onafhankelijk zijn met een gedefinieerde ABI die niet door fixes mag worden gewijzigd.


Antwoord 7, autoriteit 5%

Hier wordt uitgebreid ingegaan op gedeelde bibliotheken op Linux en implicaties voor prestaties.


Antwoord 8, autoriteit 3%

Het beste voorbeeld voor dynamisch koppelen is wanneer de bibliotheek afhankelijk is van de gebruikte hardware. In de oudheid werd besloten om de C-wiskundebibliotheek dynamisch te maken, zodat elk platform alle processormogelijkheden kan gebruiken om het te optimaliseren.

Een nog beter voorbeeld zou OpenGL kunnen zijn. OpenGl is een API die door AMD en NVidia anders wordt geïmplementeerd. En je kunt geen NVidia-implementatie gebruiken op een AMD-kaart, omdat de hardware anders is. Je kunt OpenGL daarom niet statisch koppelen aan je programma. Dynamische koppeling wordt hier gebruikt om de API voor alle platforms te optimaliseren.


Antwoord 9, autoriteit 3%

Het is eigenlijk vrij eenvoudig. Wanneer u een wijziging aanbrengt in uw broncode, wilt u dan 10 minuten wachten voordat deze is gebouwd of 20 seconden? Twintig seconden is alles wat ik kan verdragen. Verder haal ik het zwaard tevoorschijn of begin ik na te denken over hoe ik afzonderlijke compilatie en koppeling kan gebruiken om het terug in de comfortzone te brengen.


Antwoord 10, autoriteit 3%

Op Unix-achtige systemen kan dynamisch koppelen het ‘root’ moeilijk maken om een ​​applicatie te gebruiken met de gedeelde bibliotheken die op afgelegen locaties zijn geïnstalleerd. Dit komt omdat de dynamische linker over het algemeen geen aandacht schenkt aan LD_LIBRARY_PATH of het equivalent daarvan voor processen met rootrechten. Soms redt statische koppeling de dag.

Als alternatief moet het installatieproces de bibliotheken lokaliseren, maar dat kan het moeilijk maken voor meerdere versies van de software naast elkaar op de machine.


Antwoord 11, autoriteit 2%

Dynamisch koppelen vereist extra tijd voor het besturingssysteem om de dynamische bibliotheek te vinden en te laden. Met statische koppeling is alles bij elkaar en is het een eenmalige belasting in het geheugen.

Zie ook DLL Hell. Dit is het scenario waarin de DLL die het besturingssysteem laadt niet de DLL is die bij uw toepassing is geleverd, of de versie die uw toepassing verwacht.


Antwoord 12, autoriteit 2%

Static linking is een proces tijdens het compileren wanneer een gekoppelde inhoud wordt gekopieerd naar het primaire binaire bestand en een enkel binair bestand wordt.

Nadelen:

  • compilatietijd is langer
  • uitvoer binair is groter

Dynamic linking is een proces in runtime wanneer een gekoppelde inhoud wordt geladen. Deze techniek maakt het mogelijk om:

  • upgrade gekoppeld binair bestand zonder een primair bestand opnieuw te compileren dat de stabiliteit van ABI verhoogt[Over]< /sup>
  • heeft één gedeelde kopie

Nadelen:

  • starttijd is langzamer (gelinkte inhoud moet worden gekopieerd)
  • linkerfouten worden gegenereerd tijdens runtime

[iOS Static vs Dynamic framework]


Antwoord 13, autoriteit 2%

Een ander probleem dat nog niet is besproken, is het oplossen van bugs in de bibliotheek.

Met statische koppeling moet u niet alleen de bibliotheek opnieuw opbouwen, maar moet u ook het uitvoerbare bestand opnieuw koppelen en distribueren. Als de bibliotheek slechts in één uitvoerbaar bestand wordt gebruikt, is dit mogelijk geen probleem. Maar hoe meer uitvoerbare bestanden er opnieuw moeten worden gekoppeld en gedistribueerd, hoe groter de pijn is.

Met dynamisch koppelen, herbouwt en distribueert u gewoon de dynamische bibliotheek en u bent klaar.


Antwoord 14, autoriteit 2%

Statische koppelingen bevatten de bestanden die het programma nodig heeft in een enkel uitvoerbaar bestand.

Dynamisch koppelen is wat u als normaal zou beschouwen, het maakt een uitvoerbaar bestand waarvoor nog steeds DLL’s en dergelijke in dezelfde map moeten staan ​​(of de DLL’s kunnen zich in de systeemmap bevinden).

(DLL = dynamische link bibliotheek)

Dynamisch gekoppelde uitvoerbare bestanden worden sneller gecompileerd en zijn niet zo veel resources.


Antwoord 15

statisch linken geeft je slechts een enkele exe, om een ​​wijziging aan te brengen moet je je hele programma opnieuw compileren. Terwijl u bij dynamisch koppelen alleen wijzigingen hoeft aan te brengen in de dll en wanneer u uw exe uitvoert, worden de wijzigingen tijdens runtime opgepikt. Het is gemakkelijker om updates en bugfixes te bieden door dynamisch koppelen (bijv. Windows).


Antwoord 16

Er is een enorm en toenemend aantal systemen waar een extreem niveau van statische koppeling een enorm positieve invloed kan hebben op applicaties en systeemprestaties.

Ik verwijs naar wat vaak ’embedded systemen’ worden genoemd, waarvan vele nu steeds meer algemene besturingssystemen gebruiken, en deze systemen worden gebruikt voor alles wat denkbaar is.

Een extreem veelvoorkomend voorbeeld zijn apparaten die GNU/Linux-systemen gebruiken die Busybox gebruiken. Ik heb dit tot het uiterste doorgevoerd met NetBSD door een opstartbare i386 (32-bit) systeemimage te bouwen die bevat zowel een kernel als zijn rootbestandssysteem, de laatste die een enkel statisch gekoppeld (door crunchgen) binair bestand bevat met harde links naar alle programma’s die zelf alles bevatten (nou ja tel als laatste 274) van de standaard systeemprogramma’s met volledige functionaliteit (de meeste behalve de toolchain), en het is minder dan 20 megabytes groot (en werkt waarschijnlijk zeer comfortabel in een systeem met slechts 64 MB aan geheugen (zelfs met het rootbestandssysteem ongecomprimeerd en volledig in RAM), hoewel ik er geen zo klein heb kunnen vinden om het op te testen).

In eerdere berichten is vermeld dat de opstarttijd van statisch gekoppelde binaire bestanden sneller is (en het kan veel sneller zijn), maar dat is slechts een deel van de afbeelding, vooral wanneer alle objectcode in hetzelfde bestand is gekoppeld, en nog meer in het bijzonder wanneer het besturingssysteem het oproepen van code rechtstreeks vanuit het uitvoerbare bestand ondersteunt. In dit ideale scenario is de opstarttijd van programma’s letterlijk verwaarloosbaar aangezien bijna alle pagina’s met code al in het geheugen staan ​​en in gebruik zijn door de shell (en en init andere achtergrondprocessen die mogelijk worden uitgevoerd), zelfs als het gevraagde programma nog nooit is uitgevoerd sinds het opstarten, omdat er misschien maar één pagina geheugen hoeft te worden geladen om aan de runtime-vereisten van het programma te voldoen.

Maar dat is nog niet het hele verhaal. Ik bouw en gebruik meestal ook de installatie van het NetBSD-besturingssysteem voor mijn volledige ontwikkelingssystemen door alle binaire bestanden statisch te koppelen. Ook al kost dit enorm veel meer schijfruimte (~ 6,6 GB totaal voor x86_64 met alles, inclusief toolchain en X11 statisch gekoppeld) (vooral als men volledige debug-symbooltabellen beschikbaar houdt voor alle programma’s nog eens ~2,5 GB), is het resultaat nog steeds werkt over het algemeen sneller en gebruikt voor sommige taken zelfs minder geheugen dan een typisch dynamisch gekoppeld systeem dat beweert bibliotheekcodepagina’s te delen. Schijf is goedkoop (zelfs snelle schijf), en geheugen voor het cachen van veelgebruikte schijfbestanden is ook relatief goedkoop, maar CPU-cycli zijn dat echt niet, en het betalen van de ld.so opstartkosten voor elk proces dat start elke keer dat het start, zal het uren en uren CPU-cycli vergen van taken waarvoor veel processen moeten worden gestart, vooral wanneer dezelfde programma’s steeds opnieuw worden gebruikt, zoals compilers op een ontwikkelsysteem. Statisch gekoppelde toolchain-programma’s kunnen de bouwtijd van het hele besturingssysteem met meerdere architectuur voor mijn systemen met uur verkorten. Ik moet de toolchain nog inbouwen in mijn enkele crunchgen‘ed binary, maar ik vermoed dat als ik dat doe er meer uren aan bouwtijd bespaard zullen worden vanwege de winst voor de CPU-cache.


Antwoord 17

Een andere overweging is het aantal objectbestanden (vertaaleenheden) dat u daadwerkelijk in een bibliotheek gebruikt, versus het totale aantal dat beschikbaar is. Als een bibliotheek is opgebouwd uit veel objectbestanden, maar je gebruikt alleen symbolen van een paar ervan, kan dit een argument zijn om statisch linken te verkiezen, aangezien je alleen de objecten koppelt die je gebruikt als je statisch linkt (meestal) en niet doet. t dragen normaal gesproken de ongebruikte symbolen. Als je kiest voor een gedeelde bibliotheek, bevat die bibliotheek alle vertaaleenheden en kan deze veel groter zijn dan wat je wilt of nodig hebt.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

1 + 6 =

Other episodes