Pengantar
Jadi, Anda mendapatkan email yang menyatakan bahwa game web/aplikasi web Anda berperforma buruk setelah jangka waktu tertentu, Anda memeriksa kode, tidak melihat apa pun yang menonjol, hingga Anda membuka alat performa memori Chrome, dan melihat ini:
Salah satu rekan kerja Anda tertawa, karena dia menyadari bahwa Anda mengalami masalah performa terkait memori.
Dalam tampilan grafik memori, pola gergaji gergaji ini sangat menunjukkan masalah performa yang mungkin kritis. Seiring penggunaan memori yang meningkat, Anda akan melihat area diagram juga meningkat dalam rekaman linimasa. Jika diagram turun secara tiba-tiba, ini adalah instance saat Garbage Collector 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 Pemroses Sampah. Dalam banyak bahasa, programmer bertanggung jawab secara langsung untuk mengalokasikan dan mengosongkan memori dari Memory Heap sistem. Namun, sistem Garbage Collector mengelola tugas ini atas nama programmer, yang berarti bahwa objek tidak langsung dilepaskan dari memori ketika programmer menghapus referensinya, melainkan di lain waktu ketika heuristik GC memutuskan bahwa hal itu akan bermanfaat untuk melakukannya. Proses keputusan ini mengharuskan GC mengeksekusi beberapa analisis statistik pada objek aktif dan tidak aktif, yang membutuhkan blok waktu untuk dijalankan.
Pembersihan sampah sering kali digambarkan sebagai kebalikan dari pengelolaan memori manual, yang mengharuskan programmer menentukan objek mana yang akan dide-alokasikan dan dikembalikan ke sistem memori
Proses di mana GC mengklaim kembali memori tidak bebas, biasanya mengurangi kinerja yang tersedia dengan meluangkan waktu satu jam untuk melakukan pekerjaannya; di samping itu, sistem itu sendiri yang membuat keputusan kapan akan dijalankan. Anda tidak memiliki kontrol atas tindakan ini, pulsa GC dapat terjadi kapan saja selama eksekusi kode, yang akan memblokir eksekusi kode hingga selesai. Durasi pulsa ini umumnya tidak diketahui oleh Anda; akan memerlukan waktu beberapa saat untuk dijalankan, bergantung pada cara program Anda menggunakan memori pada waktu tertentu.
Aplikasi berperforma tinggi mengandalkan batas performa yang konsisten untuk memastikan pengalaman yang lancar bagi pengguna. Sistem pengumpulan sampah dapat mengganggu sasaran ini, karena dapat berjalan pada waktu acak untuk durasi acak, sehingga mengurangi waktu yang tersedia yang diperlukan aplikasi untuk memenuhi sasaran performanya.
Mengurangi Churn Memori, Mengurangi beban Pembersihan Sampah Memori
Seperti yang telah disebutkan, pulsa GC akan terjadi setelah serangkaian heuristik menentukan bahwa ada cukup objek tidak aktif sehingga pulsa akan bermanfaat. Dengan demikian, kunci untuk mengurangi jumlah waktu yang diperlukan Garbage Collector dari aplikasi Anda terletak pada pengurangan kasus pembuatan dan rilis objek yang berlebihan sebanyak mungkin. Proses pembuatan/pembebasan objek ini sering disebut “churn memori”. Jika Anda dapat mengurangi churn memori selama masa aktif aplikasi, Anda juga akan mengurangi jumlah waktu yang diperlukan GC dari eksekusi. Artinya, Anda perlu menghapus/mengurangi jumlah objek yang dibuat dan dihancurkan, sehingga Anda harus berhenti mengalokasikan memori.
Proses ini akan memindahkan grafik memori Anda dari:
menjadi ini:
Dalam model ini, Anda dapat melihat bahwa grafik tidak lagi memiliki pola seperti gergaji gergaji, tetapi tumbuh sangat besar di awal, lalu perlahan meningkat dari waktu ke waktu. Jika Anda mengalami masalah performa karena churn memori, inilah jenis grafik yang sebaiknya Anda buat.
Beralih ke JavaScript memori statis
JavaScript Memori Statis adalah teknik yang melibatkan pra-alokasi, di awal aplikasi Anda, semua memori yang akan diperlukan selama masa aktifnya, dan mengelola memori tersebut selama eksekusi karena objek tidak lagi diperlukan. Kita dapat mendekati tujuan ini dalam beberapa langkah sederhana:
- Lengkapi aplikasi Anda untuk menentukan jumlah maksimum objek memori live yang diperlukan (per jenis) untuk berbagai skenario penggunaan
- Terapkan kembali kode Anda untuk mengalokasikan jumlah maksimum tersebut terlebih dahulu, lalu ambil/lepas 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 kumpulan objek yang tidak digunakan yang memiliki jenis yang sama. Jika Anda memerlukan objek baru untuk kode, alih-alih mengalokasikan objek baru dari Memory Heap, Anda akan mendaur ulang salah satu objek yang tidak digunakan dari kumpulan. Setelah kode eksternal selesai dengan objek, daripada merilisnya ke memori utama, kode akan dikembalikan ke kumpulan. Karena objek tidak pernah didereferensi (alias dihapus) dari kode, objek tersebut tidak akan di-garbage collection. Dengan menggunakan kumpulan objek, kontrol memori akan kembali ke tangan programmer, sehingga mengurangi pengaruh pembersih sampah memori terhadap performa.
Karena ada sekumpulan jenis objek heterogen yang dipertahankan oleh aplikasi, penggunaan kumpulan objek yang tepat mengharuskan Anda memiliki satu kumpulan per jenis yang mengalami churn tinggi selama runtime aplikasi Anda.
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 beberapa tingkat dalam hal kebutuhan untuk mengalokasikan objek baru. Setelah beberapa kali dijalankan, Anda akan bisa memahami batas atas ini dengan baik, dan dapat mengalokasikan objek tersebut di awal aplikasi.
Melakukan pra-pengalokasian objek
Menerapkan penggabungan objek ke dalam project akan memberi Anda maksimum teoretis untuk jumlah objek yang diperlukan selama runtime aplikasi. Setelah menjalankan situs melalui berbagai skenario pengujian, Anda dapat memahami dengan baik 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 eksekusi.
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 maksimum rata-rata dapat memberi Anda jejak memori yang lebih kecil untuk pengguna non-pengguna berat.
Bukan solusi ajaib
Ada banyak klasifikasi aplikasi yang pola pertumbuhan memori statisnya dapat menjadi keuntungan. Namun, seperti yang ditunjukkan oleh rekan Chrome DevRel Renato Mangini, ada beberapa kelemahan.
Kesimpulan
Salah satu alasan JavaScript ideal untuk web adalah karena JavaScript adalah bahasa yang cepat, menyenangkan, dan mudah digunakan. Hal ini terutama karena batasan sintaksisnya yang rendah dan penanganan masalah memori untuk Anda. Anda dapat membuat kode dan membiarkannya menangani pekerjaan yang berat. Namun, untuk aplikasi web berperforma tinggi, seperti game HTML5, GC sering kali menghabiskan kecepatan frame yang sangat diperlukan, sehingga mengurangi pengalaman pengguna akhir. Dengan beberapa instrumentasi dan penerapan kumpulan objek yang cermat, Anda dapat mengurangi beban kecepatan frame ini, dan mengklaim kembali waktu tersebut untuk hal-hal yang lebih menakjubkan.
Kode Sumber
Ada banyak implementasi kumpulan objek yang beredar di web, jadi saya tidak akan membuat Anda bosan dengan implementasi lainnya. Sebagai gantinya, saya akan mengarahkan Anda ke artikel berikut, yang masing-masing memiliki nuansa penerapan tertentu; yang penting, mengingat setiap penggunaan aplikasi mungkin memiliki kebutuhan penerapan tertentu.
- Kumpulan objek Gamecore.js
- Beej's Object Pools
- Kumpulan objek Emehrkay yang sangat sederhana
- Koleksi objek game yang berfokus pada game dari Steven Lambert
- Penyiapan objectPool RenderEngine