Panduan praktis untuk pola pikir performa ala John Carmack: profiling, anggaran waktu frame, tradeoff, dan cara mengirimkan sistem real-time yang kompleks.

John Carmack sering diperlakukan seperti legenda mesin game, tetapi bagian yang berguna bukanlah mitologinya—melainkan kebiasaan yang bisa diulang. Ini bukan soal meniru gaya satu orang atau menganggap "langkah jenius." Ini tentang prinsip praktis yang konsisten menghasilkan perangkat lunak yang lebih cepat dan lebih halus, terutama ketika tenggat waktu dan kompleksitas menumpuk.
Rekayasa kinerja berarti membuat perangkat lunak memenuhi target kecepatan di perangkat keras nyata, dalam kondisi nyata—tanpa merusak kebenaran program. Bukan "buat cepat dengan cara apa pun." Ini sebuah loop disiplin:
Pola pikir ini muncul berulang kali dalam kerja Carmack: berargumen dengan data, buat perubahan yang dapat dijelaskan, dan pilih pendekatan yang mudah dipelihara.
Grafik waktu nyata tidak pandang bulu karena ada tenggat setiap frame. Jika Anda melewatinya, pengguna langsung merasakannya sebagai stutter, lag input, atau gerakan yang tidak rata. Perangkat lunak lain dapat menyembunyikan inefisiensi di balik antrean, layar muat, atau pekerjaan latar belakang. Renderer tidak bisa bernegosiasi: Anda selesai tepat waktu, atau tidak.
Itulah mengapa pelajarannya meluas di luar game. Sistem apa pun dengan kebutuhan latensi ketat—UI, audio, AR/VR, trading, robotika—diuntungkan dengan berpikir dalam anggaran, memahami bottleneck, dan menghindari lonjakan tak terduga.
Anda akan mendapatkan checklist, heuristik, dan pola pengambilan keputusan yang dapat diterapkan ke pekerjaan Anda sendiri: cara menetapkan anggaran waktu frame (atau latensi), cara memprofil sebelum mengoptimalkan, cara memilih "satu hal" untuk diperbaiki, dan cara mencegah regresi sehingga performa menjadi rutinitas—bukan panik di tahap akhir.
Pemikiran performa ala Carmack dimulai dengan perubahan sederhana: berhenti bicara tentang "FPS" sebagai satuan utama dan mulailah bicara tentang waktu frame.
FPS bersifat kebalikan ("60 FPS" terdengar baik, "55 FPS" terdengar dekat), tetapi pengalaman pengguna ditentukan oleh berapa lama tiap frame berlangsung—dan, sama pentingnya, seberapa konsisten waktu-waktu itu. Lonjakan dari 16.6 ms ke 33.3 ms terlihat instan meskipun rata-rata FPS Anda masih terlihat wajar.
Sebuah produk waktu nyata memiliki beberapa anggaran, bukan hanya "render lebih cepat":
Anggaran ini saling berinteraksi. Menghemat waktu GPU dengan menambahkan batching berat di CPU bisa berbalik merugikan, dan mengurangi memori bisa meningkatkan biaya streaming atau dekompresi.
Jika target Anda 60 FPS, total anggaran Anda adalah 16.6 ms per frame. Pembagian kasar mungkin seperti:
Jika baik CPU atau GPU melebihi anggaran, Anda melewatkan frame. Inilah sebabnya tim berbicara tentang "CPU-bound" atau "GPU-bound"—bukan sekadar label, tetapi cara memutuskan dari mana milidetik berikutnya realistis didapat.
Intinya bukan mengejar metrik kesombongan seperti "FPS tertinggi di PC high-end." Intinya adalah mendefinisikan apa yang cukup cepat untuk audiens Anda—target hardware, resolusi, batas baterai, termal, dan responsivitas input—lalu memperlakukan performa sebagai anggaran eksplisit yang bisa Anda kelola dan pertahankan.
Langkah default Carmack bukanlah "optimalkan," melainkan "verifikasi." Masalah performa waktu nyata penuh dengan cerita yang masuk akal—pause GC, "shader lambat", "terlalu banyak draw call"—dan sebagian besar salah pada build Anda di hardware Anda. Profiling adalah cara menggantikan intuisi dengan bukti.
Perlakukan profiling seperti fitur kelas satu, bukan alat penyelamat menit terakhir. Tangkap waktu frame, timeline CPU dan GPU, serta hitungan yang menjelaskannya (segitiga, draw call, perubahan state, alokasi, cache miss jika bisa). Tujuannya menjawab satu pertanyaan: ke mana sebenarnya waktu pergi?
Model yang berguna: dalam setiap frame lambat, satu hal adalah faktor pembatas. Mungkin GPU terhambat pada pass berat, CPU macet di update animasi, atau main thread terhenti pada sinkronisasi. Temukan constraint itu dulu; semuanya lain hanyalah noise.
Loop disiplin menjaga Anda dari thrashing:
Jika peningkatan tidak jelas, anggap tidak membantu—karena kemungkinan besar tidak akan bertahan saat konten berikutnya masuk.
Pekerjaan performa sangat rentan terhadap penipuan diri:
Profiling terlebih dahulu menjaga usaha Anda fokus, tradeoff Anda beralasan, dan perubahan Anda lebih mudah dipertahankan dalam review.
Masalah performa waktu nyata terasa berantakan karena semuanya terjadi sekaligus: gameplay, rendering, streaming, animasi, UI, fisika. Insting Carmack adalah memotong kebisingan dan mengidentifikasi pembatas dominan—satu hal yang saat ini menentukan waktu frame Anda.
Sebagian besar perlambatan masuk ke beberapa golongan:
Tujuannya bukan memberi label untuk laporan—tetapi memilih tuas yang tepat.
Beberapa eksperimen cepat bisa memberitahu apa yang benar-benar mengendalikan:
Anda jarang menang dengan mengikis 1% dari sepuluh sistem. Temukan biaya terbesar yang berulang tiap frame dan serang itu dulu. Menghapus satu pelanggar 4 ms mengalahkan minggu-minggu micro-optimisasi.
Setelah Anda memperbaiki batu besar, batu besar berikutnya menjadi terlihat. Itu normal. Perlakukan pekerjaan performa sebagai loop: ukur → ubah → ukur ulang → reprioritaskan. Tujuannya bukan profil sempurna; melainkan kemajuan stabil menuju waktu frame yang dapat diprediksi.
Waktu frame rata-rata bisa terlihat baik sementara pengalaman masih terasa buruk. Grafik waktu nyata dinilai dari momen terburuk: frame terlewat saat ledakan besar, hitch saat memasuki ruangan baru, tiba-tiba stutter saat membuka menu. Itu adalah tail latency—frame lambat yang jarang tapi cukup sering pengguna langsung merasakannya.
Sebuah game berjalan 16.6 ms sebagian besar waktu (60 FPS) namun spike ke 60–120 ms setiap beberapa detik akan terasa "rusak," walau rata-rata masih tercetak sebagai 20 ms. Manusia sensitif terhadap ritme. Satu frame panjang merusak prediktabilitas input, gerakan kamera, dan sinkronisasi audio/visual.
Spike sering berasal dari pekerjaan yang tidak tersebar merata:
Tujuannya membuat pekerjaan mahal menjadi dapat diprediksi:
Jangan hanya plot garis FPS rata-rata. Rekam timing per-frame dan visualisasikan:
Jika Anda tidak bisa menjelaskan 1% frame terburuk Anda, Anda belum benar-benar menjelaskan performa.
Pekerjaan performa menjadi lebih mudah saat Anda berhenti berpura-pura bisa mendapatkan semuanya sekaligus. Gaya Carmack mendorong tim untuk menyebutkan tradeoff secara terang-terangan: apa yang kita dapatkan, apa yang kita bayar, dan siapa yang merasakan perbedaannya?
Sebagian besar keputusan berada pada beberapa sumbu:
Jika suatu perubahan memperbaiki satu sumbu tetapi diam-diam membebani tiga lainnya, dokumentasikan. "This adds 0.4 ms GPU and 80 MB VRAM to gain softer shadows" adalah pernyataan yang bisa dipakai. "Terlihat lebih baik" bukan.
Grafik waktu nyata bukan tentang kesempurnaan; ini tentang mencapai target secara konsisten. Sepakati threshold seperti:
Setelah tim setuju bahwa, misalnya, 16.6 ms pada 1080p di GPU baseline adalah tujuan, argumen menjadi konkret: apakah fitur ini menjaga kita di bawah anggaran, atau memaksa penurunan kualitas di tempat lain?
Saat belum yakin, pilih opsi yang bisa dibatalkan:
Keterbalikan melindungi jadwal. Anda bisa mengirimkan jalur aman dan menyimpan yang ambisius di balik toggle.
Hindari overengineering untuk kemenangan yang tidak terlihat. Peningkatan rata-rata 1% jarang bernilai sebulan kompleksitas—kecuali itu menghilangkan stutter, memperbaiki latensi input, atau mencegah crash memori keras. Prioritaskan perubahan yang langsung dirasakan pemain, dan biarkan sisanya menunggu.
Pekerjaan performa menjadi jauh lebih mudah ketika program itu benar. Banyak waktu optimisasi sebenarnya dihabiskan mengejar bug correctness yang hanya tampak seperti masalah performa: loop O(N²) karena kerja ganda, pass render berjalan dua kali karena flag tak direset, memory leak yang perlahan menaikkan waktu frame, atau race condition yang berubah menjadi stutter acak.
Engine yang stabil dan dapat diprediksi memberi Anda pengukuran bersih. Jika perilaku berubah antar run, Anda tidak bisa mempercayai profil, dan akan berakhir mengoptimalkan noise.
Praktik rekayasa disiplin membantu percepatan:
Banyak spike waktu frame adalah "Heisenbug": mereka hilang ketika Anda menambahkan logging atau step-through debugger. Penawarnya adalah reproduksi deterministik.
Buat harness pengujian kecil dan terkontrol:
Saat hitch muncul, Anda ingin tombol yang memutarnya 100 kali—bukan laporan samar bahwa itu "kadang muncul setelah 10 menit."
Pekerjaan kecepatan diuntungkan dari perubahan kecil yang dapat direview. Refactor besar menciptakan banyak mode kegagalan sekaligus: regresi, alokasi baru, dan kerja tersembunyi ekstra. Diff kecil membuat lebih mudah menjawab satu pertanyaan penting: apa yang berubah pada waktu frame, dan mengapa?
Disiplin bukan birokrasi di sini—itu adalah cara menjaga pengukuran dapat dipercaya sehingga optimisasi menjadi lugas, bukan penuh takhayul.
Performa waktu nyata bukan hanya tentang "kode lebih cepat." Ini tentang menyusun kerja agar CPU dan GPU bisa melakukannya secara efisien. Carmack sering menekankan satu kebenaran sederhana: mesin itu literal. Ia menyukai data yang dapat diprediksi dan membenci overhead yang bisa dihindari.
CPU modern sangat cepat—sampai mereka menunggu memori. Jika data Anda tersebar di banyak objek kecil, CPU menghabiskan waktu mengejar pointer alih-alih melakukan perhitungan.
Model mental yang berguna: jangan pergi belanja sepuluh kali untuk sepuluh barang. Masukkan semuanya ke satu keranjang dan jalan sekali. Dalam kode, itu berarti menyimpan nilai yang sering dipakai berdekatan (sering dalam array atau struct yang padat) sehingga setiap fetch cache line membawa data yang akan Anda gunakan.
Alokasi sering menciptakan biaya tersembunyi: overhead allocator, fragmentasi memori, dan jeda tak terduga ketika sistem harus merapikan. Bahkan jika tiap alokasi "kecil", aliran konstan bisa menjadi pajak yang Anda bayar tiap frame.
Perbaikan umum adalah membosankan dengan sengaja: reuse buffer, pool objek, dan prefer alokasi long-lived untuk hot path. Tujuannya bukan kecerdikan—melainkan konsistensi.
Jumlah waktu frame yang mengejutkan bisa hilang ke bookkeeping: perubahan state, draw call, kerja driver, syscall, dan koordinasi thread.
Batching adalah versi "satu keranjang besar" untuk rendering dan simulasi. Alih-alih mengeluarkan banyak operasi kecil, kelompokkan pekerjaan serupa sehingga Anda melintasi boundary mahal lebih sedikit kali. Seringkali, mengurangi overhead mengalahkan micro-optimisasi shader atau loop bagian dalam—karena mesin menghabiskan lebih sedikit waktu menyiapkan kerja dan lebih banyak waktu melakukan kerja.
Pekerjaan performa bukan hanya soal kode lebih cepat—itu juga soal memiliki lebih sedikit kode. Kompleksitas punya biaya yang Anda bayar setiap hari: bug butuh waktu lebih lama untuk diisolasi, perbaikan memerlukan pengujian lebih teliti, iterasi melambat karena setiap perubahan menyentuh lebih banyak bagian, dan regresi merayap lewat jalur yang jarang dipakai. Kompleksitas ini tidak hanya membuang waktu pengembang; sering menambahkan overhead runtime (branch tambahan, alokasi, cache miss, sinkronisasi) yang sulit terlihat sampai terlambat.
Sistem "cerdas" bisa terlihat elegan sampai Anda berada di bawah tenggat dan spike frame muncul hanya di satu peta, satu GPU, atau satu kombinasi pengaturan. Setiap feature flag, jalur fallback, dan kasus khusus menggandakan jumlah perilaku yang harus Anda pahami dan ukur. Kompleksitas itu bukan hanya menyia-nyiakan waktu pengembang; sering menambahkan overhead runtime yang sulit terlihat sampai terlambat.
Aturan yang baik: jika Anda tidak bisa menjelaskan model performa kepada rekan dalam beberapa kalimat, kemungkinan besar Anda tidak bisa mengoptimalkannya secara andal.
Solusi sederhana punya dua keuntungan:
Kadang jalur tercepat adalah menghapus fitur, memotong opsi, atau menyatukan beberapa varian menjadi satu. Lebih sedikit fitur berarti lebih sedikit jalur kode, lebih sedikit kombinasi state, dan lebih sedikit tempat performa bisa menurun tanpa terdeteksi.
Menghapus kode juga langkah kualitas: bug terbaik adalah yang Anda hapus dengan menghapus modul yang bisa menghasilkannya.
Patch (perbaikan bedah) ketika:
Refactor (sederhanakan struktur) ketika:
Kesederhanaan bukan berarti "kurang ambisius." Ini memilih desain yang tetap bisa dipahami saat tekanan—saat performa paling penting.
Pekerjaan performa hanya melekat jika Anda bisa melihat kapan ia tergelincir. Itulah tujuan pengujian regresi performa: cara yang dapat diulang untuk mendeteksi ketika perubahan baru membuat produk lebih lambat, kurang halus, atau lebih boros memori. Berbeda dengan tes fungsional (yang menjawab "apakah ini bekerja?"), tes regresi menjawab "apakah ini masih terasa sama cepat?" Build bisa 100% benar secara fungsional dan tetap menjadi rilis buruk jika menambah 4 ms waktu frame atau menggandakan waktu muat.
Anda tidak butuh lab untuk mulai—cukup konsistensi.
Pilih beberapa baseline scene yang mewakili penggunaan nyata: satu view GPU-heavy, satu view CPU-heavy, dan satu scene stress "worst case". Jaga agar mereka stabil dan ter-script sehingga jalur kamera dan input identik tiap run.
Jalankan tes pada hardware tetap (PC/console/devkit yang dikenal). Jika Anda mengganti driver, OS, atau pengaturan clock, catatlah. Perlakukan kombinasi hardware/software seperti bagian dari fixture tes.
Simpan hasil dalam riwayat versi: hash commit, konfigurasi build, ID mesin, dan metrik yang terukur. Tujuannya bukan angka sempurna—melainkan garis tren yang dapat dipercaya.
Pilih metrik yang sulit untuk diperdebatkan:
Tentukan ambang sederhana (mis. p95 tidak boleh regresi lebih dari 5%).
Perlakukan regresi seperti bug dengan pemilik dan tenggat.
Pertama, bisect untuk menemukan perubahan yang memperkenalkannya. Jika regresi menghalangi rilis, revert cepat dan re-land dengan perbaikan.
Saat memperbaiki, tambahkan guardrail: pertahankan test, tambahkan catatan di kode, dan dokumentasikan anggaran yang diharapkan. Kebiasaan itulah yang menang—performa menjadi sesuatu yang Anda pelihara, bukan sesuatu yang "dilakukan nanti."
"Mengirimkan" bukanlah peristiwa di kalender—itu adalah persyaratan rekayasa. Sistem yang hanya berjalan baik di lab, atau hanya mencapai waktu frame setelah seminggu pengaturan manual, belum selesai. Mindset Carmack memperlakukan keterbatasan dunia nyata (variasi hardware, konten berantakan, perilaku pemain yang tak terduga) sebagai bagian dari spes sejak hari pertama.
Saat mendekati rilis, kesempurnaan kurang bernilai dibandingkan prediktabilitas. Definisikan non-negotiable secara gamblang: target FPS, spike waktu frame terburuk, batas memori, dan waktu muat. Lalu perlakukan apa pun yang melanggar itu sebagai bug, bukan "polish." Ini mengubah pekerjaan performa dari optimisasi opsional menjadi pekerjaan reliabilitas.
Tidak semua perlambatan sama. Perbaiki masalah yang paling terlihat oleh pengguna terlebih dahulu:
Disiplin profiling akan sangat membantu: Anda tidak menebak isu mana yang "terasa besar," Anda memilih berdasarkan dampak terukur.
Pekerjaan performa siklus akhir berisiko karena "perbaikan" bisa memperkenalkan biaya baru. Gunakan rollout bertahap: land instrumentasi dulu, lalu perubahan di balik toggle, kemudian perluas eksposur. Pilih default yang aman untuk performa—pengaturan yang melindungi waktu frame meski mengurangi kualitas visual sedikit—terutama untuk konfigurasi yang terdeteksi otomatis.
Jika Anda mengirim ke banyak platform atau tier, perlakukan default sebagai keputusan produk: lebih baik terlihat sedikit kurang mewah daripada terasa tidak stabil.
Terjemahkan tradeoff ke hasil: "Efek ini menambah 2 ms tiap frame pada GPU mid-tier, yang berisiko turun di bawah 60 FPS saat pertarungan." Tawarkan opsi, bukan kuliah: turunkan resolusi, sederhanakan shader, batasi spawn rate, atau terima target lebih rendah. Keterbatasan lebih mudah diterima bila dibingkai sebagai pilihan konkret dengan dampak pengguna yang jelas.
Anda tidak perlu engine baru atau rewrite untuk mengadopsi pola pikir performa ala Carmack. Anda butuh loop yang bisa diulang yang membuat performa terlihat, dapat diuji, dan sulit rusak tanpa sengaja.
Jika Anda ingin mengoperasionalkan kebiasaan ini di seluruh tim, kuncinya mengurangi friction: eksperimen cepat, harness yang bisa diulang, dan rollback yang mudah.
Koder.ai bisa membantu di sini saat Anda membangun tooling pendukung—bukan engine itu sendiri. Karena ini platform vibe-coding yang menghasilkan kode sumber nyata yang dapat diekspor (web app di React; backend di Go dengan PostgreSQL; mobile di Flutter), Anda bisa cepat memutar dashboard internal untuk persentil waktu frame, histori regresi, dan checklist "review performa", lalu iterasi via chat saat kebutuhan berkembang. Snapshot dan rollback juga cocok dengan loop "ubah satu hal, ukur ulang."
Jika Anda ingin panduan praktis lebih lanjut, jelajahi /blog atau lihat bagaimana tim mengoperasionalkan ini di /pricing.
Waktu frame adalah waktu per frame dalam milidetik (ms), dan ini memetakan langsung berapa banyak pekerjaan yang dilakukan CPU/GPU.
Pilih target (mis. 60 FPS) dan konversikan menjadi batas keras (16.6 ms). Kemudian bagi tenggat tersebut ke dalam anggaran yang eksplisit.
Contoh titik awal:
Perlakukan ini sebagai persyaratan produk, dan sesuaikan menurut platform, resolusi, termal, dan sasaran latensi input.
Mulailah dengan membuat pengujian yang dapat diulang, lalu ukur sebelum mengubah apa pun.
Hanya setelah Anda tahu ke mana waktu pergi barulah putuskan apa yang akan dioptimalkan.
Jalankan eksperimen cepat yang menyingkap pembatas:
Hindari menulis ulang sistem sampai Anda bisa menyebutkan biaya dominan dalam milidetik.
Karena pengguna merasakan frame terburuk, bukan rata-rata.
Lacak:
Build yang rata-rata 16.6 ms tapi spike ke 80 ms tetap akan terasa rusak.
Buat pekerjaan mahal menjadi dapat diprediksi dan terjadwal:
Juga catat spike agar Anda bisa mereproduksi dan memperbaikinya, bukan hanya berharap hilang.
Jadikan tradeoff eksplisit dalam angka dan dampak pada pengguna.
Gunakan pernyataan seperti:
Lalu putuskan berdasarkan threshold yang disepakati:
Karena ketidakstabilan koreksi membuat data performa tidak dapat dipercaya.
Langkah praktis:
Jika perilaku berubah tiap kali dijalankan, Anda akan mengoptimalkan noise bukannya bottleneck.
Sebagian besar pekerjaan "kode cepat" sebenarnya adalah pekerjaan pada memori dan overhead.
Fokus pada:
Seringkali, memangkas overhead memberi keuntungan lebih besar daripada memperbaiki loop bagian dalam.
Buat performa terukur, dapat diulang, dan sulit untuk rusak tanpa sengaja.
Ukur: tangkap baseline (rata-rata, p95, spike terburuk) untuk waktu frame dan subsistem kunci.
Anggaran: tetapkan anggaran per-frame untuk CPU dan GPU (dan memori bila ketat). Tulis anggaran di samping tujuan fitur.
Isolasi: reproduksi biaya di scene atau tes minimal. Jika tidak bisa reproduksi, Anda tidak bisa memperbaikinya secara andal.
Optimalkan: ubah satu hal pada satu waktu. Pilih perubahan yang mengurangi kerja, bukan sekadar "membuat lebih cepat."
Validasi: profil ulang, bandingkan delta, dan cek regresi kualitas serta masalah correctness.
Dokumentasi: catat apa yang berubah, mengapa itu membantu, dan apa yang harus diwaspadai di masa depan.
Jika ragu, pilih keputusan yang dapat dibalik (feature flag, tingkat kualitas yang skalabel).
Saat ada regresi: bisect, tunjuk pemilik, dan revert cepat jika menghalangi rilis.