Topik ini membahas performa PHP modern dari tiga sisi, yaitu peran opcache dalam menyimpan hasil kompilasi, cara kerja just-in-time (JIT) yang menerjemahkan kode yang paling sering dijalankan menjadi instruksi mesin, dan pola pikir profiling (pengukuran profil eksekusi) sebelum pengoptimalan apa pun dilakukan. Contoh memakai pekerjaan pencarian pada indeks arsip. Setelah membacanya, urutan keputusan performa menjadi jelas, yaitu ukur terlebih dahulu, perbaiki algoritma yang terbukti mahal, dan biarkan opcache serta JIT bekerja di lapisan bawah.
Peran opcache#
Menjalankan satu berkas PHP tanpa cache terdiri atas dua tahap besar, yaitu membaca kode sumber dan mengompilasinya menjadi bytecode (deretan instruksi yang dipahami mesin virtual PHP), lalu mengeksekusinya. Kompilasi adalah pekerjaan yang mahal, dan tanpa cache pekerjaan itu diulang untuk setiap permintaan meskipun kode sumbernya tidak pernah berubah. opcache adalah ekstensi resmi yang menyimpan hasil kompilasi tersebut di memori bersama, sehingga tahap kompilasi hanya terjadi sekali untuk setiap versi berkas.
Diagram berikut memperlihatkan percabangan yang terjadi pada setiap permintaan ketika opcache aktif.
Kebijakan penyegaran dikendalikan lewat konfigurasi. Direktif validate_timestamps memerintahkan opcache memeriksa waktu perubahan berkas, dan revalidate_freq menetapkan jeda antarpemeriksaannya, sehingga perubahan kode tetap terdeteksi tanpa memeriksa setiap berkas pada setiap permintaan.
opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000
opcache.validate_timestamps=1
opcache.revalidate_freq=2Direktif memory_consumption menetapkan ukuran memori cache dalam satuan MB, sedangkan max_accelerated_files menetapkan jumlah berkas yang dapat tersimpan. Proyek dengan ribuan berkas membutuhkan nilai yang lebih besar agar entri cache tidak saling menggusur, dan nilai memori yang terlalu kecil membuat opcache membuang entri lama lebih dahulu.
JIT pada PHP 8#
JIT adalah komponen opcache yang melangkah lebih jauh daripada sekadar menyimpan bytecode. Pada mode tracing, JIT mengamati kode yang paling sering dijalankan, lalu mengompilasi bagian itu menjadi instruksi mesin sehingga mesin virtual PHP tidak lagi menafsirkan instruksinya satu per satu. Mode function melakukan kompilasi per fungsi tanpa pengamatan, dan opcache.jit_buffer_size menetapkan memori khusus untuk hasil kompilasinya.
opcache.jit_buffer_size=64M
opcache.jit=tracingKeaktifan JIT pada lingkungan produksi dapat diperiksa lewat keluaran konfigurasi PHP.
# tidak-dijalankan
php -i | grep opcache.jitKenaikan performa JIT sangat bergantung pada jenis beban kerja. Perhitungan yang padat CPU, seperti pemrosesan citra atau matematika numerik, memperoleh manfaat paling besar karena seluruh waktunya habis di dalam eksekusi kode. Aplikasi web pada umumnya menghabiskan waktu untuk menunggu basis data, berkas, dan jaringan, sehingga kenaikannya moderat. Di situlah pola pikir profiling menjadi penentu, karena pengukuran menunjukkan di mana waktu benar-benar dihabiskan sebelum keputusan pengoptimalan diambil.
Pola pikir profiling#
Profiling adalah kegiatan mengukur eksekusi program untuk menemukan bagian yang paling banyak menyita waktu, dan alat seperti profiler milik Xdebug menyajikannya per fungsi. Kebiasaan yang ditegaskan di sini adalah mengukur sebelum mengubah apa pun, karena tebakan tentang titik lambat sering meleset. Satu perbaikan algoritmik yang dibuktikan oleh pengukuran biasanya melebihi sekumpulan penyesuaian mikro tanpa dasar pengukuran.
Demonstrasi berikut memakai penghitungan operasi alih-alih pencatatan waktu, sehingga hasilnya dapat direproduksi secara persis di lingkungan mana pun. Dua fungsi pencarian pada contoh itu mencari angka yang sama pada indeks arsip berisi seribu entri, tetapi dengan strategi yang berbeda.
<?php
declare(strict_types=1);
function cariBertahap(array $data, int $dicari): int
{
$operasi = 0;
foreach ($data as $nilai) {
$operasi += 1;
if ($nilai === $dicari) {
return $operasi;
}
}
return $operasi;
}
function cariBagiDua(array $data, int $dicari): int
{
$operasi = 0;
$bawah = 0;
$atas = count($data) - 1;
while ($bawah <= $atas) {
$operasi += 1;
$tengah = intdiv($bawah + $atas, 2);
if ($data[$tengah] === $dicari) {
return $operasi;
}
if ($data[$tengah] < $dicari) {
$bawah = $tengah + 1;
} else {
$atas = $tengah - 1;
}
}
return $operasi;
}
$data = range(1, 1000);
printf("Pencarian bertahap: %d operasi\n", cariBertahap($data, 999));
printf("Pencarian bagi dua: %d operasi\n", cariBagiDua($data, 999));Pencarian bertahap: 999 operasi
Pencarian bagi dua: 9 operasiPencarian bertahap memeriksa hampir seluruh entri, sedangkan pencarian bagi dua cukup sembilan langkah karena rentang pencariannya terbagi dua pada setiap langkah. Penghitungan operasi menangkap perbedaan biaya ini secara deterministik, dan pada pengukuran nyata perbedaan yang sama akan tampil sebagai perbedaan waktu. Catatannya tetap sama, yaitu kode yang berjalan lama sebaiknya diganti algoritmanya, bukan sekadar dioptimalkan baris demi baris.
Kesalahan umum#
Kesalahan pertama adalah mengoptimalkan tanpa profil. Perbaikan yang ditujukan pada bagian yang jarang dijalankan tidak mengubah performa total apa pun, sementara perbaikan yang menyentuh bagian yang paling banyak menghabiskan waktu memberi hasil seketika. Jalankan profiler terlebih dahulu, lalu urutkan sasaran berdasarkan porsi waktunya.
Kesalahan kedua adalah menilai kecepatan dari satu kali eksekusi dengan pencatatan waktu yang dicetak langsung. Waktu eksekusi berfluktuasi akibat cache perangkat keras, beban sistem, dan kebetulan lainnya, sehingga satu angka tunggal mudah menyesatkan. Ukur dengan banyak pengulangan, bandingkan rata-ratanya, dan pastikan kode yang dibandingkan menerima masukan yang sama persis.
Kesalahan ketiga adalah mengharapkan JIT menyelamatkan kode yang lambat. Bila profil menunjukkan waktu habis untuk menunggu basis data atau jaringan, JIT tidak memiliki apa pun untuk dipercepat, dan perbaikan yang benar berada pada jumlah permintaan, indeks, atau penantian itu. JIT mempercepat eksekusi kode, bukan penantian.
Ringkasan#
- opcache menyimpan hasil kompilasi bytecode di memori bersama sehingga tahap kompilasi tidak diulang pada setiap permintaan.
- JIT pada opcache mengompilasi kode yang paling sering dijalankan menjadi instruksi mesin dan membutuhkan
jit_buffer_sizedi atas nol. - Kenaikan performa JIT terbesar terjadi pada beban yang padat CPU, sedangkan aplikasi web yang menunggu I/O memperoleh manfaat moderat.
- Pola pikir profiling menempatkan pengukuran sebelum pengoptimalan, dan perbaikan algoritmik yang terbukti biasanya paling berdampak.
Referensi#
- Opcache — PHP Manual — Gambaran resmi ekstensi opcache dan cara kerjanya.
- Runtime Configuration — PHP Manual — Daftar direktif konfigurasi opcache termasuk opcache.jit dan jit_buffer_size.
- Fitur baru PHP 8.0 — PHP Manual — Daftar fitur PHP 8.0 yang memperkenalkan JIT compiler.
- Xdebug: Profiler — Xdebug — Dokumentasi profiler Xdebug untuk mengukur waktu eksekusi per fungsi.