جعل C ++ استثناء معالجة أصغر على x64

يقدم Visual Studio 2019 Preview 3 ميزة جديدة لتقليل الحجم الثنائي للمعالجة استثناء C ++ (try / catch و destructors التلقائي) على x64. مدبلج FH4 (لـ __CxxFrameHandler4 ، انظر أدناه) ، لقد قمت بتطوير تنسيق ومعالجة جديدين للبيانات المستخدمة لمعالجة استثناء C ++ والتي تقل بنسبة 60٪ تقريبًا عن التنفيذ الحالي مما يؤدي إلى تخفيض ثنائي إجمالي يصل إلى 20٪ للبرامج ذات الاستخدام المكثف لـ C ++ معالجة استثناء.


هذه المادة في بلوق .

كيف يمكنني تشغيل هذا؟


تم إيقاف تشغيل FH4 حاليًا بشكل افتراضي لأن تغييرات وقت التشغيل المطلوبة لتطبيقات المتجر لم تتمكن من تحويلها إلى الإصدار الحالي. لتشغيل FH4 للتطبيقات غير المخصصة للتخزين ، مرر العلامة "/ d2FH4" غير الموثقة إلى برنامج التحويل البرمجي MSVC في Visual Studio 2019 Preview 3 وما بعده.


نحن نخطط لتمكين FH4 بشكل افتراضي بمجرد تحديث وقت تشغيل المتجر. نأمل في القيام بذلك في Visual Studio 2019 التحديث 1 وسوف نقوم بتحديث هذا المنشور الذي نعرفه أكثر.


أدوات التغييرات


سيكون أي تثبيت لبرنامج Visual Studio 2019 Preview 3 وما بعده التغييرات في برنامج التحويل البرمجي و C ++ وقت التشغيل لدعم FH4. توجد تغييرات برنامج التحويل البرمجي داخليًا تحت علامة "/ d2FH4" المذكورة أعلاه. يحتوي وقت تشغيل C ++ على DLL جديد يسمى vcruntime140_1.dll والذي يتم تثبيته تلقائيًا بواسطة VCRedist. هذا مطلوب لكشف معالج الاستثناء الجديد __CxxFrameHandler4 الذي يستبدل روتين __CxxFrameHandler3 الأقدم. يتم دعم الارتباط الثابت والتطبيق المحلي لوقت تشغيل C ++ الجديد أيضًا.


الآن على الأشياء الممتعة! سيغطي باقي هذا المنشور النتائج الداخلية من تجربة FH4 على أنظمة Windows و Office و SQL ، متبوعة بتفاصيل تقنية أكثر عمقًا وراء هذه التقنية الجديدة.


الدافع والنتائج


منذ عام تقريبًا ، جاء شركاؤنا في مشروع C ++ / WinR T إلى فريق Microsoft C ++ مع تحدٍ: إلى أي مدى يمكننا تقليل الحجم الثنائي لمعالجة استثناء C ++ للبرامج التي تستخدمه بكثافة؟


في سياق برنامج يستخدم C ++ / WinRT ، أشاروا إلينا إلى مكون Windows Microsoft.UI.Xaml.dll والذي كان معروفًا أن له بصمة ثنائية كبيرة بسبب معالجة استثناء C ++. لقد أكدت أن هذا هو الحال بالفعل وأحدثت انهيارًا في الحجم الثنائي مع __CxxFrameHandler3 الحالي ، الموضح أدناه. تمثل النسب المئوية في الجانب الأيمن من المخطط نسبة مئوية من إجمالي الحجم الثنائي الذي تشغله جداول بيانات تعريف محددة ورمز مخطط تفصيلي.


حجم انهيار Microsoft.UI.Xaml.dll باستخدام __CxxFrameHandler3


لن أناقش في هذا المنشور ما تفعله الهياكل المحددة على الجانب الأيمن من المخطط (راجع حديث جيمس ماكنيليس حول كيفية عمل فك المكدس على Windows للحصول على مزيد من التفاصيل). بالنظر إلى البيانات الوصفية والرمز الإجمالية ، تم استخدام 26.4٪ من الحجم الثنائي بمعالجة استثناء C ++. هذا هو مقدار هائل من الفضاء وكان يعوق اعتماد C ++ / WinRT.


لقد أجرينا تغييرات في الماضي لتقليل حجم استثناء C ++ في المترجم دون تغيير وقت التشغيل. يتضمن ذلك إسقاط البيانات الوصفية لمناطق الكود التي لا يمكنها رمي حالات متطابقة منطقية وقابلة للطي. ومع ذلك ، كنا نصل إلى نهاية ما يمكننا القيام به في المترجم فقط ولن نكون قادرين على تحقيق تقدم كبير في شيء بهذا الحجم. أظهر التحليل أن هناك انتصارات كبيرة قد تم إجراؤها ولكنها تتطلب تغييرات أساسية في البيانات والرمز ووقت التشغيل. لذلك ذهبنا إلى الأمام وفعلناهم.


مع __CxxFrameHandler4 الجديد والبيانات الوصفية المصاحبة له ، أصبح توزيع الحجم لـ Microsoft.UI.XAML.dll الآن على النحو التالي:


حجم انهيار Microsoft.UI.Xaml.dll باستخدام __CxxFrameHandler4


ينخفض ​​الحجم الثنائي المستخدم في معالجة الاستثناء C ++ بنسبة 64٪ مما يؤدي إلى انخفاض إجمالي حجم الثنائي بنسبة 18.6٪ على هذا الثنائي. تقلص كل نوع من الهيكل في حجم من خلال درجات مذهلة:

ايه البيانات__CxxFrameHandler3 الحجم (بايت)__CxxFrameHandler4 الحجم (بايت)تخفيض حجم ٪
مداخل البيانات147،864118،26020.0 ٪
فك الرموز22428492،81058.6 ٪
معلومات بقية المقال255،4402775589.1 ٪
خرائط IP2State186،94445،09875.9 ٪
فك خرائط809526975713.8 ٪
التقاط خرائط معالج52،0606،14788.2 ٪
جرب الخرائط51،9605،19690.0 ٪
dtor funclets54.57045.73916.2 ٪
القبض على funclets102،4004،30195.8 ٪
المجموع1،156،474415،06364.1 ٪

مجتمعة ، أدى التبديل إلى __CxxFrameHandler4 إلى إسقاط الحجم الكلي لـ Microsoft.UI.Xaml.dll من 4.4 ميغابايت إلى 3.6 ميغابايت.


تُظهر تجربة FH4 على مجموعة تمثيلية من ثنائيات Office تقليل حجم ~ 10٪ في مكتبات الارتباط الحيوي (DLL) التي تستخدم استثناءات بشكل كبير. حتى في Word و Excel ، المصممة لتقليل استخدام الاستثناء ، لا يزال هناك انخفاض كبير في الحجم الثنائي.

ثنائيالحجم القديم (ميغابايت)حجم جديد (ميغابايت)تخفيض حجم ٪وصف
chart.dll17.2715.1012.6 ٪دعم للتفاعل مع الرسوم البيانية والرسوم البيانية
Csi.dll9.788.6611.4 ٪دعم للعمل مع الملفات المخزنة في السحابة
Mso20Win32Client.dll6.075.4111.0 ٪كود مشترك مشترك بين جميع تطبيقات Office
Mso30Win32Client.dll8.117.309.9 ٪كود مشترك مشترك بين جميع تطبيقات Office
oart.dll18.2116.2011.0 ٪ميزات الرسومات التي يتم مشاركتها بين تطبيقات Office
wwlib.dll42.1541.122.5٪مايكروسوفت وورد الرئيسي الثنائي
excel.exe52.8650.294.9٪ثنائي مايكروسوفت اكسل الرئيسي

تُظهر تجربة FH4 على ثنائيات SQL الأساسية انخفاضًا بنسبة 4-21٪ في الحجم ، بشكل أساسي من ضغط بيانات التعريف الموصوفة في القسم التالي:

ثنائيالحجم القديم (ميغابايت)حجم جديد (ميغابايت)تخفيض حجم ٪وصف
sqllang.dll47.1244.335.9 ٪خدمات المستوى الأعلى: محلل اللغة ، الموثق ، محسن ، ومحرك التنفيذ
sqlmin.dll48.1745.834.8٪خدمات منخفضة المستوى: المعاملات ومحرك التخزين
qds.dll1.421.336.3 ٪وظيفة مخزن الاستعلام
SqlDK.dll3.193.054.4٪مزودات نظام التشغيل SQL: الذاكرة ، المواضيع ، الجدولة ، إلخ.
autoadmin.dll1.771.647.3 ٪قاعدة بيانات مستشار ضبط المنطق
xedetours.dll0.450.3621.6 ٪مسجل بيانات الرحلة للاستعلامات

التكنولوجيا


عند تحليل السبب الذي جعل استثناء C ++ الذي يعالج البيانات كبيرًا جدًا في Microsoft.UI.Xaml.dll ، وجدت اثنين من المذنبين الأساسيين:


  1. هياكل البيانات نفسها كبيرة: تم تحديد حجم جداول بيانات التعريف بحقول الإزاحات ذات الصلة بالصورة والأعداد الصحيحة التي يبلغ طول كل منها أربعة بايت. تحتوي الوظيفة التي تحتوي على try / catch واحدة وواحدة أو اثنين من أدوات التدمير التلقائي على أكثر من 100 بايت من بيانات التعريف.
  2. بنيات البيانات والتعليمات البرمجية التي تم إنشاؤها لم تكن قابلة للدمج. تحتوي جداول البيانات الأولية على إزاحات نسبية للصورة حالت دون طي COMDAT (العملية حيث يمكن للرابط أن يطوي أجزاء متماثلة من البيانات لتوفير مساحة) ما لم تكن الوظائف التي يمثلونها متطابقة. بالإضافة إلى ذلك ، لا يمكن طي funclets (رمز تفصيلي من كتل catch الخاصة بالبرنامج) حتى لو كانت متطابقة مع الكود لأن بيانات التعريف الخاصة بها موجودة في والديهم.

لمعالجة هذه المشكلات ، تقوم FH4 بإعادة هيكلة البيانات التعريفية والرمز بحيث:


  1. تم ضغط القيم ذات الحجم الثابت السابق باستخدام ترميز عدد صحيح بطول متغير يسقط> 90٪ من حقول البيانات الأولية من أربعة بايت إلى واحد. تعد جداول البيانات التعريفية الآن أيضًا متغيرة الطول مع رأس للإشارة إلى ما إذا كانت هناك بعض الحقول موجودة لتوفير مساحة على انبعاث الحقول الفارغة.
  2. جميع الإزاحات ذات الصلة بالصورة التي يمكن أن تكون ذات صلة بالوظيفة قد أصبحت دالة نسبيًا. يتيح ذلك إمكانية طي COMDAT بين البيانات الوصفية للوظائف المختلفة ذات الخصائص المتشابهة (إنشاء مثيل لقالب التفكير) ويسمح بضغط هذه القيم. تم إعادة تصميم funclets المصيد بحيث لم يعد يتم تخزين بيانات التعريف الخاصة بهم في والديهم بحيث يمكن الآن طي أي funclets الصيد مطابقة التعليمات البرمجية إلى نسخة واحدة في الثنائية.

لتوضيح ذلك ، دعنا ننظر إلى التعريف الأصلي لجدول بيانات معلومات الوظيفة المستخدم في __CxxFrameHandler3. هذا هو جدول البداية لوقت التشغيل عند معالجة EH ويشير إلى جداول بيانات التعريف الأخرى. هذا الرمز متاح للجمهور في أي تثبيت VS ، ابحث عن <مسار تثبيت 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; 

هذه البنية ثابتة الحجم تحتوي على 10 حقول لكل منها 4 بايت. هذا يعني أن كل وظيفة تحتاج إلى معالجة C ++ باستثناء افتراضات تتضمن 40 بايت من بيانات التعريف.


الآن إلى بنية البيانات الجديدة (<مسار تثبيت 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 }; 

لاحظ أن:


  1. تمت إزالة الرقم السحري ، حيث ينبعث 0x19930522 في كل مرة يصبح فيها مشكلة عندما يحتوي البرنامج على الآلاف من هذه الإدخالات.
  2. تم نقل EHFlags إلى الرأس بينما تم التخلص التدريجي من dispestypeList بسبب انخفاض دعم مواصفات الاستثناء الديناميكي في C ++ 17. سيقوم المحول البرمجي افتراضيًا بـ __CxxFrameHandler3 الأقدم في حالة استخدام مواصفات الاستثناء الديناميكي.
  3. لم يعد يتم تخزين أطوال الجداول الأخرى في "معلومات الوظيفة 4". يتيح ذلك إمكانية طي COMDAT لطي المزيد من الجداول الموجهة حتى إذا كان جدول "معلومات الوظيفة 4" نفسه لا يمكن طيه.
  4. (غير مبين بشكل صريح) أصبح الآن حقلي dispFrame و bbtFlags عددًا صحيحًا متغير الطول. التمثيل الرفيع المستوى يجعله بمثابة uint32_t لسهولة المعالجة.
  5. يمكن حذف bbtFlags و dispUnwindMap و dispTryBlockMap و dispFrame وفقًا للحقول المعينة في الرأس.

مع أخذ كل هذا في الاعتبار ، يبلغ الآن متوسط ​​حجم بنية "معلومات الوظيفة 4" الجديدة 13 بايت (1 بايت رأس + إزاحة 3 بايت 4 إزاحة نسبية للجداول الأخرى) والتي يمكن تقليصها بدرجة أكبر إذا لم تكن هناك حاجة لبعض الجداول. تم نقل أطوال الجداول ، لكن يتم الآن ضغط هذه القيم وتم العثور على 90٪ منها في Microsoft.UI.Xaml.dll لتناسب داخل بايت واحد. إذا جمعنا كل ذلك ، فهذا يعني أن متوسط ​​الحجم لتمثيل نفس البيانات الوظيفية في المعالج الجديد هو 16 بايت مقارنةً بـ 40 بايت السابقة - وهو تحسن كبير جدًا!


من أجل الطي ، دعنا نلقي نظرة على عدد الجداول الفريدة ووظائفها باستخدام المعالج القديم والجديد:

ايه البياناتعد في __CxxFrameHandler3عد في __CxxFrameHandler4تخفيض ٪
مداخل البيانات12،3229،85520.0 ٪
معلومات بقية المقال6،3862،74757.0 ٪
إدخالات خريطة IP2State6،3632،14866.2 ٪
التراجع عن إدخالات الخريطة1،48714641.5٪
التقاط خرائط معالج2،60360176.9 ٪
جرب الخرائط2،59864875.1 ٪
dtor funclets23011،52733.6 ٪
القبض على funclets2،6038496.8 ٪
المجموع366631907448.0 ٪

ينخفض ​​عدد إدخالات بيانات EH الفريدة بنسبة 48 ٪ من خلق فرص قابلة للطي إضافية عن طريق إزالة RVAs وإعادة تصميم دورات الصيد. أريد تحديد عدد دورات funclets المائلة باللون الأخضر: حيث تنخفض من 2.603 إلى 84 فقط. وهذا نتيجة لاستثناءات C ++ / WinRT ترجمة HRESULT إلى C ++ التي تولد الكثير من funclets لمتطابقة الكود والتي يمكن الآن مطوية. من المؤكد أن هناك انخفاضًا في هذا الحجم على أعلى النتائج ولكن مع ذلك يوضح إمكانية تحقيق وفورات في الحجم المحتمل عندما يمكن تصميم هياكل البيانات مع وضعها في الاعتبار.


الأداء


مع التصميم الذي يقدم الضغط وتعديل تنفيذ وقت التشغيل ، كان هناك مخاوف من معالجة أداء الاستثناء. التأثير ، ومع ذلك ، هو إيجابي : تحسين أداء معالجة الاستثناء مع __CxxFrameHandler4 بدلاً من __CxxFrameHandler3. لقد اختبرت الإنتاجية باستخدام برنامج مرجعي يستريح من خلال 100 إطار مكدس مع كل محاولة / catch و 3 كائنات تلقائية للتدمير. تم تشغيل هذا 50000 مرة إلى وقت تنفيذ الملف الشخصي ، مما أدى إلى أوقات التنفيذ الإجمالية:

__CxxFrameHandler3__CxxFrameHandler4
وقت التنفيذ4.84 ثانية4.25s

أظهر التوصيف أن إزالة الضغط لا توفر وقتًا إضافيًا للمعالجة ولكن تكلفتها أقل من المتاجر الأقل إلى تخزين الصفحات المحلية في تصميم وقت التشغيل الجديد.


الخطط المستقبلية


كما هو مذكور في العنوان ، يتم تمكين FH4 حاليًا فقط x64 ثنائيات. ومع ذلك ، فإن التقنيات الموصوفة قابلة للتمديد إلى ARM32 / ARM64 وإلى حد أقل إلى x86. نحن نبحث حاليًا عن أمثلة جيدة (مثل Microsoft.UI.Xaml.dll) لتحفيز مد هذه التكنولوجيا إلى منصات أخرى - إذا كنت تعتقد أن لديك حالة استخدام جيدة ، فأخبرنا بذلك!


تجري عملية دمج تغييرات وقت التشغيل لتطبيقات المتجر لدعم FH4. بمجرد الانتهاء من ذلك ، سيتم تمكين المعالج الجديد افتراضيًا بحيث يمكن للجميع الحصول على هذه الوفورات في الحجم الثنائي دون أي جهد إضافي.


ملاحظات ختامية


لأي شخص يعتقد أن ثنائيات x64 لديه يمكن أن تفعله مع بعض القطع: جرب FH4 (عبر '/ d2FH4') اليوم! يسرنا أن نرى ما هي الوفورات التي يمكن أن توفرها الآن بعد أن أصبحت هذه الميزة في المتوحش. بالطبع ، إذا واجهت أي مشاكل ، فيرجى إعلامنا في التعليقات أدناه ، أو عبر البريد الإلكتروني ( visualcpp@microsoft.com ) ، أو من خلال مجتمع Developer . يمكنك أن تجدنا أيضًا على Twitter ( VisualC ).


شكرًا لكيني كير على توجيهنا إلى Microsoft.UI.Xaml.dll ، ورافي بينجالا لجمع الأرقام على Office ، وروبرت روسلر لتجربة ذلك على SQL.

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


All Articles