Pengantar
Jadi, Anda mendapatkan email yang menyatakan bahwa game web/aplikasi web Anda berperforma buruk setelah jangka waktu tertentu, Anda memeriksa kode, tidak melihat sesuatu yang mencolok, hingga Anda membuka alat performa memori Chrome, dan melihat ini:
Salah satu rekan kerja Anda tertawa kecil, karena dia menyadari bahwa Anda mengalami masalah performa terkait memori.
Dalam tampilan grafik memori, pola gigi gergaji ini sangat menunjukkan potensi masalah performa yang kritis. Seiring bertambahnya penggunaan memori, Anda akan melihat area diagram juga bertambah dalam rekaman linimasa. Saat diagram turun tiba-tiba, ini adalah contoh saat Pembersih Sampah Memori telah berjalan, dan membersihkan objek memori yang direferensikan.
Dalam grafik seperti ini, Anda dapat melihat bahwa ada banyak peristiwa Pembersihan Sampah Memori yang terjadi, yang dapat membahayakan performa aplikasi web Anda. Artikel ini akan membahas cara mengontrol penggunaan memori, sehingga mengurangi dampak pada performa Anda.
Pembersihan sampah memori dan biaya performa
Model memori JavaScript dibuat berdasarkan teknologi yang dikenal sebagai Garbage Collector. Dalam banyak bahasa, programmer bertanggung jawab langsung untuk mengalokasikan dan mengosongkan memori dari Heap Memori sistem. Namun, sistem Pembersih Sampah mengelola tugas ini atas nama programmer, yang berarti objek tidak langsung dikosongkan dari memori saat programmer mendereferensikannya, melainkan di lain waktu saat heuristik GC memutuskan bahwa hal tersebut akan bermanfaat. Proses keputusan ini mengharuskan GC menjalankan beberapa analisis statistik pada objek aktif dan tidak aktif, yang memerlukan waktu untuk melakukannya.
Pembersihan sampah memori sering digambarkan sebagai kebalikan dari manajemen memori manual, yang mengharuskan programmer menentukan objek mana yang akan dibatalkan alokasinya dan dikembalikan ke sistem memori
Proses GC mengklaim kembali memori tidaklah gratis, biasanya proses ini mengurangi performa yang tersedia dengan mengambil blok waktu untuk melakukan tugasnya; selain itu, sistem itu sendiri yang membuat keputusan kapan harus berjalan. Anda tidak memiliki kontrol atas tindakan ini, pulsa GC dapat terjadi kapan saja selama eksekusi kode, yang akan memblokir eksekusi kode hingga selesai. Durasi denyut ini umumnya tidak Anda ketahui; akan memerlukan waktu untuk berjalan, bergantung pada cara program Anda menggunakan memori pada titik tertentu.
Aplikasi berperforma tinggi mengandalkan batas performa yang konsisten untuk memastikan pengalaman yang lancar bagi pengguna. Sistem pembersih sampah memori dapat mengganggu sasaran ini, karena dapat berjalan pada waktu acak dengan durasi acak, sehingga menghabiskan waktu yang tersedia yang dibutuhkan aplikasi untuk memenuhi sasaran performanya.
Mengurangi churn memori, mengurangi beban pembersihan sampah memori
Seperti yang telah disebutkan, denyut GC akan terjadi setelah serangkaian heuristik menentukan bahwa ada cukup objek tidak aktif sehingga denyut akan bermanfaat. Oleh karena itu, kunci untuk mengurangi jumlah waktu yang dibutuhkan Pembersih Sampah Memori dari aplikasi Anda terletak pada penghapusan sebanyak mungkin kasus pembuatan dan pelepasan objek yang berlebihan. Proses pembuatan/pembebasan objek yang sering ini disebut “churn memori”. Jika Anda dapat mengurangi churn memori selama masa aktif aplikasi, Anda juga akan mengurangi jumlah waktu yang dibutuhkan GC dari eksekusi Anda. Artinya, Anda perlu menghapus/mengurangi jumlah objek yang dibuat dan dihancurkan, secara efektif, Anda harus berhenti mengalokasikan memori.
Proses ini akan memindahkan grafik memori Anda dari ini :
menjadi berikut:
Dalam model ini, Anda dapat melihat bahwa grafik tidak lagi memiliki pola seperti gigi gergaji, tetapi tumbuh pesat di awal, lalu meningkat perlahan seiring waktu. Jika Anda mengalami masalah performa karena churn memori, ini adalah jenis grafik yang perlu Anda buat.
Beralih ke JavaScript memori statis
Memori Statis JavaScript adalah teknik yang melibatkan pra-alokasi, di awal aplikasi Anda, semua memori yang akan diperlukan selama masa aktifnya, dan mengelola memori tersebut selama eksekusi saat objek tidak lagi diperlukan. Kita dapat mencapai sasaran ini dalam beberapa langkah sederhana:
- Lengkapi aplikasi Anda untuk menentukan jumlah maksimum objek memori aktif yang diperlukan (per jenis) untuk berbagai skenario penggunaan
- Terapkan ulang kode Anda untuk mengalokasikan jumlah maksimum tersebut terlebih dahulu, lalu ambil/lepaskan secara manual, bukan ke memori utama.
Pada kenyataannya, untuk mencapai #1, kita perlu melakukan sedikit #2, jadi mari kita mulai dari sana.
Kumpulan objek
Secara sederhana, penggabungan objek adalah proses mempertahankan sekumpulan objek yang tidak digunakan yang memiliki jenis yang sama. Saat Anda memerlukan objek baru untuk kode, daripada mengalokasikan objek baru dari Heap Memori sistem, Anda dapat mendaur ulang salah satu objek yang tidak digunakan dari kumpulan. Setelah kode eksternal selesai dengan objek, objek tersebut akan dikembalikan ke kumpulan, bukan dilepaskan ke memori utama. Karena objek tidak pernah dihapus referensinya (alias dihapus) dari kode, objek tersebut tidak akan dibersihkan oleh pembersih sampah memori. Dengan memanfaatkan kumpulan objek, kontrol memori dikembalikan ke tangan programmer, sehingga mengurangi pengaruh pembersih sampah memori terhadap performa.
Karena ada kumpulan jenis objek heterogen yang dikelola aplikasi, penggunaan kumpulan objek yang tepat mengharuskan Anda memiliki satu kumpulan per jenis yang mengalami churn tinggi selama runtime aplikasi.
var newEntity = gEntityObjectPool.allocate();
newEntity.pos = {x: 215, y: 88};
//..... do some stuff with the object that we need to do
gEntityObjectPool.free(newEntity); //free the object when we're done
newEntity = null; //free this object reference
Untuk sebagian besar aplikasi, Anda pada akhirnya akan mencapai tingkat tertentu dalam hal kebutuhan untuk mengalokasikan objek baru. Selama beberapa kali menjalankan aplikasi, Anda akan dapat mengetahui batas atas ini, dan dapat mengalokasikan jumlah objek tersebut di awal aplikasi.
Pra-alokasi objek
Menerapkan penggabungan objek ke dalam project akan memberi Anda jumlah maksimum teoretis untuk jumlah objek yang diperlukan selama runtime aplikasi. Setelah menjalankan situs melalui berbagai skenario pengujian, Anda dapat memahami jenis persyaratan memori yang akan diperlukan, dan dapat membuat katalog data tersebut di suatu tempat, serta menganalisisnya untuk memahami batas atas persyaratan memori untuk aplikasi Anda.
Kemudian, dalam versi pengiriman aplikasi, Anda dapat menetapkan fase inisialisasi untuk mengisi otomatis semua kumpulan objek ke jumlah yang ditentukan. Tindakan ini akan mendorong semua inisialisasi objek ke bagian depan aplikasi Anda, dan mengurangi jumlah alokasi yang terjadi secara dinamis selama eksekusinya.
function init() {
//preallocate all our pools.
//Note that we keep each pool homogeneous wrt object types
gEntityObjectPool.preAllocate(256);
gDomObjectPool.preAllocate(888);
}
Jumlah yang Anda pilih sangat berkaitan dengan perilaku aplikasi Anda; terkadang jumlah maksimum teoretis bukanlah opsi terbaik. Misalnya, memilih nilai maksimum rata-rata dapat memberi Anda jejak memori yang lebih kecil untuk pengguna non-super.
Bukan solusi ajaib
Ada seluruh klasifikasi aplikasi yang dapat memanfaatkan pola pertumbuhan memori statis. Namun, seperti yang ditunjukkan oleh sesama Chrome DevRel Renato Mangini, ada beberapa kelemahan.
Kesimpulan
Salah satu alasan JavaScript ideal untuk web adalah karena bahasa ini cepat, menyenangkan, dan mudah untuk dipelajari. Hal ini terutama karena batasan sintaksisnya yang rendah dan penanganan masalah memori untuk Anda. Anda dapat membuat kode dan membiarkannya menangani pekerjaan yang rumit. Namun, untuk aplikasi web berperforma tinggi, seperti game HTML5, GC sering kali dapat mengurangi kecepatan frame yang sangat dibutuhkan, sehingga mengurangi pengalaman pengguna akhir. Dengan instrumentasi yang cermat dan penerapan kumpulan objek, Anda dapat mengurangi beban ini pada kecepatan frame, dan mengklaim kembali waktu tersebut untuk hal-hal yang lebih mengagumkan.
Kode sumber
Ada banyak implementasi kumpulan objek yang tersebar di web, jadi kami tidak akan membuat Anda bosan dengan implementasi lainnya. Sebagai gantinya, kami akan mengarahkan Anda ke referensi berikut, yang masing-masing memiliki nuansa penerapan tertentu; hal ini penting, mengingat setiap penggunaan aplikasi mungkin memiliki kebutuhan penerapan tertentu.
- Kumpulan objek Gamecore.js
- Beej’s Object Pools
- Kumpulan objek super sederhana Emehrkay
- Kumpulan objek yang berfokus pada game dari Steven Lambert
- Penyiapan objectPool RenderEngine