Kleinere Behandlung von C ++ - Ausnahmen unter x64

In Visual Studio 2019 Preview 3 wird eine neue Funktion eingeführt, mit der die Binärgröße der C ++ - Ausnahmebehandlung (try / catch und automatische Destruktoren) unter x64 reduziert werden kann. Mit dem Namen FH4 (für __CxxFrameHandler4 siehe unten) habe ich eine neue Formatierung und Verarbeitung für Daten entwickelt, die für die Behandlung von C ++ - Ausnahmen verwendet werden und ~ 60% kleiner sind als die vorhandene Implementierung, was zu einer binären Reduzierung von bis zu 20% für Programme mit starker C ++ - Nutzung führt Ausnahmebehandlung.


Dieser Artikel im Blog .

Wie schalte ich das ein?


FH4 ist derzeit standardmäßig deaktiviert, da die für Store-Anwendungen erforderlichen Laufzeitänderungen nicht in die aktuelle Version gelangen konnten. Um FH4 für Nicht-Store-Anwendungen zu aktivieren, übergeben Sie das undokumentierte Flag "/ d2FH4" an den MSVC-Compiler in Visual Studio 2019 Preview 3 und höher.


Wir planen, FH4 standardmäßig zu aktivieren, sobald die Store-Laufzeit aktualisiert wurde. Wir hoffen, dies in Visual Studio 2019 Update 1 zu tun und diesen Beitrag zu aktualisieren, von dem wir mehr wissen.


Werkzeugänderungen


Bei jeder Installation von Visual Studio 2019 Preview 3 und höher werden die Änderungen im Compiler und in der C ++ - Laufzeit vorgenommen, um FH4 zu unterstützen. Die Compileränderungen existieren intern unter dem oben genannten Flag "/ d2FH4". Die C ++ - Laufzeit enthält eine neue DLL namens vcruntime140_1.dll, die von VCRedist automatisch installiert wird. Dies ist erforderlich, um den neuen Ausnahmebehandler __CxxFrameHandler4 verfügbar zu machen, der die ältere Routine __CxxFrameHandler3 ersetzt. Statische Verknüpfung und app-lokale Bereitstellung der neuen C ++ - Laufzeit werden ebenfalls unterstützt.


Nun zu den lustigen Sachen! Der Rest dieses Beitrags behandelt die internen Ergebnisse des Testens von FH4 unter Windows, Office und SQL, gefolgt von detaillierteren technischen Details hinter dieser neuen Technologie.


Motivation und Ergebnisse


Vor ungefähr einem Jahr kamen unsere Partner des C ++ / WinR T- Projekts mit einer Herausforderung zum Microsoft C ++ - Team: Um wie viel könnten wir die Binärgröße der C ++ - Ausnahmebehandlung für Programme reduzieren, die es häufig verwenden?


Im Zusammenhang mit einem Programm, das C ++ / WinRT verwendet , wurde auf eine Windows-Komponente Microsoft.UI.Xaml.dll verwiesen, die aufgrund der Behandlung von C ++ - Ausnahmen einen großen binären Footprint aufweist. Ich bestätigte, dass dies tatsächlich der Fall war, und erzeugte die Aufschlüsselung der Binärgröße mit dem vorhandenen __CxxFrameHandler3 (siehe unten). Die Prozentsätze auf der rechten Seite des Diagramms sind Prozent der gesamten Binärgröße, die von bestimmten Metadatentabellen und umrissenem Code belegt wird.


Größenaufschlüsselung von Microsoft.UI.Xaml.dll mit __CxxFrameHandler3


In diesem Beitrag werde ich nicht erläutern, was die spezifischen Strukturen auf der rechten Seite des Diagramms bewirken (weitere Informationen finden Sie in James McNellis 'Vortrag darüber, wie das Abwickeln von Stapeln unter Windows funktioniert ). Bei Betrachtung der gesamten Metadaten und des gesamten Codes wurden jedoch satte 26,4% der Binärgröße für die C ++ - Ausnahmebehandlung verwendet. Dies ist eine enorme Menge an Speicherplatz und hat die Einführung von C ++ / WinRT behindert.


Wir haben in der Vergangenheit Änderungen vorgenommen, um die Größe der C ++ - Ausnahmebehandlung im Compiler zu verringern, ohne die Laufzeit zu ändern. Dies umfasst das Löschen von Metadaten für Codebereiche, die keine logisch identischen Zustände auslösen und falten können. Wir hatten jedoch das Ende dessen erreicht, was wir nur im Compiler tun konnten, und waren nicht in der Lage, etwas so Großes signifikant einzudellen. Die Analyse ergab, dass signifikante Gewinne zu verzeichnen waren, jedoch grundlegende Änderungen an Daten, Code und Laufzeit erforderlich waren. Also machten wir weiter und machten sie.


Mit dem neuen __CxxFrameHandler4 und den zugehörigen Metadaten lautet die Größenverteilung für Microsoft.UI.XAML.dll jetzt wie folgt:


Größenaufschlüsselung von Microsoft.UI.Xaml.dll mit __CxxFrameHandler4


Die von der C ++ - Ausnahmebehandlung verwendete Binärgröße sinkt um 64%, was zu einer Verringerung der Gesamtbinärgröße um 18,6% für diese Binärdatei führt. Jede Art von Struktur schrumpfte um erstaunliche Grade:

Eh Daten__CxxFrameHandler3 Größe (Bytes)__CxxFrameHandler4 Größe (Bytes)% Größenreduzierung
Pdata-Einträge147.864118.26020,0%
Codes abwickeln224,28492,81058,6%
Funktionsinfos255,44027.75589,1%
IP2State-Karten186.94445.09875,9%
Karten abwickeln80.95269.75713,8%
Catch-Handler-Karten52.0606,14788,2%
Probieren Sie Karten aus51.9605,19690,0%
Dtor funclets54.57045.73916,2%
Fang Funclets102.4004,30195,8%
Insgesamt1.156.474415.06364,1%

Durch den Wechsel zu __CxxFrameHandler4 wurde die Gesamtgröße von Microsoft.UI.Xaml.dll von 4,4 MB auf 3,6 MB gesenkt.


Das Testen von FH4 auf einem repräsentativen Satz von Office-Binärdateien zeigt eine Größenreduzierung von ~ 10% bei DLLs, die häufig Ausnahmen verwenden. Selbst in Word und Excel, die die Verwendung von Ausnahmen minimieren sollen, wird die Binärgröße immer noch erheblich reduziert.

BinärAlte Größe (MB)Neue Größe (MB)% GrößenreduzierungBeschreibung
chart.dll17.2715.1012,6%Unterstützung für die Interaktion mit Diagrammen und Grafiken
Csi.dll9.788.6611,4%Unterstützung für die Arbeit mit Dateien, die in der Cloud gespeichert sind
Mso20Win32Client.dll6.075.4111,0%Gemeinsamer Code, der von allen Office-Apps gemeinsam genutzt wird
Mso30Win32Client.dll8.117.309,9%Gemeinsamer Code, der von allen Office-Apps gemeinsam genutzt wird
oart.dll18.2116.2011,0%Grafikfunktionen, die von Office-Apps gemeinsam genutzt werden
wwlib.dll42.1541.122,5%Die Hauptbinärdatei von Microsoft Word
excel.exe52,8650,294,9%Die Hauptbinärdatei von Microsoft Excel

Das Testen von FH4 auf SQL-Kernbinärdateien zeigt eine Reduzierung der Größe um 4 bis 21%, hauptsächlich aufgrund der im nächsten Abschnitt beschriebenen Metadatenkomprimierung:

BinärAlte Größe (MB)Neue Größe (MB)% GrößenreduzierungBeschreibung
sqllang.dll47.1244.335,9%Top-Level-Services: Sprachparser, Binder, Optimierer und Ausführungsmodul
sqlmin.dll48.1745,834,8%Low-Level-Services: Transaktionen und Speicher-Engine
qds.dll1.421,336,3%Abfragespeicherfunktionalität
SqlDK.dll3.193.054,4%SQL OS-Abstraktionen: Speicher, Threads, Zeitplanung usw.
autoadmin.dll1,771,647,3%Datenbank-Tuning-Advisor-Logik
xedetours.dll0,450,3621,6%Flugdatenschreiber für Anfragen

Die Technik


Bei der Analyse der Ursachen für die C ++ - Ausnahmebehandlungsdaten in Microsoft.UI.Xaml.dll habe ich zwei Hauptschuldige gefunden:


  1. Die Datenstrukturen selbst sind groß: Metadatentabellen hatten eine feste Größe mit Feldern mit bildrelativen Offsets und Ganzzahlen, die jeweils vier Bytes lang sind. Eine Funktion mit einem einzelnen try / catch und einem oder zwei automatischen Destruktoren hatte über 100 Byte Metadaten.
  2. Die generierten Datenstrukturen und der generierte Code konnten nicht zusammengeführt werden. Die Metadatentabellen enthielten bildbezogene Offsets, die das Falten von COMDAT verhinderten (der Prozess, bei dem der Linker identische Daten zusammenfügen kann, um Platz zu sparen), sofern die von ihnen dargestellten Funktionen nicht identisch waren. Darüber hinaus konnten Catch-Funclets (umrissener Code aus den Catch-Blöcken des Programms) nicht gefaltet werden, selbst wenn sie codeidentisch waren, da ihre Metadaten in ihren Eltern enthalten sind.

Um diese Probleme zu beheben, strukturiert FH4 die Metadaten und den Code so um, dass:


  1. Frühere Werte mit fester Größe wurden mithilfe einer Ganzzahlcodierung mit variabler Länge komprimiert, die> 90% der Metadatenfelder von vier Bytes auf eins reduziert. Metadatentabellen haben jetzt auch eine variable Länge mit einem Header, der angibt, ob bestimmte Felder vorhanden sind, um Platz beim Ausgeben leerer Felder zu sparen.
  2. Alle bildrelativen Offsets, die funktionsrelativ sein können, wurden funktionsrelativ gemacht. Dies ermöglicht die COMDAT-Faltung zwischen Metadaten verschiedener Funktionen mit ähnlichen Merkmalen (Think Template-Instanziierungen) und ermöglicht die Komprimierung dieser Werte. Catch-Funclets wurden so umgestaltet, dass ihre Metadaten nicht mehr in den Daten ihrer Eltern gespeichert sind, sodass alle codeidentischen Catch-Funclets jetzt zu einer einzigen Kopie in der Binärdatei gefaltet werden können.

Schauen wir uns zur Veranschaulichung die ursprüngliche Definition für die Metadatentabelle "Funktionsinfo" an, die für __CxxFrameHandler3 verwendet wird. Dies ist die Starttabelle für die Laufzeit bei der Verarbeitung von EH und zeigt auf die anderen Metadatentabellen. Dieser Code ist in jeder VS-Installation öffentlich verfügbar. Suchen Sie nach <VS-Installationspfad> \ VC \ Tools \ MSVC \ <Version> \ include \ ehdata.h:


typedef const struct _s_FuncInfo { unsigned int magicNumber:29; // Identifies version of compiler unsigned int bbtFlags:3; // flags that may be set by BBT processing __ehstate_t maxState; // Highest state number plus one (thus // number of entries in unwind map) int dispUnwindMap; // Image relative offset of the unwind map unsigned int nTryBlocks; // Number of 'try' blocks in this function int dispTryBlockMap; // Image relative offset of the handler map unsigned int nIPMapEntries; // # entries in the IP-to-state map. NYI (reserved) int dispIPtoStateMap; // Image relative offset of the IP to state map int dispUwindHelp; // Displacement of unwind helpers from base int dispESTypeList; // Image relative list of types for exception specifications int EHFlags; // Flags for some features. } FuncInfo; 

Diese Struktur hat eine feste Größe und enthält 10 Felder mit einer Länge von jeweils 4 Bytes. Dies bedeutet, dass jede Funktion, die standardmäßig eine C ++ - Ausnahmebehandlung benötigt, 40 Byte Metadaten enthält.


Nun zur neuen Datenstruktur (<VS Installationspfad> \ VC \ Tools \ MSVC \ <Version> \ include \ ehdata4_export.h):


 struct FuncInfoHeader { union { struct { uint8_t isCatch : 1; // 1 if this represents a catch funclet, 0 otherwise uint8_t isSeparated : 1; // 1 if this function has separated code segments, 0 otherwise uint8_t BBT : 1; // Flags set by Basic Block Transformations uint8_t UnwindMap : 1; // Existence of Unwind Map RVA uint8_t TryBlockMap : 1; // Existence of Try Block Map RVA uint8_t EHs : 1; // EHs flag set uint8_t NoExcept : 1; // NoExcept flag set uint8_t reserved : 1; }; uint8_t value; }; }; struct FuncInfo4 { FuncInfoHeader header; uint32_t bbtFlags; // flags that may be set by BBT processing int32_t dispUnwindMap; // Image relative offset of the unwind map int32_t dispTryBlockMap; // Image relative offset of the handler map int32_t dispIPtoStateMap; // Image relative offset of the IP to state map uint32_t dispFrame; // displacement of address of function frame wrt establisher frame, only used for catch funclets }; 

Beachten Sie, dass:


  1. Die magische Zahl wurde entfernt und die Ausgabe von 0x19930522 wird jedes Mal zum Problem, wenn ein Programm Tausende dieser Einträge enthält.
  2. EHFlags wurde in den Header verschoben, während dispESTypeList aufgrund der fehlenden Unterstützung dynamischer Ausnahmespezifikationen in C ++ 17 aus dem Programm genommen wurde. Der Compiler verwendet standardmäßig den älteren __CxxFrameHandler3, wenn dynamische Ausnahmespezifikationen verwendet werden.
  3. Die Längen der anderen Tabellen werden nicht mehr in „Funktionsinfo 4“ gespeichert. Auf diese Weise kann die COMDAT-Faltung mehr von den Tabellen, auf die gezeigt wird, falten, selbst wenn die Tabelle „Funktionsinfo 4“ selbst nicht gefaltet werden kann.
  4. (Nicht explizit gezeigt) Die Felder dispFrame und bbtFlags sind jetzt Ganzzahlen variabler Länge. Die übergeordnete Darstellung belässt es als uint32_t für eine einfache Verarbeitung.
  5. bbtFlags, dispUnwindMap, dispTryBlockMap und dispFrame können abhängig von den im Header festgelegten Feldern weggelassen werden.

Unter Berücksichtigung all dessen beträgt die durchschnittliche Größe der neuen Struktur „Funktionsinfo 4“ jetzt 13 Byte (1-Byte-Header + drei 4-Byte-Bild-relative Offsets zu anderen Tabellen), was sich noch weiter verkleinern lässt, wenn einige Tabellen nicht benötigt werden. Die Längen der Tabellen wurden verschoben, aber diese Werte sind jetzt komprimiert und 90% davon in Microsoft.UI.Xaml.dll passen in ein einzelnes Byte. Alles in allem bedeutet dies, dass die durchschnittliche Größe für die Darstellung der gleichen Funktionsdaten im neuen Handler 16 Byte im Vergleich zu den vorherigen 40 Byte beträgt - eine dramatische Verbesserung!


Schauen wir uns zum Falten die Anzahl der einzigartigen Tische und Funclets mit dem alten und dem neuen Handler an:

Eh DatenZählen Sie in __CxxFrameHandler3Zählen Sie in __CxxFrameHandler4% Reduktion
Pdata-Einträge12,3229.85520,0%
Funktionsinfos6,3862,74757,0%
IP2State-Karteneinträge6,3632,14866,2%
Karteneinträge abwickeln1,4871.4641,5%
Catch-Handler-Karten2,60360176,9%
Probieren Sie Karten aus2,59864875,1%
Dtor funclets2.3011,52733,6%
Fang Funclets2,6038496,8%
Insgesamt36.66319.07448,0%

Die Anzahl der eindeutigen EH-Dateneinträge sinkt um 48% , da zusätzliche Faltmöglichkeiten geschaffen werden, indem RVAs entfernt und Fangfunclets neu gestaltet werden. Ich möchte speziell die Anzahl der grün kursiv gedruckten Catch-Funclets nennen: Sie sinkt von 2,603 ​​auf nur 84. Dies ist eine Folge der C ++ / WinRT-Übersetzung von HRESULTs in C ++ - Ausnahmen, die viele codeidentische Catch-Funclets generiert, die jetzt verfügbar sind gefaltet. Sicherlich liegt ein Rückgang dieser Größenordnung am oberen Ende der Ergebnisse, zeigt jedoch, welche potenziellen Einsparungen durch Größenfaltung erzielt werden können, wenn die Datenstrukturen unter Berücksichtigung dieser Kriterien entworfen werden.


Leistung


Mit dem Entwurf, der die Komprimierung einführt und die Laufzeitausführung ändert, gab es Bedenken, dass die Leistung bei der Ausnahmebehandlung beeinträchtigt wird. Die Auswirkungen sind jedoch positiv : Die Leistung bei der Ausnahmebehandlung verbessert sich mit __CxxFrameHandler4 im Gegensatz zu __CxxFrameHandler3. Ich habe den Durchsatz mit einem Benchmark- Programm getestet, das 100 Stack-Frames mit jeweils einem Try / Catch und 3 zu zerstörenden automatischen Objekten abwickelt. Dies wurde 50.000 Mal ausgeführt, um die Ausführungszeit des Profils zu bestimmen. Dies führte zu Gesamtausführungszeiten von:

__CxxFrameHandler3__CxxFrameHandler4
Ausführungszeit4,84 s4,25s

Die Profilerstellung ergab, dass die Dekomprimierung zwar zusätzliche Verarbeitungszeit bedeutet, die Kosten jedoch durch weniger Speicher für den threadlokalen Speicher im neuen Laufzeitdesign aufgewogen werden.


Zukunftspläne


Wie im Titel erwähnt, ist FH4 derzeit nur für x64-Binärdateien aktiviert. Die beschriebenen Techniken sind jedoch auf ARM32 / ARM64 und in geringerem Umfang auf x86 erweiterbar. Wir suchen derzeit nach guten Beispielen (wie Microsoft.UI.Xaml.dll), um die Ausweitung dieser Technologie auf andere Plattformen zu motivieren. Wenn Sie der Meinung sind, dass Sie einen guten Anwendungsfall haben, lassen Sie es uns wissen!


Der Prozess der Integration der Laufzeitänderungen für Store-Anwendungen zur Unterstützung von FH4 ist im Gange. Sobald dies erledigt ist, wird der neue Handler standardmäßig aktiviert, sodass jeder diese Einsparungen bei der Binärgröße ohne zusätzlichen Aufwand erzielen kann.


Schlussbemerkungen


Für alle, die glauben, dass ihre x64-Binärdateien etwas reduziert werden könnten: Probieren Sie noch heute FH4 (über '/ d2FH4') aus! Wir sind gespannt, welche Einsparungen dies jetzt bringen kann, da diese Funktion nicht mehr verfügbar ist. Wenn Sie auf Probleme stoßen , teilen Sie uns dies bitte in den Kommentaren unten, per E-Mail ( visualcpp@microsoft.com ) oder über die Entwickler-Community mit . Sie finden uns auch auf Twitter ( @VisualC ).


Vielen Dank an Kenny Kerr, der uns an Microsoft.UI.Xaml.dll weitergeleitet hat, an Ravi Pinjala, der die Zahlen in Office gesammelt hat, und an Robert Roessler, der dies in SQL ausprobiert hat.

Source: https://habr.com/ru/post/de442996/


All Articles