Réduire la gestion des exceptions C ++ sur x64

Visual Studio 2019 Preview 3 introduit une nouvelle fonctionnalité pour réduire la taille binaire de la gestion des exceptions C ++ (try / catch et destructeurs automatiques) sur x64. Surnommé FH4 (pour __CxxFrameHandler4, voir ci-dessous), j'ai développé un nouveau formatage et traitement pour les données utilisées pour la gestion des exceptions C ++ qui est ~ 60% plus petit que l'implémentation existante, ce qui entraßne une réduction binaire globale jusqu'à 20% pour les programmes utilisant intensivement C ++ gestion des exceptions.


Cet article dans le blog .

Comment activer cette fonction?


FH4 est actuellement dĂ©sactivĂ© par dĂ©faut car les modifications d'exĂ©cution requises pour les applications du magasin n'ont pas pu ĂȘtre intĂ©grĂ©es dans la version actuelle. Pour activer FH4 pour les applications non Store, passez l'indicateur non documentĂ© «/ d2FH4» au compilateur MSVC dans Visual Studio 2019 Preview 3 et au-delĂ .


Nous prévoyons d'activer FH4 par défaut une fois le runtime Store mis à jour. Nous espérons le faire dans Visual Studio 2019 Update 1 et mettrons à jour ce post que nous en savons plus.


Modifications des outils


Toute installation de Visual Studio 2019 Preview 3 et au-delà aura les modifications dans le compilateur et le runtime C ++ pour prendre en charge FH4. Les modifications du compilateur existent en interne sous l'indicateur «/ d2FH4» susmentionné. Le runtime C ++ arbore une nouvelle DLL appelée vcruntime140_1.dll qui est automatiquement installée par VCRedist. Cela est nécessaire pour exposer le nouveau gestionnaire d'exceptions __CxxFrameHandler4 qui remplace l'ancienne routine __CxxFrameHandler3. La liaison statique et le déploiement local de l'application du nouveau runtime C ++ sont également pris en charge.


Maintenant sur les trucs amusants! Le reste de cet article couvrira les résultats internes de l'essai de FH4 sur Windows, Office et SQL, suivis de détails techniques plus approfondis derriÚre cette nouvelle technologie.


Motivation et résultats


Il y a environ un an, nos partenaires du projet C ++ / WinR T sont venus à l'équipe Microsoft C ++ avec un défi: dans quelle mesure pourrions-nous réduire la taille binaire de la gestion des exceptions C ++ pour les programmes qui l'utilisaient beaucoup?


Dans le contexte d'un programme utilisant C ++ / WinRT , ils nous ont indiqué un composant Windows Microsoft.UI.Xaml.dll qui était connu pour avoir une grande empreinte binaire en raison de la gestion des exceptions C ++. J'ai confirmé que c'était effectivement le cas et généré la répartition de la taille binaire avec le __CxxFrameHandler3 existant, illustré ci-dessous. Les pourcentages dans le cÎté droit du graphique sont des pourcentages de la taille binaire totale occupée par des tables de métadonnées spécifiques et le code décrit.


Répartition de la taille de Microsoft.UI.Xaml.dll à l'aide de __CxxFrameHandler3


Je ne discuterai pas dans cet article de ce que font les structures spécifiques sur le cÎté droit du graphique (voir la présentation de James McNellis sur le fonctionnement du déroulement de la pile sur Windows pour plus de détails). Cependant, en regardant le total des métadonnées et du code, un énorme 26,4% de la taille binaire a été utilisé par la gestion des exceptions C ++. C'est un espace énorme et entravait l'adoption de C ++ / WinRT.


Nous avons apporté des modifications dans le passé pour réduire la taille de la gestion des exceptions C ++ dans le compilateur sans modifier l'exécution. Cela inclut la suppression des métadonnées pour les régions de code qui ne peuvent pas lancer et replier des états logiquement identiques. Cependant, nous arrivions à la fin de ce que nous pouvions faire avec le compilateur et ne serions pas en mesure de faire une brÚche significative dans quelque chose d'aussi gros. L'analyse a montré qu'il y avait des gains importants à gagner mais nécessitait des changements fondamentaux dans les données, le code et l'exécution. Nous sommes donc allés de l'avant et nous les avons fait.


Avec le nouveau __CxxFrameHandler4 et les métadonnées qui l'accompagnent, la répartition de la taille de Microsoft.UI.XAML.dll est désormais la suivante:


Répartition de la taille de Microsoft.UI.Xaml.dll à l'aide de __CxxFrameHandler4


La taille binaire utilisée par la gestion des exceptions C ++ chute de 64%, ce qui entraßne une diminution globale de la taille binaire de 18,6% sur ce binaire. Chaque type de structure a diminué de taille par degrés stupéfiants:

Eh données__CxxFrameHandler3 Taille (octets)__CxxFrameHandler4 Taille (octets)% De réduction de taille
Entrées Pdata147 864118,26020,0%
Détendez les codes224,28492 81058,6%
Infos fonction255,44027 75589,1%
Cartes IP2State186 94445 09875,9%
Détendez-vous sur les cartes80,95269 75713,8%
Cartes des gestionnaires de captures52 0606 14788,2%
Essayez des cartes51 9605 19690,0%
Funtets Dtor54 57045,73916,2%
Attrapez des funclets102 4004,30195,8%
Total1,156,474415 06364,1%

Combiné, le passage à __CxxFrameHandler4 a fait chuter la taille globale de Microsoft.UI.Xaml.dll de 4,4 Mo à 3,6 Mo.


L'essai de FH4 sur un ensemble reprĂ©sentatif de binaires Office montre une rĂ©duction de taille de ~ 10% dans les DLL qui utilisent fortement les exceptions. MĂȘme dans Word et Excel, qui sont conçus pour minimiser l'utilisation des exceptions, il y a toujours une rĂ©duction significative de la taille binaire.

BinaireAncienne taille (Mo)Nouvelle taille (Mo)% De réduction de tailleLa description
chart.dll17,2715.1012,6%Prise en charge de l'interaction avec des tableaux et des graphiques
Csi.dll9,788.6611,4%Prise en charge de l'utilisation de fichiers stockés dans le cloud
Mso20Win32Client.dll6.075.4111,0%Code commun partagé entre toutes les applications Office
Mso30Win32Client.dll8.117h309,9%Code commun partagé entre toutes les applications Office
oart.dll18,2116.2011,0%Fonctionnalités graphiques partagées entre les applications Office
wwlib.dll42,1541.122,5%Binaire principal de Microsoft Word
excel.exe52,8650,294,9%Binaire principal de Microsoft Excel

Le test de FH4 sur les binaires SQL de base montre une réduction de 4 à 21% de la taille, principalement à partir de la compression des métadonnées décrite dans la section suivante:

BinaireAncienne taille (Mo)Nouvelle taille (Mo)% De réduction de tailleLa description
sqllang.dll47.1244,335,9%Services de haut niveau: analyseur de langage, classeur, optimiseur et moteur d'exécution
sqlmin.dll48.1745,834,8%Services de bas niveau: moteur de transactions et de stockage
qds.dll1,421,336,3%FonctionnalitĂ© de magasin de requĂȘtes
SqlDK.dll3.193.054,4%Abstractions SQL OS: mémoire, threads, planification, etc.
autoadmin.dll1,771,647,3%Logique du conseiller de réglage de base de données
xedetours.dll0,450,3621,6%Enregistreur de donnĂ©es de vol pour les requĂȘtes

La technologie


Lors de l'analyse de ce qui a causé la si grande exception de gestion des données C ++ dans Microsoft.UI.Xaml.dll, j'ai trouvé deux principaux coupables:


  1. Les structures de donnĂ©es elles-mĂȘmes sont grandes: les tables de mĂ©tadonnĂ©es Ă©taient de taille fixe avec des champs de dĂ©calages relatifs Ă  l'image et des entiers de quatre octets chacun. Une fonction avec un seul try / catch et un ou deux destructeurs automatiques avait plus de 100 octets de mĂ©tadonnĂ©es.
  2. Les structures de donnĂ©es et le code gĂ©nĂ©rĂ©s ne pouvaient pas ĂȘtre fusionnĂ©s. Les tables de mĂ©tadonnĂ©es contenaient des dĂ©calages relatifs Ă  l'image qui empĂȘchaient le pliage de COMDAT (le processus oĂč l'Ă©diteur de liens peut plier des Ă©lĂ©ments de donnĂ©es identiques pour Ă©conomiser de l'espace) Ă  moins que les fonctions qu'ils reprĂ©sentaient soient identiques. De plus, les funclets catch (code dĂ©crit Ă  partir des blocs catch du programme) ne pouvaient pas ĂȘtre pliĂ©s mĂȘme s'ils Ă©taient identiques au code car leurs mĂ©tadonnĂ©es sont contenues dans leurs parents.

Pour résoudre ces problÚmes, FH4 restructure les métadonnées et le code de sorte que:


  1. Les valeurs de taille fixe prĂ©cĂ©dentes ont Ă©tĂ© compressĂ©es Ă  l'aide d'un codage entier de longueur variable qui supprime> 90% des champs de mĂ©tadonnĂ©es de quatre octets Ă  un. Les tables de mĂ©tadonnĂ©es sont dĂ©sormais Ă©galement de longueur variable avec un en-tĂȘte pour indiquer si certains champs sont prĂ©sents pour Ă©conomiser de l'espace lors de l'Ă©mission de champs vides.
  2. Tous les dĂ©calages relatifs Ă  l'image qui peuvent ĂȘtre relatifs Ă  la fonction ont Ă©tĂ© rendus relatifs Ă  la fonction. Cela permet au COMDAT de se replier entre des mĂ©tadonnĂ©es de diffĂ©rentes fonctions avec des caractĂ©ristiques similaires (pensez aux instanciations de modĂšle) et permet de compresser ces valeurs. Les funclets de capture ont Ă©tĂ© repensĂ©s pour ne plus avoir leurs mĂ©tadonnĂ©es stockĂ©es dans celles de leurs parents afin que tous les funclets de capture identiques au code puissent maintenant ĂȘtre pliĂ©s en une seule copie dans le binaire.

Pour illustrer cela, regardons la définition d'origine de la table de métadonnées Info fonction utilisée pour __CxxFrameHandler3. Il s'agit de la table de démarrage de l'exécution lors du traitement d'EH et pointe vers les autres tables de métadonnées. Ce code est disponible publiquement dans toute installation VS, recherchez <chemin d'installation VS> \ 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; 

Cette structure est de taille fixe contenant 10 champs de 4 octets de long. Cela signifie que chaque fonction qui a besoin de la gestion des exceptions C ++ par défaut encourt 40 octets de métadonnées.


Passons maintenant à la nouvelle structure de données (<chemin d'installation VS> \ 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 }; 

Notez que:


  1. Le nombre magique a été supprimé, l'émission de 0x19930522 à chaque fois devient un problÚme lorsqu'un programme a des milliers de ces entrées.
  2. EHFlags a Ă©tĂ© dĂ©placĂ© dans l'en-tĂȘte tandis que dispESTypeList a Ă©tĂ© supprimĂ© en raison de la suppression de la prise en charge des spĂ©cifications d'exceptions dynamiques en C ++ 17. Le compilateur prendra par dĂ©faut l'ancienne __CxxFrameHandler3 si des spĂ©cifications d'exceptions dynamiques sont utilisĂ©es.
  3. Les longueurs des autres tables ne sont plus enregistrĂ©es dans «Function Info 4». Cela permet au pliage COMDAT de plier davantage les tables pointĂ©es mĂȘme si la table «Function Info 4» elle-mĂȘme ne peut pas ĂȘtre pliĂ©e.
  4. (Non explicitement affiché) Les champs dispFrame et bbtFlags sont maintenant des entiers de longueur variable. La représentation de haut niveau le laisse comme uint32_t pour un traitement facile.
  5. bbtFlags, dispUnwindMap, dispTryBlockMap et dispFrame peuvent ĂȘtre omis selon les champs dĂ©finis dans l'en-tĂȘte.

Compte tenu de tout cela, la taille moyenne de la nouvelle structure «Function Info 4» est dĂ©sormais de 13 octets (en-tĂȘte de 1 octet + trois dĂ©calages relatifs d'image de 4 octets par rapport Ă  d'autres tables), ce qui peut rĂ©duire encore plus si certaines tables ne sont pas nĂ©cessaires. Les longueurs des tables ont Ă©tĂ© dĂ©placĂ©es, mais ces valeurs sont dĂ©sormais compressĂ©es et 90% d'entre elles dans Microsoft.UI.Xaml.dll se sont avĂ©rĂ©es tenir dans un seul octet. Dans l'ensemble, cela signifie que la taille moyenne pour reprĂ©senter les mĂȘmes donnĂ©es fonctionnelles dans le nouveau gestionnaire est de 16 octets par rapport aux 40 octets prĂ©cĂ©dents - une amĂ©lioration assez spectaculaire!


Pour le pliage, regardons le nombre de tables et de funclets uniques avec l'ancien et le nouveau gestionnaire:

Eh donnéesCompter dans __CxxFrameHandler3Compter dans __CxxFrameHandler4% De réduction
Entrées Pdata12 3229 85520,0%
Infos fonction6 3862 74757,0%
Entrées de carte IP2State6 3632,14866,2%
Détendre les entrées de la carte1 4871,4641,5%
Cartes des gestionnaires de captures2,60360176,9%
Essayez des cartes2 59864875,1%
Funtets Dtor2,3011,52733,6%
Attrapez des funclets2,6038496,8%
Total36,66319 07448,0%

Le nombre d'entrĂ©es de donnĂ©es EH uniques diminue de 48% par rapport Ă  la crĂ©ation d'opportunitĂ©s de repli supplĂ©mentaires en supprimant les RVA et en repensant les funclets de capture. Je veux spĂ©cifiquement appeler le nombre de funclets catch en italique en vert: il passe de 2.603 Ă  seulement 84. Ceci est une consĂ©quence de la conversion de HRESULTs C ++ / WinRT en exceptions C ++ qui gĂ©nĂšre beaucoup de funclets catch identiques au code qui peuvent maintenant ĂȘtre pliĂ©. Certes, une baisse de cette ampleur se situe dans le haut de gamme des rĂ©sultats, mais dĂ©montre nĂ©anmoins les Ă©conomies de taille potentielles que le pliage peut rĂ©aliser lorsque les structures de donnĂ©es sont conçues en tenant compte de cela.


Performances


Avec la conception introduisant la compression et modifiant l'exécution du runtime, les performances de gestion des exceptions étaient affectées. L'impact est cependant positif : les performances de gestion des exceptions s'améliorent avec __CxxFrameHandler4 par opposition à __CxxFrameHandler3. J'ai testé le débit en utilisant un programme de référence qui se déroule à travers 100 cadres de pile chacun avec un essai / capture et 3 objets automatiques à détruire. Cela a été exécuté 50 000 fois pour profiler le temps d'exécution, ce qui a conduit à des temps d'exécution globaux de:

__CxxFrameHandler3__CxxFrameHandler4
Temps d'exécution4.84s4,25 s

Le profilage a montré que la décompression introduit un temps de traitement supplémentaire, mais son coût est compensé par moins de magasins pour le stockage local des threads dans la nouvelle conception d'exécution.


Plans futurs


Comme mentionné dans le titre, FH4 n'est actuellement activé que pour les binaires x64. Cependant, les techniques décrites sont extensibles à ARM32 / ARM64 et dans une moindre mesure x86. Nous recherchons actuellement de bons exemples (comme Microsoft.UI.Xaml.dll) pour motiver l'extension de cette technologie à d'autres plates-formes - si vous pensez que vous avez un bon cas d'utilisation, faites-le nous savoir!


Le processus d'intégration des modifications d'exécution pour les applications Store pour prendre en charge FH4 est en cours. Une fois cela fait, le nouveau gestionnaire sera activé par défaut afin que tout le monde puisse obtenir ces économies de taille binaire sans effort supplémentaire.


Remarques de clĂŽture


Pour tous ceux qui pensent que leurs binaires x64 pourraient faire un peu de réduction: essayez FH4 (via '/ d2FH4') aujourd'hui! Nous sommes ravis de voir quelles économies cela peut apporter maintenant que cette fonctionnalité est disponible dans la nature. Bien sûr, si vous rencontrez des problÚmes, veuillez nous en informer dans les commentaires ci-dessous, par e-mail ( visualcpp@microsoft.com ) ou via la communauté des développeurs . Vous pouvez également nous trouver sur Twitter ( @VisualC ).


Merci à Kenny Kerr de nous avoir dirigé vers Microsoft.UI.Xaml.dll, Ravi Pinjala pour avoir collecté les chiffres sur Office et Robert Roessler pour avoir testé cela sur SQL.

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


All Articles