Apa itu cache opcode php?

Ekstensi PHP opcache mengimplementasikan berbagai fungsi untuk mempercepat PHP secara transparan. Seperti namanya, asal dan tujuan utamanya adalah caching opcode, tetapi saat ini juga berisi pengoptimal dan kompiler just-in-time. Namun, posting blog ini hanya akan fokus pada aspek caching opcode

Opcache memiliki tiga lapis cache. Cache memori bersama yang asli, cache file diperkenalkan di PHP 7, dan fungsionalitas preloading ditambahkan di PHP 7. 4. Kami akan membahas semua ini secara bergantian

Sementara opcache secara nominal merupakan ekstensi independen, fungsinya sangat bergantung pada detail implementasi mesin, dan modifikasi pada mesin sering kali memerlukan perubahan pada opcache juga. Dengan demikian, cara kerja opcache berbeda secara signifikan antara versi PHP. Artikel ini menjelaskan status pada PHP 8. 1 dan menyoroti beberapa perubahan dalam versi ini

Berbagi memori

Tujuan utama opcache adalah untuk meng-cache artefak kompilasi di memori bersama, untuk menghindari keharusan mengkompilasi ulang skrip PHP pada setiap eksekusi

Pada sistem mirip Unix, satu segmen memori bersama ukuran tetap (SHM) dialokasikan saat startup. Untuk menangani permintaan, PHP kemudian akan melakukan proses tambahan atau menelurkan utas tambahan. Proses/utas ini akan melihat segmen SHM di alamat yang sama

Karena Windows tidak mendukung forking, biasanya malah menelurkan proses PHP yang sepenuhnya terpisah, yang tidak memiliki ruang alamat bersama. Ini adalah masalah besar untuk opcache, karena mengharuskan segmen SHM dipetakan pada alamat yang sama di setiap proses. Jika tidak, pointer ke SHM tidak akan valid di seluruh proses

Untuk membuat ini berfungsi, opcache menyimpan alamat dasar SHM, dan mencoba memetakan segmen di alamat yang sama di proses lain. Jika ini gagal, opcache kembali menggunakan file cache. Namun, meski berhasil, ada batasannya. Meskipun ini menjamin alamat yang sama untuk segmen SHM, alamat fungsi/kelas internal mungkin berbeda antar proses karena ASLR. Ini berarti bahwa pada Windows, artefak yang di-cache tidak mungkin bergantung pada fungsi/kelas internal, dll

Windows adalah satu-satunya platform di mana dua proses PHP yang tidak terkait dapat berbagi SHM opcache yang sama. Misalnya, dua pemanggilan CLI bersamaan dapat berbagi cache yang sama, yang tidak mungkin dilakukan pada sistem operasi lain. Pengaturan opcache.cache_id ada untuk memaksa cache yang berbeda dalam kasus ini

Karena mempertahankan perilaku terpisah untuk Windows adalah sesuatu yang menyusahkan, opcache dapat menghentikan dukungan untuk pemasangan kembali dari proses yang tidak terkait di masa mendatang, yang berarti bahwa pada Windows, penggunaan SAPI berbasis utas daripada berbasis proses akan diperlukan.

Mengunci dan kekekalan

Saat memori bersama digunakan, selalu penting untuk mempertimbangkan model akses Anda. Karena kami tidak ingin melakukan operasi penguncian berbutir halus atau penghitungan referensi atom saat runtime, model memori opcache akhirnya menjadi sangat sederhana. Memori bersama tidak dapat diubah

Opcache pada dasarnya hanya memiliki dua kunci. Salah satunya adalah kunci tulis, yang hanya dapat dipegang oleh satu proses yang diizinkan untuk memodifikasi SHM. Saat kunci tulis ditahan, proses lain masih diizinkan untuk membaca SHM. Dengan demikian, memegang kunci tulis umumnya hanya memungkinkan Anda untuk mengalokasikan memori baru di segmen SHM dan menulis padanya, tetapi tidak mengubah memori bersama yang sudah dialokasikan dan berpotensi digunakan (dengan beberapa pengecualian)

Opsi opcache.protect_memory_ dapat digunakan untuk melindungi seluruh segmen SHM setiap kali kunci tulis tidak ditahan, yang berguna untuk mendeteksi pelanggaran invarian kekekalan (tetapi tidak boleh diaktifkan dalam produksi karena alasan kinerja)

Kunci lainnya adalah kunci baca yang diperoleh saat permintaan menggunakan SHM untuk pertama kalinya. Itu tidak melacak apa yang sedang digunakan dan apakah itu berhenti digunakan. Satu-satunya tujuan adalah untuk mencatat bahwa cache sedang digunakan dalam permintaan ini

Tujuan dari penguncian ini adalah untuk memfasilitasi restart opcache. Karena kami tidak melacak bagian mana dari cache yang digunakan dengan sangat halus, tidak mungkin menghapus apa pun dari cache opcode. Saat cache sudah penuh, sebagai gantinya dijadwalkan mulai ulang

Jika mulai ulang dijadwalkan, maka permintaan yang baru dimulai tidak akan menggunakan cache SHM (tetapi mungkin kembali ke cache file). Saat jumlah pengguna turun menjadi nol, seluruh cache dihapus dan kita dapat memulai dari awal. Jika jumlah pengguna tidak turun ke nol dalam opcache.force_restart_timeout, maka opcache akan mematikan pengguna yang tersisa

Petunjuk peta

Beberapa struktur yang disimpan dalam cache SHM perlu (atau setidaknya ingin) merujuk data per permintaan. Misalnya, meskipun definisi fungsi umumnya tidak dapat diubah, definisi tersebut mungkin berisi variabel statis, yang akan berbeda untuk setiap permintaan. Demikian pula, fungsi menggunakan run-time cache untuk meng-cache resolusi simbol khusus permintaan

Karena kami tidak dapat menyimpan informasi per-permintaan dalam cache memori bersama yang tidak dapat diubah, kami menggunakan tipuan "penunjuk peta" sebagai gantinya. Daripada menyimpan pointer ke variabel statis, kami malah menyimpan referensi ke mana variabel statis akan disimpan

Dalam implementasi saat ini, penunjuk peta mengambil salah satu dari dua bentuk. Entah itu adalah penunjuk biasa ke penyimpanan penunjuk yang sebenarnya, yang merupakan representasi yang digunakan ketika struktur tidak di-cache di SHM. Pointer tipuan biasanya dialokasikan arena

Alternatifnya, penunjuk peta hanya menyimpan offset dari alamat dasar, di mana alamat dasar akan berbeda untuk setiap permintaan. Ini adalah representasi yang digunakan untuk struktur yang tidak dapat diubah dalam memori bersama. Kami melacak seberapa besar area penunjuk peta yang digunakan harus dan membidiknya pada setiap permintaan

For mutable memory: map_ptr & 1 == 0

map pointer ----> indirection pointer -----> static variables
                  (arena allocated)


For immutable memory: map_ptr & 1 == 1

map base pointer: slot 0
                  slot 1
    + map offset: slot 2 -----> static variables
                  slot 3

Meskipun sudah jelas mengapa kita memerlukan tipuan dalam kasus kedua (pisahkan area penunjuk peta untuk setiap permintaan), orang mungkin bertanya-tanya apa tujuan penunjuk tipuan dalam kasus pertama. Karena memori bisa berubah, kita bisa menyimpan penunjuk variabel statis secara langsung. Ini memang hanya artefak sejarah, dan tipuan yang tidak perlu kemungkinan besar akan hilang di PHP 8. 2

String yang diinternir

Pada titik ini, mari kita kesampingkan sebentar untuk membahas string yang diinternir. String dalam PHP direpresentasikan sebagai struktur yang dihitung referensi yang menyimpan panjang string, isinya, dan hashnya. Meskipun string dapat dibagikan, mungkin juga ada beberapa string dengan konten yang sama, jika dibuat secara terpisah

String yang diinternir dideduplikasi. Hanya akan ada satu string yang diinternir dengan konten tertentu. Ini menghemat memori dan dapat membuat perbandingan lebih efisien, karena jalur cepat persamaan penunjuk lebih cenderung terpicu. String yang diinternir dalam PHP juga tidak dapat diubah karena tidak dihitung referensi

Tanpa opcache, PHP memisahkan string yang diinternir menjadi persisten dan per-permintaan. String yang diinternir terus-menerus dibuat selama startup, misalnya untuk nama kelas/fungsi internal. String per-permintaan dibuat untuk simbol dan literal dalam skrip PHP (jika belum ada string tetap yang diinternir untuknya) dan dibuang di akhir permintaan

Ketika opcache diaktifkan, string yang diinternir disimpan di SHM, sehingga duplikasinya dihapus di seluruh proses dan dapat dirujuk oleh struktur yang di-cache di SHM. Saat startup, opcache akan menyalin string yang diinternir ke dalam SHM berdasarkan upaya terbaik (mungkin tidak mengetahui tentang semua pointer yang disimpan di suatu tempat), tetapi ini tidak penting untuk kebenaran

Selain itu, pembuatan string yang diinternir selama permintaan dinonaktifkan. Sebagai gantinya, string normal yang tidak diasingkan akan dibuat. Hanya ketika skrip yang dikompilasi di-cache (dan kunci tulis SHM diperoleh) string dapat diubah menjadi string yang diinternir SHM

Cache entri kelas

Skrip PHP berisi banyak referensi ke kelas dalam bentuk string, mis. g. new Foo atau tipe Foo $param. Karena identitas sebenarnya dari Foo mungkin berbeda di antara permintaan, tidak mungkin untuk mengompilasinya menjadi referensi kelas langsung

Mengambil entri kelas dari nama kelas relatif mahal untuk seberapa umum itu. Kita perlu mengecilkan string dan mencarinya di tabel hash kelas. Untuk referensi seperti new Foo pencarian ini di-cache dalam cache run time fungsi. Namun, tidak selalu memungkinkan untuk menggunakan cache run time. Misalnya, pemeriksaan tipe properti tidak dapat menggunakan cache run time dan sebelum PHP 8. 1 digunakan untuk mengganti nama string dengan entri kelas langsung di dalam tipe, yang berarti tipe tersebut tidak dapat hidup di SHM

PHP 8. 1 memperkenalkan cache entri kelas, yang menggabungkan string yang diinternir dengan penunjuk peta. Untuk string yang diinternir yang digunakan pada posisi tertentu (deklarasi kelas dan nama tipe), slot penunjuk peta dialokasikan, yang menyimpan entri kelas yang diselesaikan untuk nama ini. Untuk menghindari bertambahnya ukuran string, ini menggunakan trik

Biasanya, string yang diinternir selalu memiliki jumlah referensi 2. Namun, jumlah referensi sebenarnya tidak masalah, hanya perlu lebih besar dari 1 untuk memastikan string terduplikasi saat modifikasi. String dengan refcount 1 dapat dimodifikasi di tempat. Dengan demikian, kita dapat menggunakan bidang refcount untuk menyimpan offset penunjuk peta untuk digunakan sebagai cache entri kelas

Ini memang datang dengan beberapa batasan, karena terikat pada mekanisme string yang diinternir. Misalnya, jika opcache diaktifkan tetapi skrip tidak di-cache, maka string yang diinternir tidak akan digunakan dan akibatnya cache entri kelas tidak akan tersedia

Salah satu hal yang menyenangkan tentang cache entri kelas adalah cukup umum dan tidak terikat pada konstruksi bahasa tertentu (seperti cache run-time). Jika Anda menulis new ReflectionClass(Foo::class), pencarian kelas dapat di-cache, meskipun terjadi secara dinamis

Bertahan

Kegigihan sebenarnya dari skrip ke dalam memori bersama relatif mudah. Skrip pertama kali dikompilasi seperti biasa, terlepas dari beberapa opsi untuk memastikan tidak ada ketergantungan lintas file yang digunakan selama kompilasi. Hasil kompilasi dipindahkan dari tabel fungsi/kelas global ke dalam struktur skrip persisten mandiri

Kemudian ukuran segmen memori bersama yang diperlukan dihitung. Langkah ini harus persis mencerminkan logika dari langkah bertahan yang sebenarnya, tetapi (kebanyakan) tidak mengubah skrip. Jika alokasi memori bersama gagal, kita masih bisa mem-bypass opcache dan menjalankannya seperti biasa. Satu-satunya modifikasi yang dilakukan langkah "persist calc" adalah mengubah string menjadi string yang diinternir SHM jika memungkinkan, karena string yang diinternir disimpan dalam segmen ukuran tetap yang terpisah dari skrip yang bertahan. String yang berhasil diinternir tidak diperhitungkan dalam ukuran skrip

Terakhir, langkah bertahan menyalin skrip ke memori bersama dan membebaskan skrip asli. Untuk melakukannya, ini melacak tabel xlat, yang memetakan pointer asli ke pointer baru di memori bersama. Ini memungkinkan penyelesaian penggunaan pointer yang sama berulang kali

Tembolok warisan

Kelas secara internal datang dalam dua bentuk. Kelas yang tidak tertaut mewakili deklarasi kelas seperti yang Anda tulis dalam kode. Ini berisi metode yang dideklarasikan dalam kelas itu dan ketergantungan referensi (kelas induk, antarmuka, sifat) sebagai string. Kelas tertaut mewakili deklarasi kelas yang telah berhasil menyelesaikan pewarisan. Ini berisi metode/properti/dll yang diwariskan dan ketergantungan referensi sebagai entri kelas yang diselesaikan

Saat melihat satu skrip, kelas biasanya ada dalam bentuk yang tidak ditautkan (kecuali jika tidak memiliki ketergantungan). Menautkan kelas membutuhkan melihat kelas di file lain. Namun, deklarasi kelas yang digunakan mungkin berbeda dari satu permintaan ke permintaan berikutnya

Sebelum PHP 8. 1, ini berarti bahwa hanya template kelas yang tidak ditautkan yang di-cache, dan pewarisan masih harus dilakukan pada setiap permintaan. Karena pewarisan adalah proses yang cukup mahal, ini memiliki dampak kinerja yang tidak sepele. PHP 8. 1 mengatasinya dengan memperkenalkan cache warisan

Cache warisan menyimpan hasil warisan tertaut untuk sekumpulan dependensi tertentu. Saat pewarisan diminta saat run-time, dependensi nama kelas diselesaikan menjadi entri kelas dan jika entri cache untuk kumpulan dependensi ini sudah ada, itu akan digunakan. Meskipun dependensi dapat berbeda di antara permintaan, dalam praktiknya biasanya akan sama, jadi pewarisan hanya perlu dilakukan sekali

Jika tidak ada entri cache, kelas yang tidak ditautkan disalin dari SHM ke dalam memori per-proses yang dapat berubah dan proses pewarisan dilakukan di atasnya (di tempat). Hasilnya disimpan ke dalam cache warisan menggunakan proses persistensi normal, bersama dengan dependensi yang entri cache ini valid

Pramuat

Preloading adalah solusi yang lebih radikal untuk masalah pewarisan. Apa pun yang dimuat oleh skrip preload akan bertahan di seluruh permintaan. Dengan demikian, aman untuk menggunakan ketergantungan skrip silang dalam kasus ini. Kerugiannya adalah preload state tidak dapat diubah tanpa me-restart PHP

Beberapa manfaat preloading kemungkinan telah usang oleh cache warisan di PHP 8. 1, meskipun preloading masih memiliki beberapa kelebihan. Kelas tersedia dalam bentuk yang sepenuhnya diwariskan pada awal permintaan. Satu-satunya biaya pra-pemuatan per permintaan adalah membersihkan area penunjuk peta. Penggunaan opcache normal masih memerlukan autoloading, mencari skrip persisten, mendaftarkan entri dalam tabel hash global, mencari dan memeriksa dependensi untuk cache warisan, dll.

Preloading dapat beroperasi dalam dua mode. Ketika kelas dimuat dengan mudah menggunakan require_, pewarisan akan terjadi seperti biasanya dan pemuatan awal dapat mendukung kelas dengan skenario pewarisan kompleks yang sewenang-wenang (termasuk siklus varians). Ini juga memudahkan untuk memastikan bahwa semua dependensi yang diperlukan disediakan oleh pemuat otomatis

Sebagai alternatif, dimungkinkan untuk melakukan pramuat file menggunakan opcache.cache_id0. Dalam hal ini opcache akan mencoba memuat kelas terlebih dahulu jika semua dependensinya juga tersedia. Jika tidak, itu akan mengeluarkan peringatan dan menyimpan skrip dengan cara lama. Sebelum PHP 8. 1 persyaratan "semua dependensi" agak bermasalah

Di versi PHP sebelumnya, kelas yang tidak ditautkan dipertahankan dalam dua bagian. Satu yang benar-benar tidak dapat diubah, dan satu lagi yang harus disalin ke dalam memori per permintaan, karena dapat dimodifikasi saat run-time. Ini termasuk tipe properti serta penginisialisasi konstanta/properti. Jika ini tidak dapat diselesaikan sepenuhnya selama pemuatan awal, kelas tidak dapat dimuat sebelumnya, karena kami tidak dapat melakukan salinan per permintaan dalam kasus tersebut. Di PHP8. 1 semua bagian yang dapat dimodifikasi run-time yang tersisa dialihkan ke penunjuk peta, sehingga melonggarkan kendala pada apa yang dianggap sebagai "ketergantungan". Sekarang ini hanya mencakup orang tua/antarmuka/sifat, serta tipe yang diperlukan untuk melakukan pemeriksaan varians

Pemeriksaan varians adalah masalah lain. Diperlukan atau tidaknya tipe argumen/pengembalian untuk melakukan pemeriksaan varian sangat sulit untuk ditentukan sebelumnya. Ini tergantung pada apakah suatu metode sebenarnya merupakan override (yang tidak jelas dengan adanya sifat) dan apakah hubungan subtipe dapat ditentukan tanpa memuat kelas (mis. g. jika jenis dalam metode induk dan anak persis sama). Versi PHP sebelumnya menyelesaikan ini secara heuristik, membutuhkan lebih banyak ketergantungan daripada yang diperlukan. PHP 8. 1 sebagai gantinya hanya akan mencoba pewarisan pada salinan kelas dan membuangnya jika gagal

Ini berarti bahwa preloading berbasis opcache.cache_id_0 harus lebih dapat diprediksi di PHP 8. 1

Tembolok file

Cache file yang diperkenalkan di PHP 7 dapat digunakan secara mandiri (opcache.cache_id2) atau bersama dengan cache SHM sebagai cache tingkat kedua. Dalam kasus terakhir, itu akan digunakan pada start dingin, atau ketika cache SHM tidak tersedia selama restart opcache. Di Windows, fallback cache file diaktifkan secara default, untuk memastikan bahwa setidaknya beberapa caching tersedia jika reattachment SHM gagal

Serialisasi cache file dimulai dari representasi skrip yang bertahan, baik di SHM (tingkat kedua) atau di wilayah memori sementara (mandiri), tetapi dibuat menggunakan mekanisme persistensi yang biasa. Serialisasi sebenarnya kemudian menggantikan semua pointer dengan offset ke dalam wilayah memori ("pointer unswizzling"). Ini memungkinkan unserialisasi yang efisien dengan menambahkan pointer basis baru ke semua pointer

Komplikasi utama dalam model ini adalah string yang diinternir, karena ini adalah satu-satunya petunjuk yang tidak mengarah ke wilayah memori yang bertahan. String yang diinternir yang direferensikan malah diserialisasikan ke dalam wilayah memori yang terpisah. Pada unserialization, upaya dilakukan untuk mengonversinya kembali ke string yang diinternir SHM

Unserialization bekerja dengan menyalin konten file (termasuk skrip serial dan area string yang diinternir) ke dalam buffer. Dalam mode mandiri, buffer ini bersifat non-sementara dan unserialization (pointer swizzling) terjadi langsung di buffer ini

Dalam mode tingkat kedua, buffer ini biasanya bersifat sementara. Alih-alih, alokasi SHM dibuat, di mana skrip berseri disalin dan di mana skrip tidak diserialisasi. Dalam hal ini semua string yang diinternir juga perlu diubah menjadi string yang diinternir SHM. Buffer sementara kemudian dapat dibuang. Namun, jika tidak semua string yang diinternir dapat disisipkan karena string buffer overflow yang diinternir, maka segmen SHM ditinggalkan dan unserialization per permintaan seperti dalam kasus mandiri dilakukan

Bagaimana cara menghapus cache di opcode PHP?

PHP berjalan di CLI . Untuk menghapus Opcache di CLI, cukup restart perintah PHP Anda . Biasanya sesederhana CTRL+C untuk membatalkan perintah dan memulainya lagi.

Apa gunanya OPcache?

OPcache adalah cache opcode yang menyimpan bytecode skrip PHP yang telah dikompilasi dalam memori bersama untuk eksekusi lebih cepat . OPcache akan mempercepat layanan berbasis PHP yang menghemat waktu kompilasi skrip Anda. Praktik terbaik untuk mengoptimalkan kinerja PHP adalah topik yang sangat luas dan saya dapat membahas banyak hal di dalamnya.

Bagaimana cara mengaktifkan cache opcode di PHP?

Mengaktifkan opcode caching . Pilih kotak centang di samping ekstensi caching opcode yang ingin Anda aktifkan . Jika Anda menggunakan PHP versi 5. 4 atau lebih tua, pilih apc. Jika Anda menggunakan PHP versi 5. 5 atau lebih baru, pilih opcache.

Apa perbedaan antara OPcache dan memcache di PHP?

OPcache adalah untuk mempercepat akses kode. memcached adalah untuk mempercepat akses data . Mereka benar-benar berbeda, dan sepenuhnya mandiri. Namun, Anda dapat menggunakan opcache untuk akses data dengan menulis data ke dalam file php.