Android ਦੇ build tools ਨੂੰ Kotlin Coroutines ਲਈ ਇੱਕ speed-boost ਮਿਲਿਆ ਹੈ। Android Gradle Plugin (AGP) 9.2.0 ਅਤੇ ਇਸ ਦੇ ਨਾਲ ਆਉਂਦੇ R8 shrinker ਦੇ ਨਾਲ, coroutine state handling ਨੂੰ ਚਲਾਉਣ ਵਾਲਾ bytecode ਹੁਣ ਦੁਬਾਰਾ ਲਿਖਿਆ ਗਿਆ ਹੈ, ਤਾਂ ਜੋ ਸਭ ਤੋਂ ਆਮ coroutine operations ਵਿੱਚ ਲਗਭਗ ਦੁੱਗਣੀ performance ਮਿਲ ਸਕੇ। ਉਹ developers ਜੋ coroutines 'ਤੇ ਨਿਰਭਰ Android apps ਬਣਾਉਂਦੇ ਹਨ, ਉਹ Kotlin code ਦੀ ਇੱਕ ਲਾਈਨ ਬਦਲੇ ਬਿਨਾਂ ਤੇਜ਼ cold-start times, ਸਥਿਰ frame rates ਅਤੇ ਬੈਟਰੀ ਲਾਈਫ ਵਿੱਚ ਮਾਮੂਲੀ ਸੁਧਾਰ ਦੇਖ ਸਕਦੇ ਹਨ।

Coroutine performance ਕਿਉਂ ਮਹੱਤਵਪੂਰਨ ਸੀ

Kotlin Coroutines ਕਈ threads ਤੋਂ internal fields ਨੂੰ ਸੁਰੱਖਿਅਤ ਤਰੀਕੇ ਨਾਲ ਬਦਲਣ ਲਈ AtomicFieldUpdater objects ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹਨ। ਇਹ ਤਰੀਕਾ ਹਰ update ਲਈ ਇੱਕ ਨਵਾਂ atomic object ਬਣਾਉਣ ਤੋਂ ਬਚਦਾ ਹੈ, ਪਰ ਇਹ Android 'ਤੇ ਤਿੰਨ ਲੁਕਵੇਂ ਖਰਚੇ (costs) ਪੈਦਾ ਕਰਦਾ ਹੈ:

  • Class-loading delay – updater ਨੂੰ reflectively ਬਣਾਇਆ ਜਾਂਦਾ ਹੈ, ਇਸ ਲਈ VM ਨੂੰ runtime 'ਤੇ target field ਲੱਭਣਾ ਪੈਂਦਾ ਹੈ।
  • CPU waste – ਹਰ access ਅਸਲ field ਤੱਕ ਪਹੁੰਚਣ ਤੋਂ ਪਹਿਲਾਂ updater ਦੀ state ਦੀ ਜਾਂਚ ਕਰਦਾ ਹੈ।
  • Inlining limits – compiler updater calls ਨੂੰ inline ਨਹੀਂ ਕਰ ਸਕਦਾ, ਜਿਸ ਨਾਲ ਬਣਿਆ bytecode ਵੱਡਾ ਅਤੇ ਹੌਲੀ ਰਹਿੰਦਾ ਹੈ।

ਅਸਲ ਵਿੱਚ, ਇਹ ਖਰਚੇ channel send/receive, mutex lock/unlock, StateFlow updates ਅਤੇ coroutine dispatch ਵਰਗੇ hot paths ਦੌਰਾਨ ਸਾਹਮਣੇ ਆਉਂਦੇ ਹਨ। ਨਤੀਜੇ ਵਜੋਂ, CPU ਦਾ ਕਾਫ਼ੀ ਸਮਾਂ app ਦੇ ਆਪਣੇ ਕੰਮ ਦੀ ਬਜਾਏ bookkeeping 'ਤੇ ਖ਼ਰਚ ਹੁੰਦਾ ਹੈ।

AGP 9.2.0 ਅਤੇ R8 ਵਿੱਚ ਕੀ ਬਦਲਿਆ

R8, ਜੋ ਕਿ AGP 9.2.0 ਦੇ ਨਾਲ ਆਉਂਦਾ ਇੱਕ code-shrinker ਹੈ, ਹੁਣ standard AtomicFieldUpdater pattern ਲਈ compiled bytecode ਨੂੰ ਸਕੈਨ ਕਰਦਾ ਹੈ। ਜਦੋਂ ਇਸਨੂੰ ਇਹ ਮਿਲਦਾ ਹੈ, ਤਾਂ ਇਹ ਚਾਰ ਤਬਦੀਲੀਆਂ (transformations) ਕਰਦਾ ਹੈ:

  1. Field ਦੇ memory offset ਦੀ ਪਛਾਣ ਕਰਨਾ। R8 object layout ਦੇ ਅੰਦਰ target field ਦੀ ਸਹੀ ਲੋਕੇਸ਼ਨ ਦੀ ਗਣਨਾ ਕਰਦਾ ਹੈ।
  2. Updater object ਨੂੰ ਹਟਾਉਣਾ। Reflective wrapper ਖਤਮ ਹੋ ਜਾਂਦਾ ਹੈ, ਜਿਸ ਨਾਲ memory ਬਚਦੀ ਹੈ ਅਤੇ class-loading ਦਾ ਕੰਮ ਵੀ ਖਤਮ ਹੋ ਜਾਂਦਾ ਹੈ।
  3. ਇੱਕ ਸਿੱਧੀ sun.misc.Unsafe call ਸ਼ਾਮਲ ਕਰਨਾ। ਇਹ low-level API ਇੱਕ ਸਿੰਗਲ atomic hardware instruction ਦੀ ਵਰਤੋਂ ਕਰਕੇ field ਵਿੱਚ ਲਿਖਦੀ ਹੈ।
  4. ਹਰ updater call ਨੂੰ ਨਵੀਂ unsafe instruction ਨਾਲ ਬਦਲਣਾ, ਜਿਸ ਨਾਲ JIT compiler ਨੂੰ operation ਨੂੰ inline ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਮਿਲਦੀ ਹੈ।

ਇਸਦਾ ਅੰਤਿਮ ਪ੍ਰਭਾਵ ਇਹ ਹੈ ਕਿ CPU ਨੂੰ ਹੁਣ reflective lookup ਜਾਂ runtime checks ਕਰਨ ਦੀ ਲੋੜ ਨਹੀਂ ਪੈਂਦੀ; ਇਹ ਸਿੱਧੇ ਤੌਰ 'ਤੇ atomic instruction ਨੂੰ ਚਲਾਉਂਦਾ ਹੈ। Developer ਦੇ ਨਜ਼ਰੀਏ ਤੋਂ ਇਹ ਬਦਲਾਅ ਅਦਿੱਖ ਹੈ – coroutine API ਇੱਕੋ ਜਿਹੀ ਰਹਿੰਦੀ ਹੈ – ਪਰ ਅੰਦਰੂਨੀ ਤੌਰ 'ਤੇ ਕੋਡ "metal-level" ਦੀ ਰਫ਼ਤਾਰ ਨਾਲ ਚੱਲਦਾ ਹੈ।

ਮਾਪਣਯੋਗ ਲਾਭ (Measurable gains)

ਇੱਕ ਆਮ Android device 'ਤੇ benchmarks, AGP 9.2.0, R8 enabled ਅਤੇ minification on ਕਰਨ ਤੋਂ ਬਾਅਦ ਹੇਠ ਲਿਖੀ ਰਫ਼ਤਾਰ ਵਾਧਾ ਦਿਖਾਉਂਦੇ ਹਨ:

  • Channel send/receive: 2.01 × ਤੇਜ਼
  • Mutex lock/unlock: 1.90 × ਤੇਜ਼
  • StateFlow updates: 2.02 × ਤੇਜ਼
  • Coroutine dispatch: 1.68 × ਤੇਜ਼

ਇਹ ਅੰਕੜੇ ਉਪਭੋਗਤਾ ਅਨੁਭਵ (user-experience) ਵਿੱਚ ਮਹਿਸੂਸ ਕੀਤੇ ਜਾ ਸਕਣ ਵਾਲੇ ਸੁਧਾਰਾਂ ਵਿੱਚ ਬਦਲਦੇ ਹਨ। ਇੱਕ cold launch ਜੋ coroutine synchronisation ਦੀ ਉਡੀਕ ਵਿੱਚ ਇੱਕ ਸੈਕਿੰਡ ਦਾ ਕੁਝ ਹਿੱਸਾ ਖ਼ਰਚ ਕਰਦਾ ਸੀ, ਹੁਣ ਜਲਦੀ ਖ਼ਤਮ ਹੋ ਜਾਂਦਾ ਹੈ, ਜਿਸ ਨਾਲ UI thread ਨੂੰ ਪਹਿਲਾ frame render ਕਰਨ ਲਈ ਵਧੇਰੇ ਸਮਾਂ ਮਿਲਦਾ ਹੈ। CPU contention ਘੱਟ ਹੋਣ ਨਾਲ processor ਵੀ ਜਲਦੀ sleep mode ਵਿੱਚ ਜਾ ਸਕਦਾ ਹੈ, ਜੋ ਬੈਟਰੀ ਲਾਈਫ ਨੂੰ ਸੁਧਾਰ ਸਕਦਾ ਹੈ।

ਲਾਭ ਕਿਵੇਂ ਪ੍ਰਾਪਤ ਕਰੀਏ

ਕੋਈ ਕੋਡ ਬਦਲਾਅ ਕਰਨ ਦੀ ਲੋੜ ਨਹੀਂ ਹੈ। ਇਸ rewrite ਨੂੰ ਚਾਲੂ ਕਰਨ ਲਈ ਤੁਹਾਨੂੰ ਚਾਹੀਦਾ ਹੈ:

  • AGP 9.2.0 ਜਾਂ ਨਵਾਂ – ਉਹ version ਜਿਸ ਵਿੱਚ updated R8 ਸ਼ਾਮਲ ਹੈ।
  • R8 – ਜਦੋਂ ਤੁਸੀਂ ਉਪਰੋਕਤ AGP ਨਾਲ build ਕਰਦੇ ਹੋ ਤਾਂ ਇਹ ਆਪਣੇ ਆਪ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ।
  • Kotlin Coroutines 1.8.0+ – ਉਹ library version ਜਿਸ ਵਿੱਚ AtomicFieldUpdater pattern ਸ਼ਾਮਲ ਹੈ ਜਿਸਦੀ ਉਮੀਦ optimizer ਕਰਦਾ ਹੈ।
  • ਤੁਹਾਡੇ release build type ਵਿੱਚ isMinifyEnabled = true – R8 ਉਦੋਂ ਹੀ ਚੱਲਦਾ ਹੈ ਜਦੋਂ minification on ਹੁੰਦੀ ਹੈ।

ਇਕਲੌਤਾ ਵਾਧੂ ਕਦਮ ਤੁਹਾਡੇ ProGuard (ਜਾਂ R8) rules ਦੀ ਜਾਂਚ ਕਰਨਾ ਹੈ। ਵਿਆਪਕ (Broad) -keep directives ਜੋ volatile fields ਜਾਂ updater classes ਨੂੰ ਬਚਾ ਕੇ ਰੱਖਦੇ ਹਨ, ਉਹ rewrite ਨੂੰ ਰੋਕਦੇ ਹਨ। ਯਕੀਨੀ ਬਣਾਓ ਕਿ rules R8 ਨੂੰ ਉਹਨਾਂ fields ਨੂੰ ਬਦਲਣ ਦੀ ਇਜਾਜ਼ਤ ਦਿੰਦੇ ਹਨ; ਨਹੀਂ ਤਾਂ optimizer ਵਾਪਸ ਅਸਲ reflective implementation 'ਤੇ ਚਲਾ ਜਾਵੇਗਾ।

ਤੁਸੀਂ Android Studio ਦੇ APK Analyzer ਨਾਲ ਇਸ ਤਬਦੀਲੀ ਦੀ ਪੁਸ਼ਟੀ ਕਰ ਸਕਦੇ ਹੋ। compiled APK ਨੂੰ ਖੋਲ੍ਹੋ, JobSupport ਵਰਗੀ coroutine support class ਲੱਭੋ, ਅਤੇ decompiled bytecode ਦੀ ਜਾਂਚ ਕਰੋ। ਜੇਕਰ rewrite ਸਫਲ ਰਿਹਾ ਹੈ, ਤਾਂ static updater fields ਨਹੀਂ ਹੋਣਗੇ ਅਤੇ ਇਸ ਦੀ ਬਜਾਏ ਤੁਹਾਨੂੰ Unsafe ਦੀਆਂ ਸਿੱਧੀਆਂ calls ਦਿਖਾਈ ਦੇਣਗੀਆਂ।

ਸਾਵਧਾਨੀਆਂ ਅਤੇ ਵਿਰੋਧੀ ਨੁਕਤੇ (Caveats and counter-points)

ਇਹ optimization ਦੋ ਸ਼ਰਤਾਂ 'ਤੇ ਨਿਰਭਰ ਕਰਦੀ ਹੈ ਜੋ ਹਰ ਪ੍ਰੋਜੈਕਟ ਵਿੱਚ ਪੂਰੀਆਂ ਨਹੀਂ ਹੁੰਦੀਆਂ:

  1. Minification ਚਾਲੂ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ। Debug builds, ਜਾਂ release builds ਜੋ ਡਿਬੱਗਿੰਗ ਦੀ ਸਹੂਲਤ ਲਈ minification ਨੂੰ ਬੰਦ ਰੱਖਦੇ ਹਨ, ਉਹਨਾਂ ਨੂੰ ਕੋਈ ਲਾਭ ਨਹੀਂ ਮਿਲੇਗਾ।
  2. ProGuard ਨਿਯਮ ਲਚਕਦਾਰ ਹੋਣੇ ਚਾਹੀਦੇ ਹਨ। ਉਹ ਪ੍ਰੋਜੈਕਟ ਜੋ coroutine ਦੇ ਅੰਦਰੂਨੀ ਹਿੱਸਿਆਂ ਲਈ ਐਗਰੈਸਿਵ -keep ਪੈਟਰਨ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹਨ, ਉਹਨਾਂ ਨੂੰ ਉਹਨਾਂ ਨਿਯਮਾਂ ਨੂੰ ਢਿੱਲਾ ਕਰਨ ਦੀ ਲੋੜ ਪੈ ਸਕਦੀ ਹੈ, ਜੋ ਕਿ ਜੇਕਰ ਧਿਆਨ ਨਾਲ ਟੈਸਟ ਨਾ ਕੀਤਾ ਜਾਵੇ ਤਾਂ ਅੰਦਰੂਨੀ ਕਲਾਸਾਂ ਨੂੰ shrinking ਨਾਲ ਸਬੰਧਤ ਬੱਗਾਂ ਦੇ ਖਤਰੇ ਵਿੱਚ ਪਾ ਸਕਦਾ ਹੈ।

ਇਹ ਇੱਕ ਚੰਗਾ ਅਭਿਆਸ ਹੈ ਕਿ ਤੁਸੀਂ ਉਹਨਾਂ ਵੱਖ-ਵੱਖ ਡਿਵਾਈਸਾਂ 'ਤੇ ਟੈਸਟ ਕਰੋ ਜਿਨ੍ਹਾਂ ਨੂੰ ਤੁਸੀਂ ਨਿਸ਼ਾਨਾ ਬਣਾਉਂਦੇ ਹੋ।

ਅੱਗੇ ਕੀ ਦੇਖਣਾ ਹੈ

ਇਹ ਰੀਵਰਾਈਟ ਦਿਖਾਉਂਦੀ ਹੈ ਕਿ ਕਿਵੇਂ build-time bytecode ਤਬਦੀਲੀਆਂ ਭਾਸ਼ਾ ਦੇ ਅਬਸਟਰੈਕਸ਼ਨਾਂ (abstractions) ਦੇ ਪਿੱਛੇ ਛੁਪੀ ਹੋਈ ਪਰਫਾਰਮੈਂਸ ਨੂੰ ਕੱਢ ਸਕਦੀਆਂ ਹਨ। ਆਪਣੇ ਖਾਸ ਵਰਕਲੋਡ 'ਤੇ ਲਾਭ ਦੀ ਪੁਸ਼ਟੀ ਕਰਨ ਲਈ ਆਪਣੇ coroutine-heavy ਕੋਡ ਦੀ ਪ੍ਰੋਫਾਈਲਿੰਗ ਕਰਨ ਬਾਰੇ ਵਿਚਾਰ ਕਰੋ।

Takeaway: AGP 9.2.0 ਵਿੱਚ ਅੱਪਗ੍ਰੇਡ ਕਰਨਾ ਅਤੇ R8 ਦੀ minification ਨੂੰ ਚਾਲੂ ਕਰਨਾ Kotlin coroutine-heavy ਐਪਸ ਦੀਆਂ ਮਹੱਤਵਪੂਰਨ synchronization ਗਤੀਆਂ ਨੂੰ ਲਗਭਗ ਦੁੱਗਣਾ ਕਰ ਦਿੰਦਾ ਹੈ, ਉਹ ਵੀ ਸੋਰਸ ਕੋਡ ਨੂੰ ਛੇੜੇ ਬਿਨਾਂ—ਬਸ਼ਰਤੇ ਕਿ ਬਿਲਡ ਕੌਂਫਿਗਰੇਸ਼ਨ ਆਪਟੀਮਾਈਜ਼ਰ ਨੂੰ ਆਪਣਾ ਕੰਮ ਕਰਨ ਦੇਵੇ।