Gibt es zwingende Leistungsgründe, in bestimmten Situationen statische Verknüpfungen gegenüber dynamischen Verknüpfungen zu wählen oder umgekehrt? Ich habe Folgendes gehört oder gelesen, aber ich weiß nicht genug über das Thema, um für seine Richtigkeit zu bürgen.
1) Der Unterschied in der Laufzeitleistung zwischen statischer und dynamischer Verknüpfung ist normalerweise vernachlässigbar.
2) (1) ist nicht wahr, wenn ein Profiling-Compiler verwendet wird, der Profildaten zur Optimierung von Programm-Hotpaths verwendet, da der Compiler durch statische Verknüpfung sowohl Ihren Code als auch den Bibliothekscode optimieren kann. Mit der dynamischen Verknüpfung kann nur Ihr Code optimiert werden. Wenn die meiste Zeit damit verbracht wird, Bibliothekscode auszuführen, kann dies einen großen Unterschied machen. Ansonsten gilt (1) weiterhin.
Antworten:
Einige Änderungen, um die sehr relevanten Vorschläge in die Kommentare und in andere Antworten aufzunehmen. Ich möchte darauf hinweisen, dass die Art und Weise, wie Sie damit umgehen, stark von der Umgebung abhängt, in der Sie ausgeführt werden möchten. Minimale eingebettete Systeme verfügen möglicherweise nicht über genügend Ressourcen, um dynamische Verknüpfungen zu unterstützen. Etwas größere kleine Systeme unterstützen möglicherweise die dynamische Verknüpfung, da ihr Speicher klein genug ist, um die RAM-Einsparungen durch die dynamische Verknüpfung sehr attraktiv zu machen. Ausgereifte Consumer-PCs verfügen, wie Mark bemerkt, über enorme Ressourcen, und Sie können sich wahrscheinlich von den praktischen Problemen in dieser Angelegenheit Gedanken machen lassen.
Um die Leistungs- und Effizienzprobleme anzugehen: Es kommt darauf an .
Klassischerweise erfordern dynamische Bibliotheken eine Art Klebeschicht, was häufig einen doppelten Versand oder eine zusätzliche Indirektionsebene bei der Funktionsadressierung bedeutet und ein wenig Geschwindigkeit kosten kann (aber ist die Funktionsaufrufzeit tatsächlich ein großer Teil Ihrer Laufzeit ???).
Wenn Sie jedoch mehrere Prozesse ausführen, die alle häufig dieselbe Bibliothek aufrufen, können Sie bei Verwendung der dynamischen Verknüpfung im Vergleich zur Verwendung der statischen Verknüpfung Cache-Zeilen speichern (und damit die Leistung verbessern). (Es sei denn, moderne Betriebssysteme sind intelligent genug, um identische Segmente in statisch verknüpften Binärdateien zu erkennen. Scheint schwierig, weiß jemand?)
Ein weiteres Problem: Ladezeit. Sie zahlen irgendwann die Ladekosten. Wann Sie diese Kosten bezahlen, hängt davon ab, wie das Betriebssystem funktioniert und welche Verknüpfung Sie verwenden. Vielleicht möchten Sie es lieber aufschieben, bis Sie wissen, dass Sie es brauchen.
Beachten Sie, dass die statische und dynamische Verknüpfung traditionell kein Optimierungsproblem darstellt, da beide eine separate Kompilierung bis hin zu Objektdateien umfassen. Dies ist jedoch nicht erforderlich: Ein Compiler kann im Prinzip "statische Bibliotheken" zunächst mit einem verdauten AST-Formular "kompilieren" und diese "verknüpfen", indem er diese ASTs zu den für den Hauptcode generierten hinzufügt, wodurch die globale Optimierung ermöglicht wird. Keines der von mir verwendeten Systeme tut dies, daher kann ich nicht beurteilen, wie gut es funktioniert.
Die Beantwortung von Leistungsfragen erfolgt immer durch Testen (und Verwenden einer Testumgebung, die der Bereitstellungsumgebung so ähnlich wie möglich ist).
quelle
1) basiert auf der Tatsache, dass beim Aufrufen einer DLL-Funktion immer ein zusätzlicher indirekter Sprung verwendet wird. Heute ist dies normalerweise vernachlässigbar. Innerhalb der DLL gibt es etwas mehr Overhead auf i386-CPUs, da sie keinen positionsunabhängigen Code generieren können. Auf amd64 können Sprünge relativ zum Programmzähler sein, was eine enorme Verbesserung darstellt.
2) Das ist richtig. Mit Optimierungen, die sich an der Profilerstellung orientieren, können Sie normalerweise eine Leistung von 10 bis 15 Prozent erzielen. Jetzt, da die CPU-Geschwindigkeit an ihre Grenzen gestoßen ist, könnte es sich lohnen, dies zu tun.
Ich würde hinzufügen: (3) Der Linker kann Funktionen in einer cacheeffizienteren Gruppierung anordnen, so dass teure Fehlschläge auf Cache-Ebene minimiert werden. Dies kann sich auch besonders auf die Startzeit von Anwendungen auswirken (basierend auf den Ergebnissen, die ich mit dem Sun C ++ - Compiler gesehen habe).
Und vergessen Sie nicht, dass mit DLLs keine Eliminierung von totem Code durchgeführt werden kann. Je nach Sprache ist der DLL-Code möglicherweise auch nicht optimal. Virtuelle Funktionen sind immer virtuell, da der Compiler nicht weiß, ob ein Client sie überschreibt.
Wenn aus diesen Gründen keine DLLs wirklich benötigt werden, verwenden Sie einfach die statische Kompilierung.
BEARBEITEN (um den Kommentar zu beantworten, vom Benutzer unterstrichen)
Hier ist eine gute Ressource zum positionsunabhängigen Codeproblem http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/
Wie bereits erläutert, verfügt x86 nicht über AFAIK für andere als 15-Bit-Sprungbereiche und nicht für bedingungslose Sprünge und Aufrufe. Aus diesem Grund waren Funktionen (von Generatoren) mit mehr als 32 KB schon immer ein Problem und benötigten eingebettete Trampoline.
Unter gängigen x86-Betriebssystemen wie Linux müssen Sie sich jedoch nicht darum kümmern, ob die .so / DLL-Datei nicht mit dem
gcc
Switch generiert wird-fpic
(wodurch die Verwendung der indirekten Sprungtabellen erzwungen wird). Wenn Sie dies nicht tun, wird der Code einfach so repariert, wie ein normaler Linker ihn verschieben würde. Dabei wird das Codesegment jedoch nicht gemeinsam genutzt, und es muss eine vollständige Zuordnung des Codes von der Festplatte zum Speicher und Berühren des Codes vorgenommen werden, bevor er verwendet werden kann (Leeren der meisten Caches, Aufrufen von TLBs) usw. Es gab eine Zeit wenn dies als langsam angesehen wurde.Sie hätten also keinen Nutzen mehr.
Ich kann mich nicht erinnern, welches Betriebssystem (Solaris oder FreeBSD) mir Probleme mit meinem Unix-Build-System bereitet hat, weil ich dies einfach nicht getan habe und mich gefragt habe, warum es abgestürzt ist, bis ich mich beworben
-fPIC
habegcc
.quelle
Die dynamische Verknüpfung ist der einzige praktische Weg, um einige Lizenzanforderungen wie die LGPL zu erfüllen .
quelle
Ich stimme den Punkten zu, die dnmckee erwähnt, plus:
quelle
Ein Grund für einen statisch verknüpften Build besteht darin, zu überprüfen, ob die ausführbare Datei vollständig geschlossen ist, dh ob alle Symbolreferenzen korrekt aufgelöst werden.
Als Teil eines großen Systems, das mithilfe kontinuierlicher Integration erstellt und getestet wurde, wurden die nächtlichen Regressionstests mit einer statisch verknüpften Version der ausführbaren Dateien ausgeführt. Gelegentlich wurde festgestellt, dass ein Symbol nicht aufgelöst wurde und die statische Verknüpfung fehlschlug, obwohl die dynamisch verknüpfte ausführbare Datei erfolgreich verknüpft wurde.
Dies trat normalerweise auf, wenn Symbole, die tief in den gemeinsam genutzten Bibliotheken saßen, einen falsch geschriebenen Namen hatten und daher nicht statisch verknüpft wurden. Der dynamische Linker löst nicht alle Symbole vollständig auf, unabhängig von der Verwendung der Tiefen- oder Breitenevaluierung. Sie können also eine dynamisch verknüpfte ausführbare Datei erstellen, die nicht vollständig geschlossen ist.
quelle
1 / Ich war an Projekten beteiligt, bei denen die dynamische Verknüpfung mit der statischen Verknüpfung verglichen wurde und der Unterschied nicht klein genug war, um zur dynamischen Verknüpfung zu wechseln (ich war nicht Teil des Tests, ich kenne nur die Schlussfolgerung).
2 / Dynamische Verknüpfung ist häufig mit PIC verbunden (Positionsunabhängiger Code, Code, der abhängig von der Adresse, unter der er geladen wird, nicht geändert werden muss). Abhängig von der Architektur kann PIC eine weitere Verlangsamung mit sich bringen, ist jedoch erforderlich, um die gemeinsame Nutzung einer dynamisch verknüpften Bibliothek zwischen zwei ausführbaren Dateien (und sogar zwei Prozessen derselben ausführbaren Datei, wenn das Betriebssystem die Randomisierung der Ladeadresse als Sicherheitsmaßnahme verwendet) nutzen zu können. Ich bin nicht sicher, ob alle Betriebssysteme die beiden Konzepte trennen können, aber Solaris und Linux und ISTR auch HP-UX.
3 / Ich war an anderen Projekten beteiligt, bei denen dynamische Verknüpfungen für die Funktion "Einfacher Patch" verwendet wurden. Aber dieser "einfache Patch" macht die Verteilung von kleinen Fixes ein wenig einfacher und von komplizierten zu einem Alptraum für die Versionierung. Wir mussten oft alles pushen und Probleme beim Kunden nachverfolgen, weil die falsche Version ein Token war.
Mein Fazit ist, dass ich statische Verknüpfungen verwendet habe, mit Ausnahme von:
für Dinge wie Plugins, die von dynamischer Verknüpfung abhängen
wenn die Freigabe wichtig ist (große Bibliotheken, die von mehreren Prozessen gleichzeitig verwendet werden, wie C / C ++ - Laufzeit, GUI-Bibliotheken, ... die häufig unabhängig verwaltet werden und für die der ABI streng definiert ist)
Wenn man den "einfachen Patch" verwenden möchte, würde ich argumentieren, dass die Bibliotheken wie die großen Bibliotheken oben verwaltet werden müssen: Sie müssen nahezu unabhängig von einem definierten ABI sein, der nicht durch Fixes geändert werden darf.
quelle
Hier wird ausführlich über gemeinsam genutzte Bibliotheken unter Linux und die Auswirkungen auf die Leistung gesprochen.
quelle
Auf Unix-ähnlichen Systemen kann die dynamische Verknüpfung es 'root' schwer machen, eine Anwendung mit den gemeinsam genutzten Bibliotheken zu verwenden, die an abgelegenen Orten installiert sind. Dies liegt daran, dass der dynamische Linker LD_LIBRARY_PATH oder dessen Äquivalent für Prozesse mit Root-Rechten im Allgemeinen nicht berücksichtigt. Manchmal spart eine statische Verknüpfung den Tag.
Alternativ muss der Installationsprozess die Bibliotheken lokalisieren. Dies kann jedoch dazu führen, dass mehrere Versionen der Software nicht gleichzeitig auf dem Computer vorhanden sind.
quelle
LD_LIBRARY_PATH
ist nicht gerade ein Hindernis für die Verwendung gemeinsam genutzter Bibliotheken, zumindest nicht in GNU / Linux. Wenn Sie beispielsweise die gemeinsam genutzten Bibliotheken../lib/
relativ zur Programmdatei im Verzeichnis ablegen, gibt die Linker-Option mit der GNU-Toolkette-rpath $ORIGIN/../lib
an, dass die Bibliothek von diesem relativen Speicherort aus durchsucht werden soll. Sie können die Anwendung dann problemlos zusammen mit allen zugehörigen gemeinsam genutzten Bibliotheken verschieben. Mit diesem Trick ist es auch kein Problem, mehrere Versionen der Anwendung und der Bibliotheken zu haben (vorausgesetzt, sie sind verwandt, wenn nicht, könnten Sie symbolische Links verwenden)./etc/ld.so.conf
für diesen Fall bearbeiten .Es ist wirklich ziemlich einfach. Wenn Sie Ihren Quellcode ändern, möchten Sie 10 Minuten warten, bis er erstellt wurde, oder 20 Sekunden? Zwanzig Sekunden sind alles, was ich ertragen kann. Darüber hinaus hole ich entweder das Schwert heraus oder denke darüber nach, wie ich es mithilfe einer separaten Zusammenstellung und Verknüpfung wieder in die Komfortzone bringen kann.
quelle
Das beste Beispiel für eine dynamische Verknüpfung ist, wenn die Bibliothek von der verwendeten Hardware abhängig ist. In der Antike wurde die C-Mathematikbibliothek als dynamisch eingestuft, sodass jede Plattform alle Prozessorfunktionen nutzen kann, um sie zu optimieren.
Ein noch besseres Beispiel könnte OpenGL sein. OpenGl ist eine API, die von AMD und NVidia unterschiedlich implementiert wird. Und Sie können eine NVidia-Implementierung nicht auf einer AMD-Karte verwenden, da die Hardware unterschiedlich ist. Aus diesem Grund können Sie OpenGL nicht statisch mit Ihrem Programm verknüpfen. Hier wird die dynamische Verknüpfung verwendet, um die API für alle Plattformen zu optimieren.
quelle
Die dynamische Verknüpfung benötigt zusätzliche Zeit, damit das Betriebssystem die dynamische Bibliothek finden und laden kann. Mit der statischen Verknüpfung ist alles zusammen und es ist eine einmalige Belastung des Speichers.
Siehe auch DLL Hell . Dies ist das Szenario, in dem die vom Betriebssystem geladene DLL nicht die mit Ihrer Anwendung gelieferte oder die von Ihrer Anwendung erwartete Version ist.
quelle
Ein weiteres Problem, das noch nicht besprochen wurde, ist das Beheben von Fehlern in der Bibliothek.
Bei der statischen Verknüpfung müssen Sie nicht nur die Bibliothek neu erstellen, sondern auch die ausführbare Datei neu verknüpfen und neu verteilen. Wenn die Bibliothek nur in einer ausführbaren Datei verwendet wird, ist dies möglicherweise kein Problem. Aber je mehr ausführbare Dateien neu verknüpft und neu verteilt werden müssen, desto größer ist der Schmerz.
Mit der dynamischen Verknüpfung erstellen Sie einfach die dynamische Bibliothek neu und verteilen sie, und fertig.
quelle
Durch statische Verknüpfung erhalten Sie nur eine einzige Exe. Um eine Änderung vorzunehmen, müssen Sie Ihr gesamtes Programm neu kompilieren. Während Sie bei der dynamischen Verknüpfung nur Änderungen an der DLL vornehmen müssen und wenn Sie Ihre Exe ausführen, werden die Änderungen zur Laufzeit übernommen. Es ist einfacher, Aktualisierungen und Fehlerbehebungen durch dynamische Verknüpfung bereitzustellen (z. B. Windows).
quelle
Es gibt eine große und zunehmende Anzahl von Systemen, bei denen eine extreme statische Verknüpfung enorme positive Auswirkungen auf Anwendungen und Systemleistung haben kann.
Ich beziehe mich auf sogenannte "eingebettete Systeme", von denen viele zunehmend Allzweckbetriebssysteme verwenden, und diese Systeme werden für alles Mögliche verwendet.
Ein äußerst häufiges Beispiel sind Geräte, die GNU / Linux-Systeme mit Busybox verwenden . Ich habe dies mit NetBSD auf die Spitze getrieben, indem ich ein bootfähiges i386 (32-Bit) -System-Image erstellt habe, das sowohl einen Kernel als auch dessen Root-Dateisystem enthält. Letzteres enthält eine einzelne statisch verknüpfte (by
crunchgen
) Binärdatei mit Hardlinks zu Alle Programme, die selbst alle (zuletzt 274) Standard-Systemprogramme mit vollem Funktionsumfang enthalten (die meisten außer der Toolchain), und es sind weniger als 20 Megabyte groß (und laufen wahrscheinlich sehr komfortabel in einem System mit nur 64 MB Speicher (selbst wenn das Root-Dateisystem unkomprimiert und vollständig im RAM ist), obwohl ich keinen gefunden habe, der so klein ist, um ihn zu testen).In früheren Beiträgen wurde erwähnt, dass die Startzeit einer statisch verknüpften Binärdatei schneller ist (und viel schneller sein kann), dies ist jedoch nur ein Teil des Bildes, insbesondere wenn der gesamte Objektcode mit demselben verknüpft ist Datei, und insbesondere, wenn das Betriebssystem das Anforderungs-Paging von Code direkt aus der ausführbaren Datei unterstützt. In diesem idealen Szenario ist die Startzeit von Programmen buchstäblich vernachlässigbar, da sich fast alle Codeseiten bereits im Speicher befinden und von der Shell (und und) verwendet werden
init
allen anderen möglicherweise laufenden Hintergrundprozessen) verwendet werden, selbst wenn das angeforderte Programm dies nicht getan hat wurde seit dem Start ausgeführt, da möglicherweise nur eine Seite Speicher geladen werden muss, um die Laufzeitanforderungen des Programms zu erfüllen.Das ist jedoch immer noch nicht die ganze Geschichte. Normalerweise erstelle und verwende ich auch die NetBSD-Betriebssysteminstallationen für meine vollständigen Entwicklungssysteme, indem ich alle Binärdateien statisch verknüpfe. Obwohl dies enorm viel mehr Speicherplatz beansprucht (~ 6,6 GB insgesamt für x86_64 mit allem, einschließlich Toolchain und statisch verknüpftem X11) (insbesondere wenn eine vollständige Debug-Symboltabelle für alle Programme verfügbar bleibt, weitere ~ 2,5 GB), bleibt das Ergebnis bestehen Läuft insgesamt schneller und benötigt für einige Aufgaben sogar weniger Speicher als ein typisches dynamisch verknüpftes System, das angeblich Bibliothekscodeseiten gemeinsam nutzt. Festplatte ist billig (sogar schnelle Festplatte), und Speicher zum Zwischenspeichern häufig verwendeter Festplattendateien ist ebenfalls relativ billig, CPU-Zyklen jedoch nicht
ld.so
für jeden Prozess Start Kosten , die beginnen , jederDie Startzeit dauert Stunden und Stunden von CPU-Zyklen, bis Aufgaben ausgeführt werden, bei denen viele Prozesse gestartet werden müssen, insbesondere wenn dieselben Programme immer wieder verwendet werden, z. B. Compiler auf einem Entwicklungssystem. Statisch verknüpfte Toolchain-Programme können die Build-Zeiten für mehrere Systeme mit mehreren Architekturen für meine Systeme um Stunden reduzieren . Ich habe die Toolchain noch nicht in meine Singlecrunchgen
-Ed-Binärdatei integriert, aber ich vermute, dass in diesem Fall aufgrund des Gewinns für den CPU-Cache mehr Stunden Bauzeit eingespart werden.quelle
Die statische Verknüpfung umfasst die Dateien, die das Programm in einer einzelnen ausführbaren Datei benötigt.
Dynamische Verknüpfung ist das, was Sie als üblich betrachten würden. Sie macht eine ausführbare Datei, für die weiterhin DLLs erforderlich sind und die sich im selben Verzeichnis befinden müssen (oder die DLLs könnten sich im Systemordner befinden).
(DLL = Dynamic Link Library)
Dynamisch verknüpfte ausführbare Dateien werden schneller kompiliert und sind nicht so ressourcenintensiv.
quelle
Static linking
ist ein Prozess in der Kompilierungszeit, bei dem ein verknüpfter Inhalt in die primäre Binärdatei kopiert wird und zu einer einzelnen Binärdatei wird.Nachteile:
Dynamic linking
ist ein Prozess zur Laufzeit, wenn ein verknüpfter Inhalt geladen wird. Diese Technik ermöglicht:ABI
Stabilität erhöht. [Info]Nachteile:
quelle