23 poin oleh darjeeling 2025-11-16 | Belum ada komentar. | Bagikan ke WhatsApp

Penyebab Perlambatan Kode Async dan Cara Mengatasinya (Ringkasan teknis)

Video ini membahas penyebab umum mengapa kode asyncio Python bisa lebih lambat daripada kode sinkron, serta metodologi teknis untuk mengatasinya.

1. Konsep inti Asyncio

  • Event Loop: Inti dari semua aplikasi asinkron. Dimulai dengan asyncio.run(), lalu mengelola dan menjadwalkan eksekusi task di satu thread.
  • Coroutine: Fungsi asinkron yang dideklarasikan dengan async def. Saat menemui kata kunci await, eksekusi dapat dijeda dan kontrol dikembalikan ke event loop.
  • Task: Membungkus coroutine dan menjadwalkannya agar berjalan secara bersamaan di event loop. Dibuat melalui asyncio.create_task().
  • Future: Objek level rendah yang merepresentasikan hasil akhir dari pekerjaan asinkron.

2. Contoh konversi kode sinkron ke asinkron

Gantilah time.sleep() sinkron yang ada dengan await asyncio.sleep() yang asinkron, deklarasikan fungsi dengan async def, dan jalankan coroutine utama dengan asyncio.run().


Kesalahan umum yang menyebabkan penurunan performa dan solusinya

Kesalahan 1: Eksekusi berurutan (Sequential Execution)

Jika task-task independen di-await secara berurutan alih-alih dijalankan paralel, total waktu eksekusi akan menjadi penjumlahan waktu semua task.

  • Contoh yang salah (berurutan):

    # Setiap await menunggu sampai pekerjaan sebelumnya selesai  
    await get_user_notifications()  
    await get_recent_activity()  
    await get_unread_messages()  
    
  • Solusi (paralel): Gunakan asyncio.gather atau asyncio.TaskGroup untuk menjalankan task-task independen secara bersamaan. Total waktu eksekusi dapat turun menjadi sebesar waktu task yang paling lama.

    # Tiga pekerjaan dimulai pada saat yang sama  
    await asyncio.gather(  
        get_user_notifications(),  
        get_recent_activity(),  
        get_unread_messages()  
    )  
    

Perbandingan alat eksekusi paralel

  • asyncio.gather:
    • Menjalankan beberapa coroutine secara bersamaan.
    • Kekurangan: penanganan error kurang baik. Jika exception terjadi di satu task, task lain yang sedang berjalan akan dibatalkan.
  • asyncio.create_task:
    • Memungkinkan kontrol dan penanganan error per task.
    • Berguna untuk eksekusi di background, tetapi merepotkan karena beberapa task harus di-await satu per satu.
  • asyncio.TaskGroup (Python 3.11+):
    • Alternatif modern untuk 'structured concurrency'.
    • Mengelola grup task dengan sintaks async with, dan saat keluar dari konteks dipastikan semua task selesai atau exception telah ditangani.
    async with asyncio.TaskGroup() as tg:  
        tg.create_task(some_coro_1())  
        tg.create_task(some_coro_2())  
    # Saat blok 'async with' berakhir, semua task telah di-await  
    

Kesalahan 2: Menggunakan library sinkron

Jika library sinkron (blocking) seperti requests atau pathlib digunakan di dalam kode asyncio, seluruh event loop akan terblokir. Bahkan jika dipakai di dalam asyncio.gather, perilakunya tetap pada praktiknya menjadi berurutan.

  • Solusi: Gunakan library khusus yang mendukung asinkron (non-blocking) seperti aiohttp (pengganti requests), aiofiles (pengganti files/pathlib), dan sejenisnya.

Kesalahan 3: Event loop terblokir oleh pekerjaan CPU-bound

Karena asyncio berjalan di satu thread, pekerjaan komputasi berat (CPU-bound) akan menghentikan event loop dan menunda pekerjaan I/O lainnya.

  • Solusi: Gunakan loop.run_in_executor() untuk memindahkan pekerjaan CPU-bound ke thread pool terpisah (default) atau process pool.
    loop = asyncio.get_running_loop()  
    # Menjalankan fungsi yang intensif CPU di thread terpisah  
    await loop.run_in_executor(  
        None,  # gunakan thread pool default  
        cpu_bound_function,  
        arg1  
    )  
    

Kesalahan 4: Pemblokiran akibat pekerjaan yang tidak penting

Jika pekerjaan non-inti seperti logging yang tidak terkait dengan respons pengguna tetap di-await, waktu respons akan tertunda secara tidak perlu.

  • Solusi: Gunakan asyncio.create_task() untuk memisahkan pekerjaan tersebut sebagai background task dan jangan di-await.
    user_profile = await get_user_profile()  
    # Jalankan logging di background tanpa await  
    asyncio.create_task(send_logs_to_external_service())  
    return user_profile  
    

Kesalahan 5: Membuat terlalu banyak task

Jika pekerjaan yang sangat kecil dijadikan task dalam jumlah besar, overhead context switching bisa muncul dan menurunkan performa.

  • Solusi 1: Gabungkan pekerjaan kecil (batching) menjadi beberapa task yang lebih besar.
  • Solusi 2: Gunakan asyncio.Semaphore untuk membatasi jumlah maksimum task yang berjalan bersamaan.
    # Izinkan maksimal 10 pekerjaan berjalan bersamaan  
    semaphore = asyncio.Semaphore(10)  
    
    async with semaphore:  
        await fetch_data()  
    

Kesalahan lainnya

  • Coroutine "Never Awaited": Coroutine dipanggil tetapi tidak di-await, sehingga pekerjaannya bahkan tidak dijalankan dan gagal secara diam-diam. Ini dapat dideteksi dengan linter seperti flake8-async.
  • Manajemen resource yang tidak tepat: Jika file, koneksi DB, dan sejenisnya digunakan tanpa try...finally, kebocoran resource bisa terjadi. Solusinya adalah memakai asynchronous context manager dengan async with.

Debugging dan pemilihan model konkurensi

Mode debug Asyncio

Jika mode debug yang secara default nonaktif diaktifkan (asyncio.run(debug=True)), hal ini membantu mendeteksi masalah seperti berikut.

  • Coroutine yang tidak di-await (RuntimeWarning).
  • API asinkron yang dipanggil dari thread yang salah.
  • Callback dengan waktu eksekusi melebihi 100ms.
  • Operasi selector I/O yang lambat.

Alat debugging lainnya

  • Scalene: Profiler CPU dan memori.
  • aio-monitor: Monitoring dan CLI untuk aplikasi asyncio.
  • pdb: Debugger bawaan Python.
  • py-stack: Menampilkan stack trace proses Python yang sedang berjalan untuk mendeteksi titik pemblokiran.

Panduan memilih model konkurensi

  • Asyncio (single-thread): Paling cocok untuk banyak pekerjaan I/O-bound dengan latensi tinggi, misalnya request jaringan atau file I/O.
  • Threads (multi-thread): Digunakan untuk pekerjaan I/O-bound yang membutuhkan akses ke data bersama. Karena GIL (Global Interpreter Lock), ini bukan paralelisme sejati, tetapi thread lain bisa berjalan saat menunggu I/O.
  • Processes (multi-process): Digunakan untuk pekerjaan CPU-bound seperti pemrosesan gambar atau komputasi berat. Dapat memanfaatkan banyak inti CPU untuk mencapai paralelisme sejati, tetapi overhead memori dan komunikasi lebih besar.

https://youtu.be/wGDOwNW6lVk

Belum ada komentar.

Belum ada komentar.