تمكّن فريق وقت تشغيل Android (ART) من تقليل وقت التجميع بنسبة %18 بدون التأثير في الرمز البرمجي المجمَّع أو حدوث أي تراجعات في ذروة الذاكرة. كان هذا التحسين جزءًا من مبادرتنا لعام 2025 التي تهدف إلى تحسين وقت التجميع بدون التأثير في استخدام الذاكرة أو جودة الرمز البرمجي المجمَّع.
يُعدّ تحسين سرعة وقت التجميع أمرًا بالغ الأهمية لـ ART. على سبيل المثال، عند التجميع أثناء التنفيذ (JIT)، يؤثر ذلك مباشرةً في كفاءة التطبيقات وأداء الجهاز بشكل عام. تؤدي عمليات التجميع الأسرع إلى تقليل الوقت المستغرَق قبل بدء التحسينات، ما يؤدي إلى تجربة مستخدم أكثر سلاسة واستجابة. بالإضافة إلى ذلك، بالنسبة إلى كل من JIT وAOT، فإنّ التحسينات في سرعة وقت التجميع تؤدي إلى تقليل استهلاك الموارد أثناء عملية التجميع، ما يفيد عمر البطارية ودرجة حرارة الجهاز، خاصةً على الأجهزة المنخفضة المواصفات.
تم إطلاق بعض هذه التحسينات في سرعة وقت التجميع في إصدار Android لشهر يونيو 2025، وستتوفّر بقية التحسينات في إصدار Android الذي سيتم طرحه في نهاية العام. بالإضافة إلى ذلك، يمكن لجميع مستخدمي Android على الإصدار 12 والإصدارات الأحدث تلقّي هذه التحسينات من خلال تحديثات Mainline.
تحسين برنامج التجميع المحسِّن
إنّ تحسين برنامج التجميع هو دائمًا لعبة مفاضلات. لا يمكنك الحصول على السرعة مجانًا، بل عليك التضحية بشيء ما. لقد وضعنا لأنفسنا هدفًا واضحًا وصعبًا للغاية: جعل برنامج التجميع أسرع، ولكن بدون التسبب في تراجعات في الذاكرة، والأهم من ذلك، بدون تدهور جودة الرمز الذي ينتجه. إذا كان برنامج التجميع أسرع ولكن التطبيقات تعمل بشكل أبطأ، فهذا يعني أنّنا فشلنا.
المورد الوحيد الذي كنا على استعداد لإنفاقه هو وقت التطوير الخاص بنا للتعمق والتحقيق وإيجاد حلول ذكية تلبي هذه المعايير الصارمة. لنلقِ نظرة عن كثب على كيفية عملنا للعثور على الجوانب التي يمكن تحسينها، بالإضافة إلى إيجاد الحلول المناسبة للمشاكل المختلفة.
العثور على تحسينات محتملة مفيدة
قبل أن تتمكّن من البدء في تحسين مقياس معيّن، يجب أن تكون قادرًا على قياسه. وإلا، فلن تتمكن أبدًا من التأكد مما إذا كنت قد قمت بتحسينه أم لا. لحسن الحظ، تكون سرعة وقت التجميع متسقة إلى حد ما طالما أنك تتخذ بعض الاحتياطات مثل استخدام نفس الجهاز الذي تستخدمه للقياس قبل التغيير وبعده، والتأكد من عدم خنق جهازك حراريًا. بالإضافة إلى ذلك، لدينا أيضًا قياسات حتمية مثل إحصاءات برامج التجميع التي تساعدنا في فهم ما يحدث في الخلفية.
بما أنّ المورد الذي كنا نضحّي به من أجل هذه التحسينات هو وقت التطوير، أردنا أن نتمكّن من تكرار المحاولات بأسرع ما يمكن. وهذا يعني أننا اخترنا مجموعة من التطبيقات التمثيلية (مزيج من تطبيقات الطرف الأول وتطبيقات الطرف الثالث ونظام التشغيل Android نفسه) لإنشاء نماذج أولية للحلول. بعد ذلك، تأكّدنا من أنّ التنفيذ النهائي كان يستحق العناء من خلال إجراء اختبارات يدوية وآلية على نطاق واسع.
باستخدام هذه المجموعة من ملفات APK التي تم اختيارها بعناية، سنبدأ عملية تجميع يدوية على الجهاز، ونحصل على ملف شخصي للتجميع، ونستخدم pprof لتصوّر الوقت الذي نقضيه.
مثال على رسم بياني لهب لملف شخصي في pprof
تُعدّ أداة pprof فعّالة جدًا وتتيح لنا تقسيم البيانات وفلترتها وترتيبها لمعرفة، على سبيل المثال، مراحل أو طرق برنامج التجميع التي تستغرق معظم الوقت. لن نتناول تفاصيل pprof نفسها، ولكن عليك معرفة أنّه إذا كان الشريط أكبر، يعني ذلك أنّ عملية التجميع استغرقت وقتًا أطول.
إحدى طرق العرض هذه هي طريقة العرض "من الأسفل إلى الأعلى" حيث يمكنك معرفة الطرق التي تستغرق معظم الوقت. في الصورة أدناه، يمكننا رؤية طريقة تسمى Kill، تمثل أكثر من 1% من وقت التجميع. سنناقش أيضًا بعض الطرق الأخرى الأكثر فعالية لاحقًا في منشور المدوّنة هذا.
عرض من الأسفل إلى الأعلى لملف شخصي
في برنامج التجميع المحسِّن، هناك مرحلة تُسمى "ترقيم القيم العامة" (GVN). لا داعي للقلق بشأن ما تفعله ككل، ولكن الجزء ذي الصلة هو معرفة أن لديها طلب إجراء يسمى "Kill" والذي ستحذف بعض العقد وفقًا لمرشح. يستغرق ذلك وقتًا طويلاً لأنّه يجب تكرار العملية على جميع العُقد والتحقّق منها واحدة تلو الأخرى. لاحظنا أنّ هناك بعض الحالات التي نعرف فيها مسبقًا أنّ عملية التحقّق ستكون خاطئة، بغض النظر عن العُقد النشطة في تلك المرحلة. في هذه الحالات، يمكننا تخطّي التكرار تمامًا، ما يؤدي إلى خفضه من %1.023 إلى %0.3 تقريبًا وتحسين وقت تشغيل GVN بنسبة %15 تقريبًا.
تنفيذ تحسينات مفيدة
لقد تناولنا كيفية قياس الوقت المستغرَق وتحديد أين يتم إنفاقه، ولكن هذه ليست سوى البداية. الخطوة التالية هي كيفية تحسين الوقت الذي يتم قضاؤه في التجميع.
عادةً، في حالة مثل حالة `Kill` أعلاه، سنلقي نظرة على كيفية تكرارنا للعُقد والقيام بذلك بشكل أسرع، على سبيل المثال، القيام بالأشياء بالتوازي أو تحسين الخوارزمية نفسها. في الواقع، هذا ما حاولنا فعله في البداية، وعندما لم نجد أي شيء نفعله، توقفنا للحظة وأدركنا أنّ الحل هو (في بعض الحالات) عدم التكرار على الإطلاق! عند إجراء هذه الأنواع من التحسينات، من السهل تفويت الغابة من أجل الأشجار.
في حالات أخرى، استخدمنا مجموعة من الأساليب المختلفة، بما في ذلك:
- استخدام الأساليب الإرشادية لتحديد ما إذا كانت عملية التحسين ستفشل في تحقيق نتائج مفيدة وبالتالي يمكن تخطّيها
- باستخدام هياكل بيانات إضافية لتخزين البيانات المحسوبة مؤقتًا
- تغيير هياكل البيانات الحالية لزيادة السرعة
- حساب النتائج بشكل كسول لتجنب الدورات في بعض الحالات
- استخدام التجريد الصحيح - يمكن أن تؤدي الميزات غير الضرورية إلى إبطاء الرمز
- وتجنُّب تتبُّع مؤشر مستخدَم بشكل متكرر من خلال العديد من عمليات التحميل
كيف نعرف ما إذا كانت التحسينات تستحق المتابعة؟
هذا هو الجزء الرائع، ليس عليك ذلك. بعد اكتشاف أنّ جزءًا من الرمز البرمجي يستغرق وقتًا طويلاً في التجميع، وبعد تخصيص وقت للتطوير من أجل تحسينه، قد لا تتمكّن أحيانًا من العثور على حلّ. قد لا يكون هناك ما يمكن فعله، أو قد يستغرق تنفيذ التحسين وقتًا طويلاً، أو قد يؤدي إلى تراجع مقياس آخر بشكل كبير، أو زيادة تعقيد قاعدة الرموز البرمجية، وما إلى ذلك. مقابل كل تحسين ناجح يمكنك الاطّلاع عليه في منشور المدوّنة هذا، هناك عدد لا يحصى من التحسينات الأخرى التي لم تؤتِ ثمارها.
إذا كنت في موقف مشابه، فحاول تقدير مقدار تحسين المقياس عن طريق القيام بأقل قدر ممكن من العمل. ويعني ذلك بالترتيب:
- التقدير باستخدام المقاييس التي جمعتها بالفعل، أو مجرد شعور داخلي
- التقدير باستخدام نموذج أوّلي سريع وبسيط
- نفِّذ الحلّ.
لا تنسَ تقدير عيوب الحل الذي توصّلت إليه. على سبيل المثال، إذا كنت ستعتمد على هياكل بيانات إضافية، فما مقدار الذاكرة التي ترغب في استخدامها؟
نظرة مفصَّلة
بدون مزيد من التفاصيل، لنلقِ نظرة على بعض التغييرات التي أجريناها.
أجرينا تغييرًا لتحسين طريقة تُسمى 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]
إذا تحدثنا بالتفصيل عن جميع التحسينات، فسنبقى هنا طوال اليوم! إذا كنت مهتمًا ببعض التحسينات الإضافية، يمكنك الاطّلاع على بعض التغييرات الأخرى التي أجريناها:
- أضِف ميزة "مسك الدفاتر" لتحسين أوقات التجميع بنسبة تتراوح بين %0.6 و%1.6 تقريبًا.
- احتسِب البيانات بشكل كسول لتجنُّب الدورات، إن أمكن.
- إعادة تصميم الرمز البرمجي لتخطّي عملية الحوسبة المسبقة عندما لا يتم استخدامها
- تجنَّب بعض سلاسل التحميل التابعة عندما يكون من السهل الحصول على المخصِّص من أماكن أخرى.
- هذه حالة أخرى من إضافة عملية تحقّق لتجنُّب العمل غير الضروري.
- تجنُّب التفرّع المتكرر في نوع السجلّ (الأساسي/FP) في أداة تخصيص السجلّات
- تأكَّد من تهيئة بعض المصفوفات في وقت التجميع. لا تعتمد على clang لتنفيذ ذلك.
- إزالة بعض التكرارات استخدِم حلقات النطاق التي يمكن لـ clang تحسينها بشكل أفضل لأنّها لا تحتاج إلى إعادة تحميل المؤشرات الداخلية للحاوية بسبب الآثار الجانبية للحلقة. تجنَّب استدعاء الدالة الافتراضية `HInstruction::GetInputRecords()` في الحلقة من خلال الدالة المضمّنة `InputAt(.)` لكل إدخال.
- تجنُّب دوال Accept() لنمط الزائر من خلال الاستفادة من تحسين برنامج التجميع
الخاتمة
لقد أدى التزامنا بتحسين سرعة وقت التجميع في ART إلى تحسينات كبيرة، ما جعل Android أكثر سلاسة وكفاءة مع المساهمة أيضًا في تحسين عمر البطارية ودرجة حرارة الجهاز. من خلال تحديد التحسينات وتنفيذها بدقة، أثبتنا أنّه من الممكن تحقيق مكاسب كبيرة في وقت التجميع بدون التأثير سلبًا في استخدام الذاكرة أو جودة الرمز البرمجي.
تضمّنت رحلتنا إنشاء ملفات شخصية باستخدام أدوات مثل pprof، والاستعداد للتكرار، وحتى التخلي أحيانًا عن المسارات الأقل فائدة. لم تقتصر الجهود الجماعية لفريق ART على تقليل وقت التجميع بنسبة ملحوظة، بل وضعت أيضًا الأساس للتطوّرات المستقبلية.
تتوفّر كل هذه التحسينات في تحديث Android لنهاية عام 2025، وللإصدار Android 12 والإصدارات الأحدث من خلال تحديثات Mainline. نأمل أن تكون هذه النظرة المتعمّقة في عملية التحسين قد قدّمت لك معلومات قيّمة حول تعقيدات هندسة برامج التجميع ومزاياها.
-
أخبار المنتجاتبما أنّ Unity تشغّل أكثر من نصف جميع الألعاب على الأجهزة الجوّالة في جميع أنحاء العالم، فإنّ الانطباعات الأولى مهمة. بالنسبة إلى مطوّري الألعاب، تُعدّ تجربة الإعداد السلسة والموثوقة للاعبين ضرورية لتعزيز معدّل الاحتفاظ باللاعبين من اليوم الأول وزيادة تفاعلهم على المدى الطويل.
Michelle Martin • مدّة القراءة: دقيقة واحدة -
أخبار المنتجاتمن خلال تطوير التطبيقات على Android XR، يمكنك تلبية احتياجات المستخدمين في أي وقت، سواء كانوا يبحثون عن تجربة غامرة بالكامل في المنزل أو تجربة سريعة أثناء التنقل، وذلك من خلال مجموعة واسعة من التطبيقات المكانية. في مؤتمر Android Dev Summit، احتفلنا بإنجاز رائع من خلال إطلاق نظارة XREAL AURA.
Amy Zeppenfeld, Vinny DaSilva • مدّة القراءة: 3 دقائق -
أخبار المنتجاتفي عصر الذكاء الاصطناعي، لم يعُد المستخدمون يكتفون بالنقر على التطبيقات، بل يعبّرون عن أهداف عالية المستوى ويتوقّعون من وكلاء الذكاء الاصطناعي، مثل Gemini، إكمال مهام متعددة الخطوات، أحيانًا على تطبيقات متعددة على أجهزتهم.
Jingyu Shi • مدّة القراءة: 4 دقائق
يمكنك الحصول على آخر الإحصاءات حول تطوير تطبيقات Android في بريدك الوارد أسبوعيًا.