c++ - symbolische - windows symlink tool




Statische Verknüpfung vs. dynamische Verknüpfung (10)

Gibt es überzeugende Leistungsgründe, in bestimmten Situationen statische Verknüpfungen über dynamische Verknüpfungen oder umgekehrt zu wählen? Ich habe das Folgende 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 Verknüpfung und dynamischer Verknüpfung ist normalerweise vernachlässigbar.

2) (1) ist nicht wahr, wenn ein Profiling-Compiler verwendet wird, der Profildaten verwendet, um Programmhotpfade zu optimieren, da der Compiler bei der statischen Verknüpfung sowohl Ihren Code als auch den Bibliothekscode optimieren kann. Mit dynamischer Verknüpfung kann nur Ihr Code optimiert werden. Wenn die meiste Zeit mit dem Ausführen von Bibliothekscode verbracht wird, kann dies einen großen Unterschied machen. Ansonsten gilt (1) immer noch.


1 / Ich war an Projekten beteiligt, bei denen dynamisches Linking vs. statisches Linking benchmarked wurde und der Unterschied nicht klein genug war, um zu dynamischem Linking zu wechseln (ich war nicht Teil des Tests, ich kenne nur die Schlussfolgerung)

2 / Dynamic Linking ist oft mit PIC (Position Independent Code, Code, der nicht abhängig von der Adresse geändert werden muss, an der er geladen wird). Abhängig von der Architektur kann PIC eine weitere Verlangsamung mit sich bringen, wird aber benötigt, um eine dynamisch verknüpfte Bibliothek zwischen zwei ausführbaren Dateien zu teilen (und sogar zwei Prozesse derselben ausführbaren Datei, wenn das Betriebssystem die Randomisierung der Ladeadresse als Sicherheitsmaßnahme verwendet). Ich bin mir nicht sicher, ob alle Betriebssysteme es erlauben, die beiden Konzepte zu trennen, aber Solaris und Linux tun dies ebenso wie ISTR, was auch HP-UX tut.

3 / Ich war an anderen Projekten beteiligt, bei denen dynamische Verknüpfungen für die Funktion "Easy Patch" verwendet wurden. Aber dieser "easy patch" macht die Verteilung von kleinen Fixes ein wenig einfacher und von komplizierteren einen Versioning-Albtraum. Wir haben oft am Ende alles schieben müssen und Probleme auf Kundenseite verfolgen müssen, weil die falsche Version Token war.

Meine Schlussfolgerung ist, dass ich die statische Verknüpfung mit Ausnahme verwendet habe:

  • für Dinge wie Plugins, die auf dynamische Verknüpfungen angewiesen sind

  • wenn das Teilen wichtig ist (große Bibliotheken, die von mehreren Prozessen gleichzeitig benutzt werden, wie C / C ++ - Laufzeit, GUI-Bibliotheken, ... die oft unabhängig verwaltet werden und für die der ABI streng definiert ist)

Wenn man den "easy 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 mit einem definierten ABI sein, der nicht durch Fixes geändert werden darf.


1) basiert auf der Tatsache, dass beim Aufruf einer DLL-Funktion immer ein zusätzlicher indirekter Sprung verwendet wird. Heute ist das normalerweise vernachlässigbar. Innerhalb der DLL gibt es einen zusätzlichen Aufwand für i386-CPUs, da sie keinen positionsunabhängigen Code generieren können. Amd64 können Sprünge relativ zum Programmzähler sein, also ist dies eine große Verbesserung.

2) Das ist richtig. Mit Optimierungen, die durch Profiling gesteuert werden, können Sie normalerweise 10-15 Prozent Leistung erzielen. Jetzt, wo die CPU-Geschwindigkeit ihre Grenzen erreicht hat, könnte es sich lohnen, sie zu tun.

Ich würde hinzufügen: (3) der Linker kann Funktionen in einer Cache-effizienteren Gruppierung anordnen, so dass teure Cache-Level-Fehler minimiert werden. Es kann sich auch besonders auf die Startzeit von Anwendungen auswirken (basierend auf Ergebnissen, die ich mit dem Sun C ++ - Compiler gesehen habe)

Und vergiss nicht, dass mit DLL's keine tote Code-Eliminierung durchgeführt werden kann. Je nach Sprache ist der DLL-Code möglicherweise auch nicht optimal. Virtuelle Funktionen sind immer virtuell, weil der Compiler nicht weiß, ob ein Client ihn überschreibt.

Aus diesem Grund, wenn DLLs nicht wirklich benötigt werden, verwenden Sie einfach die statische Kompilierung.

EDIT (um den Kommentar zu beantworten, durch Benutzerunterstrich)

Hier ist eine gute Quelle über das positionsunabhängige Code-Problem http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/

Wie erläutert, hat x86 keine AFAIK für etwas anderes als 15-Bit-Sprungbereiche und nicht für unbedingte Sprünge und Aufrufe. Aus diesem Grund waren Funktionen (von Generatoren) mit mehr als 32K immer ein Problem und benötigten eingebettete Trampoline.

Aber auf populären x86-Betriebssystemen wie Linux müssen Sie sich nur nicht darum kümmern, wenn die SO / DLL-Datei nicht mit dem gcc Schalter -fpic (wodurch die Verwendung der indirekten Sprungtabellen -fpic ). Denn wenn Sie das nicht tun, wird der Code einfach so fixiert, wie ein normaler Linker ihn verschieben würde. Aber dabei macht es das Code-Segment nicht teilbar und es würde eine vollständige Zuordnung des Codes von der Festplatte in den Speicher und alles berühren bevor es verwendet werden kann (Leerung der meisten Caches, Treffen TLBs) usw. Es gab eine Zeit als das als langsam galt ... zu langsam.

Du hättest keinen Vorteil mehr.

Ich kann mich nicht erinnern, welches OS (Solaris oder FreeBSD) mir Probleme mit meinem Unix-Build-System -fPIC , weil ich das einfach nicht gemacht habe und mich gefragt habe, warum es abgestürzt ist, bis ich -fPIC auf gcc angewendet -fPIC .


Das beste Beispiel für eine dynamische Verknüpfung ist, wenn die Bibliothek von der verwendeten Hardware abhängig ist. In alten Zeiten wurde entschieden, dass die C-Math-Bibliothek dynamisch ist, so dass jede Plattform alle Prozessorfähigkeiten nutzen kann, um sie zu optimieren.

Ein noch besseres Beispiel könnte OpenGL sein. OpenGl ist eine API, die von AMD und NVidia anders implementiert wird. Und Sie können keine NVidia-Implementierung auf einer AMD-Karte verwenden, da die Hardware anders ist. Sie können OpenGL deshalb nicht statisch in Ihr Programm einbinden. Dynamic Linking wird hier verwendet, um die API für alle Plattformen zu optimieren.


Die dynamische Verknüpfung benötigt zusätzliche Zeit, damit das Betriebssystem die dynamische Bibliothek finden und laden kann. Mit statischer Verknüpfung ist alles zusammen und es ist eine einmalige Ladung in den Speicher.

Siehe auch DLL Hell . Dies ist das Szenario, in dem die DLL, die das Betriebssystem lädt, nicht die ist, die mit Ihrer Anwendung geliefert wurde, oder die Version, die Ihre Anwendung erwartet.


Dynamic Linking ist der einzige praktische Weg, um einige Lizenzanforderungen wie die LGPL zu erfüllen.


Ein Grund für einen statisch verknüpften Build besteht darin, zu überprüfen, ob die ausführbare Datei vollständig geschlossen ist, dh alle Symbolreferenzen korrekt aufgelöst wurden.

Als Teil eines großen Systems, das mit kontinuierlicher Integration erstellt und getestet wurde, wurden die nächtlichen Regressionstests mit einer statisch verknüpften Version der ausführbaren Dateien ausgeführt. Gelegentlich stellten wir fest, dass ein Symbol nicht aufgelöst werden konnte und die statische Verbindung fehlschlagen würde, obwohl die dynamisch verknüpfte ausführbare Datei erfolgreich verknüpft werden würde.

Dies trat normalerweise auf, wenn Symbole, die tief in den gemeinsamen Bibliotheken platziert waren, einen falschen Namen hatten und daher nicht statisch verlinkten. Der dynamische Linker löst nicht alle Symbole vollständig auf, unabhängig davon, ob Sie die erste oder die erste Breite der Auswertung verwenden. Sie können also mit einer dynamisch verknüpften ausführbaren Datei abschließen, die nicht vollständig geschlossen ist.


Es gibt eine große und zunehmende Anzahl von Systemen, bei denen eine extreme statische Verbindung einen enormen positiven Einfluss auf die Anwendungen und die Systemleistung haben kann.

Ich beziehe mich auf sogenannte "eingebettete Systeme", von denen viele jetzt zunehmend Allzweck-Betriebssysteme verwenden, und diese Systeme werden für alles Vorstellbare verwendet.

Ein sehr häufiges Beispiel sind Geräte, die GNU / Linux-Systeme mit Busybox . Ich habe dies mit NetBSD auf die Spitze NetBSD indem ich ein bootfähiges i386 (32-Bit) Systemabbild erstellt habe, das sowohl einen Kernel als auch sein Root-Dateisystem enthält, wobei letzteres eine einzige statisch verknüpfte (by crunchgen ) Binärdatei mit harten Links enthält zu allen Programmen, die selbst alle (gut zuletzt zählen 274) der Standard-Full-Feature-System-Programme (die meisten außer der Toolchain) enthält, und es ist weniger als 20 Megabyte groß (und läuft wahrscheinlich sehr komfortabel in einem System mit nur 64MB Speicher (selbst wenn das Root-Dateisystem unkomprimiert und vollständig im RAM ist), obwohl ich keine so kleine finden konnte, um es zu testen).

Es wurde in früheren Beiträgen erwähnt, dass die Startzeit von statisch verknüpften Binärdateien schneller ist (und viel schneller sein kann), aber das ist nur ein Teil des Bildes, insbesondere wenn der gesamte Objektcode mit demselben verknüpft ist Datei, und noch spezieller, wenn das Betriebssystem die direkte Auslagerung von Code direkt aus der ausführbaren Datei unterstützt. In diesem idealen Szenario ist die Startzeit von Programmen buchstäblich vernachlässigbar, da fast alle Seiten des Codes bereits im Speicher sind und von der Shell verwendet werden (und alle anderen Hintergrundprozesse, die gerade ausgeführt werden), selbst wenn das angeforderte Programm vorhanden ist wurde seit dem Booten nicht mehr ausgeführt, da möglicherweise nur eine Seite des Speichers geladen werden muss, um die Laufzeitanforderungen des Programms zu erfüllen.

Aber das ist immer noch nicht die ganze Geschichte. Ich baue und benutze normalerweise auch die NetBSD-Betriebssysteminstallationen für meine vollständigen Entwicklungssysteme, indem ich alle Binärdateien statisch verknüpfe. Obwohl dies eine enorme Menge mehr Speicherplatz benötigt (~ 6,6 GB insgesamt für x86_64 mit allem, einschließlich Toolchain und X11 statisch verknüpft) (vor allem, wenn man volle Debug Symboltabellen für alle Programme noch ~ 2,5GB verfügbar), das Ergebnis immer noch läuft insgesamt schneller und benötigt für einige Aufgaben sogar weniger Speicher als ein typisches dynamisch verknüpftes System, das Bibliotheksseiten teilen soll. Disketten sind billig (sogar schnelle Disketten), und der Speicher zum Zwischenspeichern häufig benutzter ld.so ist auch relativ billig, aber CPU-Zyklen sind es wirklich nicht, und das Bezahlen der ld.so für jeden Prozess, der jedes Mal startet, dauert Stunden und Stunden von CPU-Zyklen entfernt von Aufgaben, bei denen viele Prozesse gestartet werden müssen, insbesondere wenn dieselben Programme immer wieder verwendet werden, z. B. Compiler in einem Entwicklungssystem. Statisch verknüpfte Toolchain-Programme können die Build-Zeiten für Multi-Architektur für ganze Systeme um Stunden reduzieren . Ich muss die Toolchain erst noch in meine Single-Binärdatei crunchgen , aber ich vermute, wenn ich das tue, werden aufgrund des Gewinns für den CPU-Cache mehr Stunden an Build-Zeit eingespart.


Es ist ziemlich einfach, wirklich. Wenn Sie Ihren Quellcode ändern, möchten Sie 10 Minuten warten, bis er erstellt wird, oder 20 Sekunden? Zwanzig Sekunden ist alles, was ich ertragen kann. Darüber hinaus hole ich entweder das Schwert heraus oder denke darüber nach, wie ich es durch separate Kompilierung und Verknüpfung wieder in die Komfortzone bringen kann.


Statisches Linken gibt Ihnen nur eine einzige exe. Um eine Änderung vorzunehmen, müssen Sie Ihr gesamtes Programm neu kompilieren. Während beim dynamischen Verlinken nur die dll geändert werden muss und wenn Sie Ihre exe ausführen, werden die Änderungen zur Laufzeit übernommen. Es ist einfacher, Updates und Fehlerbehebungen durch dynamisches Verlinken (zB: windows) bereitzustellen.


This ausführlich über Shared Libraries zu Linux und Performance Impluction diskutiert.





dynamic-linking