ابزارهای ساخت اندروید به‌تازگی برای Kotlin Coroutines با افزایش سرعت همراه شده‌اند. با استفاده از Android Gradle Plugin (AGP) 9.2.0 و ابزار کوچک‌سازی R8 که همراه آن ارائه می‌شود، بایت‌کدی که مدیریت وضعیت کوروتین‌ها را بر عهده دارد، بازنویسی شده است تا در رایج‌ترین عملیات‌های کوروتین، تقریباً دو برابر عملکرد بهتری ارائه دهد. توسعه‌دهندگانی که اپلیکیشن‌های اندرویدی مبتنی بر کوروتین عرضه می‌کنند، می‌توانند بدون تغییر حتی یک خط از کد Kotlin، شاهد زمان‌های اجرای اولیه (cold-start) سریع‌تر، نرخ فریم پایدارتر و بهبود اندکی در عمر باتری باشند.

چرا عملکرد کوروتین اهمیت داشت

Kotlin Coroutines از اشیاء AtomicFieldUpdater برای تغییر ایمن فیلدهای داخلی از چندین رشته (thread) استفاده می‌کنند. این رویکرد از تخصیص یک شیء اتمیک جدید برای هر به‌روزرسانی جلوگیری می‌کند، اما سه هزینه پنهان را در اندروید ایجاد می‌کند:

  • تأخیر در بارگذاری کلاس (Class-loading delay) – آپدیتِر به‌صورت بازتابی (reflective) ایجاد می‌شود، بنابراین ماشین مجازی (VM) باید فیلد هدف را در زمان اجرا جستجو کند.
  • اتلاف منابع CPU – هر دسترسی، پیش از رسیدن به فیلد اصلی، وضعیت آپدیتِر را بررسی می‌کند.
  • محدودیت‌های Inlining – کامپایلر نمی‌تواند فراخوانی‌های آپدیتِر را inline کند، که باعث می‌شود بایت‌کد تولید شده بزرگ‌تر و کندتر باشد.

در عمل، این هزینه‌ها در مسیرهای پرکاربرد (hot paths) مانند ارسال/دریافت در channel، قفل/باز کردن mutex، به‌روزرسانی‌های StateFlow و دیسپچ کردن (dispatch) کوروتین ظاهر می‌شوند. نتیجه این است که مقدار قابل توجهی از زمان CPU صرف کارهای جانبی مدیریت وضعیت (bookkeeping) می‌شود تا انجام کار اصلی اپلیکیشن.

چه تغییری در AGP 9.2.0 و R8 ایجاد شد

R8، ابزار کوچک‌سازی کد که همراه با AGP 9.2.0 ارائه می‌شود، اکنون بایت‌کد کامپایل‌شده را برای الگوی استاندارد AtomicFieldUpdater اسکن می‌کند. وقتی این الگو را پیدا می‌کند، چهار تغییر را انجام می‌دهد:

  1. شناسایی آفست حافظه فیلد. R8 مکان دقیق فیلد هدف را در ساختار شیء محاسبه می‌کند.
  2. حذف شیء آپدیتِر. پوشش بازتابی (reflective wrapper) حذف می‌شود که باعث صرفه‌جویی در حافظه و از بین رفتن عملیات بارگذاری کلاس می‌شود.
  3. درج یک فراخوانی مستقیم sun.misc.Unsafe. این API سطح پایین، با استفاده از یک دستور واحد سخت‌افزاری اتمیک، در فیلد می‌نویسد.
  4. جایگزینی هر فراخوانی آپدیتِر با دستور جدید unsafe، که به کامپایلر JIT اجازه می‌دهد عملیات را inline کند.

اثر نهایی این است که CPU دیگر نیازی به انجام جستجوی بازتابی یا بررسی‌های زمان اجرا ندارد؛ بلکه دستور اتمیک را مستقیماً اجرا می‌کند. از دیدگاه توسعه‌دهنده، این تغییر نامرئی است – API کوروتین همان رفتار قبلی را دارد – اما در پشت صحنه، کد با سرعت «در سطح سخت‌افزار» (metal-level) اجرا می‌شود.

دستاوردهای قابل اندازه‌گیری

بنچمارک‌ها در یک دستگاه اندرویدی معمولی، افزایش سرعت زیر را پس از ساخت با AGP 9.2.0، فعال بودن R8 و روشن بودن minification نشان می‌دهند:

  • ارسال/دریافت در Channel: 2.01 × سریع‌تر
  • قفل/باز کردن Mutex: 1.90 × سریع‌تر
  • به‌روزرسانی‌های StateFlow: 2.02 × سریع‌تر
  • دیسپچ کردن کوروتین: 1.68 × سریع‌تر

این اعداد به بهبودهای ملموس در تجربه کاربری ترجمه می‌شوند. یک اجرای اولیه (cold launch) که بخشی از ثانیه را صرف انتظار برای همگام‌سازی کوروتین می‌کرد، اکنون زودتر تمام می‌شود و فضای بیشتری برای رندر کردن اولین فریم به رشته UI می‌دهد. کاهش رقابت (contention) در CPU همچنین اجازه می‌دهد پردازنده سریع‌تر به حالت خواب (sleep) بازگردد که می‌تواند عمر باتری را بهبود بخشد.

چگونه از این مزایا بهره‌مند شویم

هیچ تغییر کدی لازم نیست. برای فعال‌سازی این بازنویسی، شما به موارد زیر نیاز دارید:

  • AGP 9.2.0 یا جدیدتر – نسخه‌ای که حاوی R8 به‌روزرسانی شده است.
  • R8 – که هنگام ساخت با AGP فوق به‌طور خودکار استفاده می‌شود.
  • Kotlin Coroutines 1.8.0+ – نسخه کتابخانه‌ای که دارای الگوی AtomicFieldUpdater مورد انتظارِ بهینه‌ساز است.
  • isMinifyEnabled = true در نوع ساخت release – R8 تنها زمانی اجرا می‌شود که minification روشن باشد.

تنها مرحله اضافی، بررسی قوانین ProGuard (یا R8) شماست. دستورات گسترده -keep که فیلدهای volatile یا خود کلاس‌های آپدیتِر را حفظ می‌کنند، مانع از بازنویسی می‌شوند. مطمئن شوید که قوانین به R8 اجازه می‌دهند آن فیلدها را تغییر دهد؛ در غیر این صورت، بهینه‌ساز به پیاده‌سازی بازتابی اصلی باز می‌گردد.

می‌توانید این تغییر را با APK Analyzer در Android Studio تأیید کنید. فایل APK کامپایل‌شده را باز کنید، یک کلاس پشتیبان کوروتین مانند JobSupport را پیدا کنید و بایت‌کد دکامپایل‌شده را بررسی کنید. اگر بازنویسی با موفقیت انجام شده باشد، فیلدهای استاتیک آپدیتِر وجود نخواهند داشت و به‌جای آن‌ها فراخوانی‌های مستقیم به Unsafe را خواهید دید.

ملاحظات و نکات متقابل

این بهینه‌سازی به دو شرط بستگی دارد که هر پروژه‌ای آن‌ها را برآورده نمی‌کند:

  1. Minification باید فعال باشد. نسخه‌های Debug، یا نسخه‌های Release که برای راحتی در دیباگ کردن Minification را غیرفعال نگه می‌دارند، از این مزیت بهره‌مند نخواهند شد.
  2. قوانین ProGuard باید منعطف باشند. پروژه‌هایی که از الگوهای سخت‌گیرانه -keep برای بخش‌های داخلی coroutine استفاده می‌کنند، ممکن است نیاز به تسهیل این قوانین داشته باشند؛ این امر اگر با دقت تست نشود، می‌تواند کلاس‌های داخلی را در معرض باگ‌های مربوط به shrinking قرار دهد.

همچنان توصیه می‌شود که روی طیف وسیعی از دستگاه‌های هدف خود تست انجام دهید.

گام‌های بعدی

این بازنویسی نشان می‌دهد که چگونه تغییرات بایت‌کد در زمان بیلد (build-time) می‌تواند عملکردی را که پشت انتزاع‌های زبانی پنهان شده است، استخراج کند. برای تأیید میزان بهبود در بار کاری (workload) خاص خود، پروفایلینگ (profiling) کدهایی را که به شدت از coroutine استفاده می‌کنند، در نظر بگیرید.

نکته کلیدی: ارتقا به AGP 9.2.0 و فعال کردن minification در R8، سرعت همگام‌سازی‌های حیاتی را در اپلیکیشن‌های مبتنی بر Kotlin coroutine تقریباً دو برابر می‌کند؛ تمام این‌ها بدون دستکاری در کد منبع انجام می‌شود — به شرطی که پیکربندی بیلد اجازه دهد بهینه‌ساز (optimizer) کار خود را انجام دهد.