Section 11/172 menit

11. Actor Hop: Biaya, Pengukuran, dan Optimasi

11. Actor Hop: Biaya, Pengukuran, dan Optimasi

Setiap await ke method actor (dari konteks berbeda) memerlukan hop: konteks dipindahkan ke executor target. Hop punya cost — bukan gratis.

Apa yang Terjadi Saat Hop

  1. Continuation (state mesin coroutine saat ini) disimpan.
  2. Job dienqueue ke executor target.
  3. Thread caller bisa pulang ke pool / mengerjakan job lain.
  4. Eventually executor target menjalankan job.
  5. Saat method actor selesai (atau await lagi), kembali hop lagi.

Cost rata-rata (data emprical, M2 MacBook):

  • Hop ke actor instance di pool: ~200-500 ns.
  • Hop ke MainActor dari background: 1-3 µs (karena cross-thread).
  • Synchronous call di actor yang sama: ~5-10 ns (sama seperti method call biasa).

Mengukur Hop

Pakai Instruments → Swift Concurrency template, atau signpost manual:

swift
import os.signpost

let log = OSLog(subsystem: "app", category: .pointsOfInterest)

actor Worker {
    func process(_ item: Item) async {
        let id = OSSignpostID(log: log)
        os_signpost(.begin, log: log, name: "process", signpostID: id)
        defer { os_signpost(.end, log: log, name: "process", signpostID: id) }
        // ...
    }
}

Di Instruments → Points of Interest, lihat distribusi durasi vs jumlah hop.

Optimasi 1: Batch di Synchronous Method

Anti-pattern: banyak await ke actor untuk operasi kecil.

swift
// ❌ Banyak hop
for id in ids {
    await store.add(id)  // hop per iterasi
}

// ✓ Satu hop
await store.addBatch(ids)  // satu hop, loop internal

Optimasi 2: isolated Parameter untuk Batch

Sudah dibahas Bagian 10. Cocok untuk operasi yang tidak natural sebagai method actor.

swift
// Bukan method actor, tapi diisolasi saat dipanggil
func computeSummary(_ store: isolated UserStore) -> Summary {
    var s = Summary()
    for user in store.allUsers {  // tidak ada await
        s.add(user)
    }
    return s
}

Optimasi 3: Hindari Task { await ... } di Hot Loop

Setiap Task membuat allocation + schedule cost. Kalau kamu butuh fan-out di hot path, prefer TaskGroup (lebih ringan, reuse parent task context).

swift
// ❌ Banyak Task allocation
for item in items {
    Task { await process(item) }
}

// ✓ TaskGroup
await withTaskGroup(of: Void.self) { group in
    for item in items {
        group.addTask { await process(item) }
    }
}

Optimasi 4: Minimasi MainActor Hop

UI update sebaiknya di-batch:

swift
// ❌ Update per item → hop per item
for change in changes {
    await MainActor.run { table.apply(change) }
}

// ✓ Satu hop
await MainActor.run {
    for change in changes { table.apply(change) }
}