أخبار المنتجات

تجميع أسرع بنسبة %18 بدون أي تنازلات

مدّة القراءة: 8 دقائق

تمكّن فريق وقت تشغيل Android (ART) من تقليل وقت التجميع بنسبة% 18 بدون التأثير في الرمز البرمجي المجمَّع أو حدوث أي تراجعات في ذروة الذاكرة. كان هذا التحسين جزءًا من مبادرتنا لعام 2025 لتحسين وقت التجميع بدون التأثير في استخدام الذاكرة أو جودة الرمز البرمجي المجمَّع.

يُعدّ تحسين سرعة وقت التجميع أمرًا بالغ الأهمية لـ ART. على سبيل المثال، عند التجميع أثناء التنفيذ (JIT)، يؤثر ذلك مباشرةً في كفاءة التطبيقات وأداء الجهاز بشكل عام. وتؤدي عمليات التجميع الأسرع إلى تقليل الوقت المستغرَق قبل بدء التحسينات، ما يؤدي إلى تجربة مستخدم أكثر سلاسة واستجابة. علاوةً على ذلك، بالنسبة إلى كل من JIT وAOT، فإنّ التحسينات في سرعة وقت التجميع تؤدي إلى تقليل استهلاك الموارد أثناء عملية التجميع، ما يفيد عمر البطارية ودرجة حرارة الجهاز، خاصةً على الأجهزة المنخفضة المواصفات.

تم إطلاق بعض هذه التحسينات على سرعة وقت التجميع في إصدار Android لشهر يونيو 2025، وستتوفّر بقية التحسينات في إصدار Android الذي سيتم طرحه في نهاية العام. بالإضافة إلى ذلك، يمكن لجميع مستخدمي Android على الإصدار 12 والإصدارات الأحدث الاستفادة من هذه التحسينات من خلال تحديثات Mainline.

تحسين برنامج التجميع المحسِّن

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

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

العثور على تحسينات محتملة مفيدة

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

 

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

 

باستخدام هذه المجموعة من ملفات APK المحدّدة يدويًا، سنبدأ عملية تجميع يدوية على الجهاز، ونحصل على ملف شخصي للتجميع، ونستخدم pprof لتصوّر الوقت الذي نقضيه.

image.png

مثال على رسم بياني لهيكل الملف الشخصي في pprof

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

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

image.png

عرض من الأسفل إلى الأعلى لملف شخصي

في برنامج التجميع المحسّن، هناك مرحلة تُسمّى "ترقيم القيم العامة" (GVN). لا داعي للقلق بشأن ما تفعله ككل، ولكن الجزء ذي الصلة هو معرفة أن لديها طلب إجراء يسمى "Kill" والذي ستحذف بعض العقد وفقًا لمرشح. يستغرق هذا وقتًا طويلاً لأنّه يجب تكرار العملية على جميع العُقد والتحقّق منها واحدة تلو الأخرى. لاحظنا أن هناك بعض الحالات التي نعرف فيها مسبقًا أن التحقق سيكون false، بغض النظر عن العُقد التي لدينا في تلك المرحلة. في هذه الحالات، يمكننا تخطّي التكرار تمامًا، ما يؤدي إلى خفضه من% 1.023 إلى% 0.3 تقريبًا وتحسين وقت تشغيل GVN بنسبة %15 تقريبًا.

تنفيذ تحسينات جديرة بالاهتمام

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

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

في حالات أخرى، استخدمنا مجموعة من الأساليب المختلفة، بما في ذلك:

  • استخدام الأساليب الإرشادية لتحديد ما إذا كانت عملية التحسين ستفشل في تحقيق نتائج مفيدة وبالتالي يمكن تخطّيها
  • باستخدام هياكل بيانات إضافية لتخزين البيانات المحسوبة مؤقتًا
  • تغيير هياكل البيانات الحالية لزيادة السرعة
  • يتم احتساب النتائج ببطء لتجنُّب التكرار في بعض الحالات
  • استخدام التجريد الصحيح - يمكن أن تؤدي الميزات غير الضرورية إلى إبطاء الرمز
  • تجنُّب تتبُّع مؤشر مستخدَم بشكل متكرر من خلال عمليات تحميل متعددة

كيف نعرف ما إذا كانت التحسينات تستحق المتابعة؟

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

إذا كنت في موقف مشابه، فحاول تقدير مقدار ما ستحسنه من المقياس عن طريق القيام بأقل قدر ممكن من العمل. ويعني ذلك بالترتيب:

  1. التقدير باستخدام المقاييس التي جمعتها بالفعل، أو مجرد شعور حدسي
  2. التقدير باستخدام نموذج أوّلي سريع وبسيط
  3. نفِّذ الحلّ.

لا تنسَ تقدير عيوب الحل الذي توصّلت إليه. على سبيل المثال، إذا كنت ستعتمد على هياكل بيانات إضافية، فما مقدار الذاكرة التي ترغب في استخدامها؟

نظرة مفصَّلة

بدون مقدمات، لنلقِ نظرة على بعض التغييرات التي أجريناها.

أجرينا تغييرًا لتحسين طريقة تُسمى FindReferenceInfoOf. كانت هذه الطريقة تُجري بحثًا خطيًا عن متجه للعثور على إدخال. عدّلنا بنية البيانات هذه لتتم فهرستها حسب معرّف التعليمات لكي تصبح FindReferenceInfoOf‏ O(1) بدلاً من O(n). أيضًا، قمنا بتخصيص المتجه مسبقًا لتجنب تغيير الحجم. لقد زدنا الذاكرة قليلاً لأنّنا اضطررنا إلى إضافة حقل إضافي يحسب عدد الإدخالات التي أدرجناها في المتجه، ولكنّها كانت تضحية صغيرة لأنّ ذروة الذاكرة لم تزد. أدّى ذلك إلى تسريع مرحلة LoadStoreAnalysis بنسبة تتراوح بين %34 و%66، ما أدّى بدوره إلى تحسين وقت التجميع بنسبة تتراوح بين %0.5 و%1.8 تقريبًا.

لدينا تنفيذ مخصّص لـ HashSet نستخدمه في عدة أماكن. كان إنشاء بنية البيانات هذه يستغرق وقتًا طويلاً، وقد اكتشفنا السبب. قبل عدة سنوات، كانت بنية البيانات هذه تُستخدَم في أماكن قليلة فقط تستخدم مجموعات HashSet كبيرة جدًا، وتم تعديلها لتحسينها لهذا الغرض. ومع ذلك، يتم استخدامها في الوقت الحالي في الاتجاه المعاكس مع عدد قليل من الإدخالات وعمر قصير. وهذا يعني أنّنا كنا نُهدر دورات المعالجة من خلال إنشاء HashSet الضخمة هذه، ولكننا استخدمناها فقط لعدد قليل من الإدخالات قبل تجاهلها. من خلال هذا التغيير، تم تحسين وقت التجميع بنسبة تتراوح بين %1.3 و%2 تقريبًا. وكميزة إضافية، انخفض استخدام الذاكرة بنسبة تتراوح بين %0.5 و%1 تقريبًا لأنّنا لم نستخدم هياكل بيانات كبيرة كما في السابق.

لقد حسّنّا وقت التجميع بنسبة تتراوح بين %0.5 و%1 تقريبًا من خلال تمرير بُنى البيانات حسب المرجع إلى دالة lambda لتجنُّب نسخها. لم يتم رصد هذه الثغرة في المراجعة الأصلية، وظلّت في قاعدة الرموز البرمجية لسنوات. بفضل إلقاء نظرة على الملفات الشخصية في pprof، لاحظنا أنّ هذه الطرق كانت تنشئ وتدمّر الكثير من بُنى البيانات، ما دفعنا إلى التحقيق فيها وتحسينها.

لقد سرّعنا المرحلة التي تكتب الناتج المجمّع من خلال تخزين القيم المحسوبة مؤقتًا، ما أدى إلى تحسين إجمالي وقت التجميع بنسبة تتراوح بين %1.3 و%2.8 تقريبًا. للأسف، كانت عمليات تتبُّع البيانات الإضافية كثيرة جدًا، ونبّهتنا الاختبارات الآلية إلى تراجع أداء الذاكرة. بعد ذلك، ألقينا نظرة ثانية على الرمز نفسه ونفّذنا إصدارًا جديدًا لم يعالج مشكلة تراجع الذاكرة فحسب، بل حسّن أيضًا وقت التجميع بنسبة تتراوح بين %0.5 و%1.8 تقريبًا. في هذا التغيير الثاني، كان علينا إعادة هيكلة هذه المرحلة وإعادة تصوّرها للتخلص من إحدى بنيتَي البيانات.

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

نقلنا عمليتَي تحقّق من فئة "عمليات التحقّق النهائية" إلى فئة "الاستدلال" لتقدير ما إذا كانت عملية التضمين ستنجح أم لا قبل إجراء أي عملية حسابية تستغرق وقتًا طويلاً. بما أنّ هذا تقدير، فهو ليس مثاليًا، ولكنّنا تأكّدنا من أنّ الأساليب الإرشادية الجديدة تغطي% 99.9 مما تم تضمينه سابقًا بدون التأثير في الأداء. كان أحد هذه الأساليب الإرشادية الجديدة يتعلّق بسجلات DEX المطلوبة (تحسُّن بنسبة تتراوح بين ‎0.2% و‎1.3% تقريبًا)، والآخر يتعلّق بعدد التعليمات (تحسُّن بنسبة ‎2% تقريبًا).

لدينا تنفيذ مخصّص لـ BitVector نستخدمه في عدة أماكن. استبدلنا فئة BitVector القابلة لتغيير الحجم بفئة BitVectorView أبسط لبعض متجهات البت ذات الحجم الثابت. يؤدي ذلك إلى التخلص من بعض عمليات التحقق غير المباشرة والتحقق من نطاق وقت التشغيل ويسرّع إنشاء كائنات متجهات البت.

بالإضافة إلى ذلك، تم إنشاء نموذج لفئة BitVectorView على نوع مساحة التخزين الأساسية (بدلاً من استخدام uint32_t دائمًا كـ BitVector القديم). يسمح ذلك لبعض العمليات، مثل Union()‎، بمعالجة ضعف عدد وحدات البت معًا على الأنظمة الأساسية التي تعمل بنظام التشغيل 64 بت. تم تقليل عيّنات الدوال المتأثرة بأكثر من% 1 في المجموع عند تجميع نظام التشغيل Android. تم تنفيذ ذلك من خلال عدة تغييرات [1، 2، 3، 4، 5، 6]

إذا تحدثنا بالتفصيل عن جميع التحسينات، فسنبقى هنا طوال اليوم! إذا كنت مهتمًا بإجراء المزيد من التحسينات، يمكنك الاطّلاع على بعض التغييرات الأخرى التي نفّذناها:

الخاتمة

لقد أدى التزامنا بتحسين سرعة وقت التجميع في ART إلى تحسينات كبيرة، ما جعل Android أكثر سلاسة وكفاءة، وساهم أيضًا في تحسين عمر البطارية ودرجة حرارة الجهاز. من خلال تحديد التحسينات وتنفيذها بعناية، أثبتنا أنّه من الممكن تحقيق مكاسب كبيرة في وقت التجميع بدون التأثير سلبًا في استخدام الذاكرة أو جودة الرمز البرمجي.

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

تتوفّر كل هذه التحسينات في تحديث Android لنهاية عام 2025، وللإصدار Android 12 والإصدارات الأحدث من خلال تحديثات Mainline. نأمل أن تكون هذه النظرة المتعمّقة في عملية التحسين قد قدّمت إحصاءات قيّمة حول تعقيدات هندسة برامج التجميع ومزاياها.

متابعة القراءة