الشرح التزايدي proccesing لتسريع يبني gradle

صورة


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


كيف يعمل التجميع التدريجي


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


دعنا نفكر في ترجمة التجنب على مثال (مأخوذ من مقال من Gradle) لمشروع يتكون من ثلاث وحدات: التطبيق ، و core و utils .


الفئة الرئيسية من وحدة التطبيق (يعتمد على الأساسية ):


public class Main { public static void main(String... args) { WordCount wc = new WordCount(); wc.collect(new File(args[0]); System.out.println("Word count: " + wc.wordCount()); } } 

في الوحدة الأساسية (يعتمد على utils ):


 public class WordCount { // ... void collect(File source) { IOUtils.eachLine(source, WordCount::collectLine); } } 

في وحدة utils :


 public class IOUtils { void eachLine(File file, Callable<String> action) { try { try (BufferedReader reader = new BufferedReader(new FileReader(file))) { // ... } } catch (IOException e) { // ... } } } 

ترتيب التجميع الأول من الوحدات كما يلي (وفقًا لترتيب التبعيات):


1) يلس
2) الأساسية
3) التطبيق


الآن ، ضع في اعتبارك ما يحدث عند تغيير التطبيق الداخلي لفئة IOUtils:


 public class IOUtils { // IOUtils lives in project `utils` void eachLine(File file, Callable<String> action) { try { try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "utf-8") )) { // ... } } catch (IOException e) { // ... } } } 

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



رسم توضيحي لتجنب التجميع على مستوى الوحدة النمطية للمشروع


المستوى الثاني من الزيادة هو الزيادة في مستوى تشغيل برنامج التحويل البرمجي للملفات التي تم تغييرها مباشرة داخل الوحدات الفردية.


على سبيل المثال ، أضف فئة جديدة إلى الوحدة الأساسية :


 public class NGrams { // NGrams lives in project `core` // ... void collect(String source, int ngramLength) { collectInternal(StringUtils.sanitize(source), ngramLength); } // ... } 

وفي utils :


 public class StringUtils { static String sanitize(String dirtyString) { ... } } 

في هذه الحالة ، من الضروري في كلتا الوحدتين إعادة ترجمة ملفين جديدين (دون التأثير على WordCount و IOUtils الحاليين وغير المتغيرين) ، حيث لا توجد تبعيات بين الفصول الجديدة والقديمة.


وبالتالي ، يقوم المحول البرمجي التزايدي بتحليل التبعيات بين الفئات وإعادة الترجمة فقط:


  • الطبقات التي تحتوي على التغييرات
  • الفصول التي تعتمد مباشرة على الفصول المتغيرة


    معالجة الشرح التزايدي


    أدخل وصف الصورة هنا



يؤدي إنشاء تعليمة برمجية باستخدام APT و KAPT إلى تقليل الوقت الذي تستغرقه في كتابة وتصحيح كود boilerplate ، لكن معالجة التعليقات التوضيحية يمكن أن تزيد بشكل كبير من وقت الإنشاء. ومما زاد الطين بلة ، لفترة طويلة ، كسر معالجة التعليقات التوضيحية بشكل أساسي إمكانيات التجميع الإضافي في Gradle.


يحكي كل معالج توضيحي في مشروع ما معلومات المحول البرمجي عن قائمة التعليقات التوضيحية التي يعالجها. ولكن من وجهة نظر التجميع ، فإن معالجة التعليقات التوضيحية هي صندوق أسود: لا يعرف Gradle ما الذي سيفعله المعالج ، على وجه الخصوص ، أي الملفات سينشئها وأين. حتى Gradle 4.7 ، تم تعطيل التجميع التزايدي تلقائيًا على مجموعات المصادر هذه حيث تم استخدام معالجات التعليقات التوضيحية.


مع إصدار Gradle 4.7 ، أصبح التجميع الإضافي يدعم معالجة التعليقات التوضيحية ، ولكن فقط لـ APT. في KAPT ، تم تقديم دعم للتعليق التزايدي باستخدام Kotlin 1.3.30. يتطلب أيضًا دعمًا من المكتبات التي توفر معالجات التعليقات التوضيحية. يتمتع مطورو معالج التعليقات التوضيحية بفرصة تعيين فئة المعالج بشكل صريح ، ومن ثم إبلاغ Gradle بالمعلومات اللازمة للتجميع التدريجي للعمل.


فئات معالج الشرح


يدعم Gradle فئتين من المعالجات:


عزل - يجب أن تتخذ مثل هذه المعالجات جميع القرارات لإنشاء الشفرة بناءً على المعلومات الواردة من AST المرتبطة بعنصر من التعليقات التوضيحية المحددة. هذه هي أسرع فئة من معالجات التعليقات التوضيحية ، نظرًا لأن Gradle قد لا يعيد تشغيل المعالج ويستخدم الملفات التي تم إنشاؤها مسبقًا في حالة عدم وجود تغييرات في الملف المصدر.


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


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


تجربتنا في تنفيذ معالجة التعليقات التوضيحية الإضافية


الآن بالنسبة للمشروعات التي تبدأ من نقطة الصفر وتستخدم أحدث إصدارات المكتبات والإضافات الإضافية ، من المرجح أن تكون الإنشاءات الإضافية نشطة بشكل افتراضي. ولكن يمكن تحقيق أكبر حصة من الزيادة في إنتاجية التجميع من خلال زيادة معالجة التعليقات التوضيحية في المشروعات الكبيرة والطويلة العمر. في هذه الحالة ، قد تكون هناك حاجة إلى تحديث إصدار ضخم. هل يستحق كل هذا العناء في الممارسة؟ لنرى!


لذلك ، لكي تتم معالجة التعليقات التوضيحية بنجاح ، نحتاج إلى:


  • Gradle 4.7+
  • كوتلين 1.3.30+
  • يجب أن تحظى جميع معالجات التعليقات التوضيحية في مشروعنا بدعمها. هذا مهم للغاية ، لأنه في وحدة نمطية واحدة لا يدعم معالج واحد على الأقل التزايد ، فسيقوم Gradle بتعطيله للوحدة بأكملها. سيتم تجميع جميع الملفات في الوحدة مرة أخرى في كل مرة! أحد الخيارات البديلة للحصول على دعم للتجميع التدريجي دون ترقية الإصدارات هو إزالة كافة التعليمات البرمجية باستخدام معالجات التعليقات التوضيحية في وحدة نمطية منفصلة. في الوحدات النمطية التي لا تحتوي على معالجات توضيحية ، يعمل التجميع التزايدي بشكل جيد

من أجل اكتشاف المعالجات التي لا تفي بالشرط الأخير ، يمكنك تشغيل التجميع مع العلم -Pkapt.verbose = true . إذا تم إجبار Gradle على تعطيل معالجة التعليقات التوضيحية الإضافية لوحدة واحدة ، فعندئذٍ في سجل الإنشاء ، سنرى رسالة حول المعالجات وأي الوحدات النمطية التي يحدث هذا (راجع اسم المهمة):


 > Task :common:kaptDebugKotlin w: [kapt] Incremental annotation processing requested, but support is disabled because the following processors are not incremental: toothpick.compiler.factory.FactoryProcessor (NON_INCREMENTAL), toothpick.compiler.memberinjector.MemberInjectorProcessor (NON_INCREMENTAL). 

في مشروع مكتبتنا الذي يحتوي على معالجات توضيحية غير تدريجية ، كان هناك 3:


  • مسواك
  • غرفة
  • PermissionsDispatcher

لحسن الحظ ، يتم دعم هذه المكتبات بشكل نشط ، وأحدث إصداراتها لديها بالفعل دعم تزايدي. علاوة على ذلك ، فإن جميع معالجات التعليقات التوضيحية في أحدث إصدارات هذه المكتبات لديها فئة مثالية - معزولة. في عملية رفع الإصدارات ، كان علي التعامل مع إعادة بيع المباني بسبب التغييرات في API Toothpick library ، والتي أثرت على كل وحدة من وحداتنا تقريبًا. ولكن في هذه الحالة ، كنا محظوظين ، واتضح أننا أعيد بناؤها تلقائيًا بالكامل باستخدام أسماء الاستبدال التلقائي لأساليب المكتبة العامة المستخدمة.


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


بالنسبة لإصدارات Kotlin 1.3.30-1.3.50 ، يجب تمكين الدعم للمعالجة الإضافية للتعليقات التوضيحية بشكل صريح من خلال kapt.incremental.apt = true في ملف gradle.properties الخاص بالمشروع. بدءًا من الإصدار 1.3.50 ، يتم ضبط هذا الخيار على "true" افتراضيًا.


التجميع التزايدي التنميط


بعد رفع إصدارات جميع التبعيات الضرورية ، حان الوقت لاختبار سرعة الإنشاءات الإضافية. للقيام بذلك ، استخدمنا المجموعة التالية من الأدوات والتقنيات:


  • مسح بناء الدرج
  • gradle-التعريف
  • لتشغيل البرامج النصية باستخدام معالجة التعليقات التوضيحية الممكّنة والمعطلة ، تم استخدام خاصية gradle kapt.incremental.apt = [true | false]
  • للحصول على نتائج متسقة وغنية بالمعلومات ، تم رفع التجميعات في بيئة CI منفصلة. بناء التكاثر تم استنساخها باستخدام أداة تحديد الاتجاه

gradle-profiler يسمح بإعداد نصوص تعريفية لمعايير البناء التزايدية. تم تجميع 4 سيناريوهات بناءً على الشروط التالية:


  • تعديل ملف يؤثر / لا يؤثر على ABI الخاص به
  • دعم تشغيل التعليق التوضيحي الإضافي على / قبالة

تشغيل كل من السيناريوهات هو سلسلة من:


  • إعادة تشغيل البرنامج الخفي للمرايا
  • إطلاق بنيات الاحماء
  • قم بتشغيل 10 تجميعات تزايدي ، قبل تغيير كل ملف عن طريق إضافة طريقة جديدة (خاصة للتغييرات غير ABI وعامة تغييرات ABI)

تم إجراء جميع الإنشاءات باستخدام Gradle 5.4.1. يشير الملف المتضمن في التغييرات إلى إحدى الوحدات الأساسية للمشروع (الشائعة) ، والتي تعتمد منها 40 وحدة (بما في ذلك العنصر الأساسي والميزة) بشكل مباشر. يستخدم هذا الملف التعليق التوضيحي لعزل المعالج.


تجدر الإشارة أيضًا إلى أن المدى القياسي تم تنفيذه على مهمتين أساسيتين: ompileDebugSources و assembleDebug . الأول يبدأ فقط في تجميع الملفات برمز المصدر ، دون القيام بأي عمل مع الموارد وتجميع التطبيق في ملف .apk. استنادًا إلى حقيقة أن التحويل البرمجي التزايدي يؤثر فقط على ملفات .kt و. java ، تم اختيار مهمة compileDedugSource للحصول على مقارنة أكثر سرعة وأسرع. في ظروف التطوير الحقيقي ، عند إعادة تشغيل التطبيق ، يستخدم Android Studio مهمة assembleDebug ، والتي تتضمن الإنشاء الكامل لإصدار تصحيح التطبيق.


نتائج المعيار


في كل الرسوم البيانية التي تم إنشاؤها بواسطة gradle-profiler أدناه ، يُظهر المحور العمودي وقت الإنشاء الإضافي بالمللي ثانية ، ويظهر المحور الأفقي رقم بداية الإنشاء.


: compileDebugSource قبل تحديث معالجات التعليقات التوضيحية


أدخل وصف الصورة هنا
كان متوسط ​​وقت التشغيل لكل سيناريو 38 ثانية قبل تحديث معالجات التعليقات التوضيحية إلى الإصدارات التي تدعم التزايدية. في هذه الحالة ، يعطل Gradle دعم التجميع التزايدي ، لذلك لا يوجد فرق كبير بين البرامج النصية.


: compileDebugSource بعد تحديث معالجات التعليقات التوضيحية



سيناريوتغيير تدريجي ABIتغيير غير تدريجي لـ ABIتغيير إضافي غير ABIغير تدريجي غير أبي التغيير
متوسط23978353702351434602
متوسط23879350192342434749
دقيقة22618339692234333292
ماكس26820380972565135843
stddev1193.291240.81888.24815.91

كان متوسط ​​التخفيض في وقت التجميع بسبب الزيادة بنسبة 31٪ للتغيرات ABI و 32.5٪ للتغيرات غير ABI. في القيمة المطلقة ، حوالي 10 ثانية.


: assembleDebug بعد تحديث معالجات التعليقات التوضيحية



سيناريوتغيير تدريجي ABIتغيير غير تدريجي لـ ABIتغيير إضافي غير ABIغير تدريجي غير أبي التغيير
متوسط39902498503900552123
متوسط38974496913871350336
دقيقة38563487823823348944
ماكس48255523644173265941
stddev2953.281011.201015.375039.11

لإنشاء إصدار تصحيح كامل للتطبيق في مشروعنا ، كان متوسط ​​الانخفاض في وقت البناء بسبب الزيادة 21.5٪ للتغيرات ABI و 23٪ للتغيرات غير ABI. بالقيمة المطلقة ، تقريبًا 10 ثوانٍ تقريبًا ، نظرًا لأن زيادة تجميع التعليمات البرمجية المصدر لا يؤثر على سرعة تجميع الموارد.


بناء مسح التشريح في Gradle Build Scan


لفهم أعمق لكيفية تحقيق الزيادة خلال التجميع التزايدي ، نقوم بمقارنة عمليات المسح للتجميعات الإضافية وغير الإضافية.


في حالة زيادة KAPT المعطلة ، فإن الجزء الرئيسي من وقت البناء هو تجميع وحدة التطبيق ، والتي لا يمكن موازتها مع المهام الأخرى. الجدول الزمني لـ KAPT غير الإضافي هو كما يلي:


أدخل وصف الصورة هنا


تنفيذ المهمة: يستغرق kaptDebugKotlin من وحدة التطبيق لدينا حوالي 8 ثوانٍ في هذه الحالة.


الجدول الزمني للحالة مع تمكين زيادة KAPT:


أدخل وصف الصورة هنا


الآن تمت إعادة تجميع وحدة التطبيق في أقل من ثانية. يجدر الانتباه إلى التباين البصري لمقاييس المسحين في الصورة أعلاه. المهام التي تبدو أقصر في الصورة الأولى ليست بالضرورة أطول في الثانية ، حيث تبدو أطول. لكن من الملاحظ للغاية كم انخفضت نسبة إعادة تجميع وحدة التطبيق عند تشغيل KAPT التزايدي. في حالتنا ، نربح حوالي 8 ثوانٍ في هذه الوحدة ونحو 2 ثانية إضافية على الوحدات الصغيرة التي يتم تجميعها بالتوازي.


في الوقت نفسه ، يبلغ إجمالي وقت تنفيذ جميع مهام kapt * لزيادة تعطل معالجة التعليقات التوضيحية دقيقة و 36 ثانية مقابل 55 ثانية عند تشغيله. وهذا هو ، دون الأخذ بعين الاعتبار التجميع الموازي للوحدات النمطية ، يكون الكسب أكبر.


تجدر الإشارة أيضًا إلى أن نتائج الاختبار المذكورة أعلاه تم إعدادها على بيئة CI مع إمكانية تشغيل 24 مؤشر ترابط متوازي للتجميع. في بيئة ذات 8 خيوط ، فإن المكسب من تمكين المعالجة التوضيحية التكميلية حوالي 20-30 ثانية في مشروعنا.


تزايدي مقابل (؟) الموازي


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


يؤدي


  • سمح لنا إدراج المعالجة التوضيحية التوضيحية في مشروعنا بتحقيق زيادة بنسبة 20 ٪ في سرعة إعادة البناء المحلية
  • لتمكين معالجة التعليقات التوضيحية الإضافية ، سيكون من المفيد دراسة السجل الكامل للتجميعات الحالية والبحث عن رسائل التحذير مع النص "معالجة التعليقات التوضيحية الإضافية مطلوبة ، ولكن يتم تعطيل الدعم لأن المعالجات التالية ليست تدريجية ...". من الضروري ترقية إصدارات المكتبات إلى إصدارات مع دعم للمعالجة الإضافية للتعليقات التوضيحية ولديك إصدارات Gradle 4.7+ و Kotlin 1.3.30+

المواد وماذا تقرأ عن الموضوع


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


All Articles