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

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

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

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

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

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

تحسين المحسِّن المُحسِّن

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

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

العثور على التحسينات المحتمَلة القيّمة

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

 

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

 

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

image.png

مثال على الرسم البياني الناري لملف شخصي في أداة pprof

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

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

image.png

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

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

تنفيذ التحسينات القيّمة

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

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

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

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

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

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

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

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

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

نظرة مفصَّلة

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

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

لدينا تنفيذ مخصّص لـ HashSet نستخدمه في عدة أماكن. كان إنشاء هيكل البيانات هذا يستغرق وقتًا طويلاً، وقد عرفنا السبب. قبل عدة سنوات، كان هيكل البيانات هذا يُستخدم في عدد قليل فقط من الأماكن التي كانت تستخدم مجموعات HashSets كبيرة جدًا، وتم تعديله ليتم تحسينه من أجل ذلك. ومع ذلك، كان يُستخدم حاليًا في الاتجاه المعاكس مع عدد قليل فقط من الإدخالات وعمر قصير. وهذا يعني أنّنا كنّا نضيّع الدورات من خلال إنشاء مجموعة 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. تم إجراء ذلك من خلال عدة تغييرات [123456]

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

الخاتمة

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

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

تتوفّر كل هذه التحسينات في تحديث نهاية العام 2025 من Android، ولإصدار Android 12 والإصدارات الأحدث من خلال التحديثات الرئيسية. نأمل أن يقدّم هذا التحليل المتعمّق لعملية التحسين إحصاءات قيّمة حول تعقيدات هندسة المحسِّن ومكافآتها!

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