كيف قمنا بترجمة 10 ملايين سطر من كود C ++ إلى معيار C ++ 14 (ثم إلى C ++ 17)

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

بالنسبة لأولئك الذين لا يعرفون، 1C:Enterprise هي بيئة للتطور السريع لتطبيقات الأعمال عبر الأنظمة الأساسية ووقت التشغيل لتنفيذها على أنظمة تشغيل مختلفة وأنظمة إدارة قواعد البيانات. بشكل عام، المنتج يحتوي على:

  • مجموعة خادم التطبيقات، يعمل على نظامي التشغيل Windows و Linux
  • زبون، يعمل مع الخادم عبر http(s) أو البروتوكول الثنائي الخاص به، ويعمل على أنظمة التشغيل Windows وLinux وmacOS
  • العميل على شبكة الإنترنت، يعمل في متصفحات Chrome وInternet Explorer وMicrosoft Edge وFirefox وSafari (مكتوب بلغة JavaScript)
  • بيئة التطوير (مكون)، يعمل على أنظمة التشغيل Windows و Linux و macOS
  • أدوات الإدارة خوادم التطبيقات، تعمل على أنظمة التشغيل Windows، Linux، macOS
  • العميل المحمول، يتصل بالخادم عبر http(s)، ويعمل على الأجهزة المحمولة التي تعمل بنظام Android، وiOS، وWindows
  • منصة متنقلة - إطار عمل لإنشاء تطبيقات الهاتف المحمول غير المتصلة بالإنترنت مع إمكانية المزامنة، والتي تعمل على أنظمة Android وiOS وWindows
  • بيئة التطوير 1C: أدوات تطوير المشاريع، مكتوب بلغة جافا
  • الخادم أنظمة التفاعل

نحاول كتابة نفس الكود لأنظمة تشغيل مختلفة قدر الإمكان - قاعدة كود الخادم شائعة بنسبة 99%، وقاعدة كود العميل حوالي 95%. تمت كتابة النظام الأساسي للتكنولوجيا 1C:Enterprise بشكل أساسي بلغة C++، وترد أدناه خصائص التعليمات البرمجية التقريبية:

  • 10 ملايين سطر من كود C++،
  • 14 ألف ملف
  • 60 ألف فئة
  • نصف مليون طريقة.

وكان لا بد من ترجمة كل هذه الأشياء إلى C++ 14. سنخبرك اليوم كيف فعلنا ذلك وما واجهناه في هذه العملية.

كيف قمنا بترجمة 10 ملايين سطر من كود C ++ إلى معيار C ++ 14 (ثم إلى C ++ 17)

تنصل

كل ما هو مكتوب أدناه حول العمل البطيء/السريع، (وليس) استهلاك الذاكرة الكبير من خلال تطبيقات الفئات القياسية في المكتبات المختلفة يعني شيئًا واحدًا: هذا صحيح بالنسبة لنا. من الممكن أن تكون التطبيقات القياسية هي الأنسب لمهامك. لقد بدأنا من مهامنا الخاصة: أخذنا البيانات النموذجية لعملائنا، وقمنا بتشغيل سيناريوهات نموذجية عليها، ونظرنا في الأداء، وحجم الذاكرة المستهلكة، وما إلى ذلك، وقمنا بتحليل ما إذا كنا نحن وعملاؤنا راضين عن هذه النتائج أم لا . وتصرفوا اعتمادا على ذلك.

ما كان لدينا

في البداية، قمنا بكتابة التعليمات البرمجية للنظام الأساسي 1C:Enterprise 8 في Microsoft Visual Studio. بدأ المشروع في أوائل العقد الأول من القرن الحادي والعشرين وكان لدينا إصدار يعمل بنظام Windows فقط. بطبيعة الحال، منذ ذلك الحين تم تطوير الكود بنشاط، تمت إعادة كتابة العديد من الآليات بالكامل. لكن تمت كتابة الكود وفقًا لمعايير عام 2000، وعلى سبيل المثال، تم فصل الأقواس القائمة لدينا بمسافات حتى ينجح التجميع، مثل هذا:

vector<vector<int> > IntV;

في عام 2006، مع إصدار النظام الأساسي الإصدار 8.1، بدأنا في دعم Linux وانتقلنا إلى مكتبة قياسية تابعة لجهة خارجية STLPort. كان أحد أسباب التحول هو العمل بخطوط واسعة. في الكود الخاص بنا، نستخدم std::wstring، والذي يعتمد على النوع wchar_t، طوال الوقت. حجمه في نظام التشغيل Windows هو 2 بايت، وفي Linux الحجم الافتراضي هو 4 بايت. وأدى ذلك إلى عدم توافق بروتوكولاتنا الثنائية بين العميل والخادم، بالإضافة إلى العديد من البيانات المستمرة. باستخدام خيارات gcc، يمكنك تحديد أن حجم wchar_t أثناء الترجمة هو أيضًا 2 بايت، ولكن بعد ذلك يمكنك نسيان استخدام المكتبة القياسية من المترجم، لأن فهو يستخدم glibc، والذي بدوره يتم تجميعه لملف wchar_t ذو 4 بايت. وكانت الأسباب الأخرى هي التنفيذ الأفضل للفئات القياسية، ودعم جداول التجزئة، وحتى محاكاة دلالات التحرك داخل الحاويات، والتي استخدمناها بنشاط. وهناك سبب آخر، كما يقولون أخيرًا وليس آخرًا، وهو أداء السلسلة. كان لدينا فصلنا الخاص للسلاسل، لأن... نظرًا لخصائص برنامجنا، يتم استخدام عمليات السلسلة على نطاق واسع جدًا وهذا أمر بالغ الأهمية بالنسبة لنا.

تعتمد سلسلتنا على أفكار تحسين السلسلة التي تم التعبير عنها في أوائل العقد الأول من القرن الحادي والعشرين أندريه ألكسندريسكو. لاحقًا، عندما عمل ألكسندريسكو في فيسبوك، بناءً على اقتراحه، تم استخدام خط في محرك فيسبوك يعمل على مبادئ مماثلة (انظر المكتبة حماقة).

يستخدم خطنا تقنيتين رئيسيتين للتحسين:

  1. بالنسبة للقيم القصيرة، يتم استخدام مخزن مؤقت داخلي في كائن السلسلة نفسه (لا يتطلب تخصيص ذاكرة إضافية).
  2. لجميع الآخرين، يتم استخدام الميكانيكا نسخ عند الكتابة. يتم تخزين قيمة السلسلة في مكان واحد، ويتم استخدام عداد مرجعي أثناء التعيين/التعديل.

لتسريع تجميع النظام الأساسي، استبعدنا تنفيذ الدفق من متغير STLPort الخاص بنا (الذي لم نستخدمه)، مما أعطانا تجميعًا أسرع بنسبة 20٪ تقريبًا. وفي وقت لاحق كان علينا أن نستخدمها بشكل محدود زيادة. يستخدم Boost الدفق بشكل مكثف، لا سيما في واجهات برمجة تطبيقات الخدمة الخاصة به (على سبيل المثال، للتسجيل)، لذلك اضطررنا إلى تعديله لإزالة استخدام الدفق. وهذا بدوره جعل من الصعب علينا الانتقال إلى الإصدارات الجديدة من Boost.

الطريق الثالث

عند الانتقال إلى معيار C++ 14، أخذنا في الاعتبار الخيارات التالية:

  1. قم بترقية STLPort الذي قمنا بتعديله إلى معيار C++ 14. الخيار صعب للغاية، لأن... تم إيقاف دعم STLPort في عام 2010، وسيتعين علينا إنشاء كل التعليمات البرمجية الخاصة به بأنفسنا.
  2. الانتقال إلى تطبيق STL آخر متوافق مع C++ 14. ومن المرغوب فيه للغاية أن يكون هذا التطبيق لنظامي التشغيل Windows وLinux.
  3. عند الترجمة لكل نظام تشغيل، استخدم المكتبة المضمنة في المترجم المقابل.

تم رفض الخيار الأول تمامًا بسبب كثرة العمل.

لقد فكرنا في الخيار الثاني لبعض الوقت؛ يعتبر مرشحا libc ++ولكن في ذلك الوقت لم يكن يعمل تحت نظام Windows. لنقل libc++ إلى Windows، سيتعين عليك القيام بالكثير من العمل - على سبيل المثال، كتابة كل ما يتعلق بالسلاسل بنفسك ومزامنة الخيوط والذرة، حيث يتم استخدام libc++ في هذه المجالات واجهة برمجة تطبيقات بوسيكس.

واخترنا الطريق الثالث.

انتقال

لذلك، كان علينا استبدال استخدام STLPort بمكتبات المترجمين المقابلين (Visual Studio 2015 لنظام التشغيل Windows، gcc 7 لنظام التشغيل Linux، clang 8 لنظام التشغيل macOS).

لحسن الحظ، تمت كتابة التعليمات البرمجية الخاصة بنا بشكل أساسي وفقًا للمبادئ التوجيهية ولم تستخدم جميع أنواع الحيل الذكية، لذلك تمت عملية الترحيل إلى المكتبات الجديدة بسلاسة نسبيًا، بمساعدة البرامج النصية التي حلت محل أسماء الأنواع والفئات ومساحات الأسماء والتضمينات في المصدر ملفات. أثرت عملية الترحيل على 10 ملف مصدر (من أصل 000). تم استبدال wchar_t بـ char14_t؛ قررنا التخلي عن استخدام wchar_t، لأنه يأخذ char000_t 16 بايت على جميع أنظمة التشغيل ولا يفسد توافق التعليمات البرمجية بين Windows وLinux.

كانت هناك بعض المغامرات الصغيرة. على سبيل المثال، في STLPort، يمكن تحويل المُكرِّر ضمنيًا إلى مؤشر إلى عنصر ما، وقد تم استخدام هذا في بعض الأماكن في الكود الخاص بنا. في المكتبات الجديدة، لم يعد من الممكن القيام بذلك، وكان لا بد من تحليل هذه المقاطع وإعادة كتابتها يدويًا.

وبذلك، اكتملت عملية ترحيل التعليمات البرمجية، وتم تجميع التعليمات البرمجية لجميع أنظمة التشغيل. حان الوقت للاختبارات.

أظهرت الاختبارات بعد الانتقال انخفاضًا في الأداء (في بعض الأماكن يصل إلى 20-30%) وزيادة في استهلاك الذاكرة (حتى 10-15%) مقارنة بالإصدار القديم من الكود. وكان هذا، على وجه الخصوص، بسبب الأداء دون الأمثل للسلاسل القياسية. لذلك، كان علينا مرة أخرى استخدام خطنا المعدل قليلاً.

تم الكشف أيضًا عن ميزة مثيرة للاهتمام لتطبيق الحاويات في المكتبات المضمنة: تخصيص الذاكرة الفارغة (بدون عناصر) std::map وstd::set من المكتبات المضمنة. وبسبب ميزات التنفيذ، يتم إنشاء عدد كبير جدًا من الحاويات الفارغة من هذا النوع في بعض الأماكن في الكود. يتم تخصيص حاويات الذاكرة القياسية قليلاً لعنصر جذر واحد، ولكن بالنسبة لنا فقد تبين أن هذا أمر بالغ الأهمية - في عدد من السيناريوهات، انخفض أدائنا بشكل ملحوظ وزاد استهلاك الذاكرة (مقارنة بـ STLPort). لذلك، قمنا في الكود الخاص بنا باستبدال هذين النوعين من الحاويات من المكتبات المدمجة مع تنفيذها من Boost، حيث لم تكن هذه الحاويات تحتوي على هذه الميزة، وهذا حل مشكلة التباطؤ وزيادة استهلاك الذاكرة.

كما يحدث غالبًا بعد التغييرات واسعة النطاق في المشاريع الكبيرة، لم يعمل التكرار الأول للكود المصدر دون مشاكل، وهنا، على وجه الخصوص، أصبح دعم تصحيح أخطاء التكرارات في تطبيق Windows مفيدًا. تقدمنا ​​للأمام خطوة بخطوة، وبحلول ربيع عام 2017 (الإصدار 8.3.11 1C:Enterprise) اكتمل الترحيل.

نتائج

استغرق الانتقال إلى معيار C++ 14 حوالي 6 أشهر. في معظم الأوقات، عمل مطور واحد (لكن مؤهل تأهيلاً عاليًا) في المشروع، وفي المرحلة النهائية انضم ممثلون عن الفرق المسؤولة عن مجالات محددة - واجهة المستخدم، ومجموعة الخوادم، وأدوات التطوير والإدارة، وما إلى ذلك.

وقد أدى هذا الانتقال إلى تبسيط عملنا إلى حد كبير بشأن الانتقال إلى أحدث الإصدارات من المعيار. وبالتالي، فإن الإصدار 1C:Enterprise 8.3.14 (قيد التطوير، الإصدار المقرر إصداره في أوائل العام المقبل) قد تم بالفعل نقله إلى المعيار سي ++ 17.

بعد الترحيل، يتوفر للمطورين المزيد من الخيارات. إذا كان لدينا سابقًا نسختنا المعدلة من STL ومساحة اسم واحدة std، فلدينا الآن فئات قياسية من مكتبات المترجم المضمنة في مساحة الاسم std، في مساحة الاسم stdx - خطوطنا وحاوياتنا المُحسّنة لمهامنا، في Boost - أحدث نسخة من دفعة. ويستخدم المطور تلك الفئات المناسبة على النحو الأمثل لحل مشاكله.

يساعد التنفيذ "الأصلي" لمنشئي الحركة أيضًا في التطوير (تحرك البنائين) لعدد من الفصول. إذا كانت الفئة تحتوي على مُنشئ نقل وتم وضع هذه الفئة في حاوية، فإن STL تعمل على تحسين نسخ العناصر داخل الحاوية (على سبيل المثال، عندما يتم توسيع الحاوية ويكون من الضروري تغيير السعة وإعادة تخصيص الذاكرة).

يطير في مرهم

ولعل العواقب الأكثر إزعاجا (ولكنها ليست حاسمة) للهجرة هي أننا نواجه زيادة في الحجم ملفات obj، وبدأت النتيجة الكاملة للإنشاء مع جميع الملفات الوسيطة تشغل ما بين 60 إلى 70 جيجابايت. ويرجع هذا السلوك إلى خصوصيات المكتبات القياسية الحديثة، التي أصبحت أقل أهمية لحجم ملفات الخدمة التي تم إنشاؤها. لا يؤثر هذا على تشغيل التطبيق المترجم، ولكنه يسبب عددًا من المضايقات في التطوير، على وجه الخصوص، فهو يزيد من وقت الترجمة. تتزايد أيضًا متطلبات المساحة الحرة على القرص على خوادم الإنشاء وعلى أجهزة المطورين. يعمل مطورونا على عدة إصدارات من النظام الأساسي بالتوازي، وفي بعض الأحيان تؤدي مئات الجيجابايت من الملفات المتوسطة إلى خلق صعوبات في عملهم. إن المشكلة مزعجة ولكنها ليست حرجة، وقد قمنا بتأجيل حلها في الوقت الراهن. نحن نعتبر التكنولوجيا أحد الخيارات لحلها بناء الوحدة (على وجه الخصوص، تستخدمه Google عند تطوير متصفح Chrome).

المصدر: www.habr.com

إضافة تعليق