Belum lama ini, sebuah artikel bagus muncul di Habré Optimasi pengumpulan sampah di layanan .NET yang sangat dimuat . Artikel ini sangat menarik karena penulis, yang dipersenjatai dengan teori, melakukan hal yang mustahil sebelumnya: mereka mengoptimalkan aplikasi mereka menggunakan pengetahuan tentang GC. Dan jika sebelumnya kita tidak tahu bagaimana GC ini bekerja, sekarang ini disajikan kepada kita di piring perak melalui upaya Konrad Cocos dalam bukunya Pro .NET Memory Management . Kesimpulan apa yang telah saya buat untuk diri saya sendiri? Mari kita membuat daftar bidang masalah dan memikirkan cara mengatasinya.
Pada lokakarya CLRium # 5 baru-baru ini: Pengumpul Sampah, kami berbicara tentang GC sepanjang hari. Namun, saya memutuskan untuk menerbitkan satu laporan dengan decoding teks. Ini adalah pembicaraan tentang kesimpulan tentang pengoptimalan aplikasi.
Kurangi Konektivitas Lintas Generasi
Masalah
Untuk mengoptimalkan kecepatan pengumpulan sampah, GC mengumpulkan generasi muda jika memungkinkan. Tetapi untuk melakukan ini, ia juga membutuhkan informasi tentang tautan dari generasi yang lebih tua (mereka dalam hal ini bertindak sebagai root tambahan): dari tabel kartu.
Pada saat yang sama, satu tautan dari generasi yang lebih tua ke generasi yang lebih muda memaksa Anda untuk menutupi area tersebut dengan tabel kartu:
- 4 byte tumpang tindih 4 kb atau maks. 320 objek - untuk arsitektur x86
- 8 byte tumpang tindih 8 kb atau maks. 320 objek - untuk arsitektur x64
Yaitu GC, memeriksa tabel kartu, memenuhi nilai bukan nol di dalamnya, dipaksa untuk memeriksa maksimal 320 objek untuk keberadaan tautan keluar dalam generasi kami.
Karenanya, tautan yang jarang di generasi muda akan membuat GC lebih memakan waktu
Solusi
- Temukan objek dengan koneksi di generasi muda - di dekatnya;
- Jika lalu lintas objek generasi nol seharusnya, gunakan menarik. Yaitu membuat kumpulan objek (tidak akan ada yang baru: tidak akan ada objek generasi nol). Dan selanjutnya, dengan "menghangatkan" kolam dengan dua GC berturut-turut sehingga isinya dijamin gagal pada generasi kedua, Anda karenanya menghindari tautan ke generasi yang lebih muda dan memiliki nol di tabel kartu;
- Hindari tautan ke generasi muda;
Hindari Konektivitas Yang Kuat
Masalah
Sebagai berikut dari algoritma fase kompresi objek dalam SOH:
- Untuk mengompres tumpukan, Anda harus berkeliling pohon dan memeriksa semua tautan, mengoreksi mereka untuk nilai-nilai baru
- Selain itu, tautan dari tabel kartu memengaruhi seluruh kelompok objek
Oleh karena itu, konektivitas objek yang kuat secara umum dapat menyebabkan subsidensi selama GC.
Solusi
- Memiliki objek yang terhubung sangat dekat, dalam satu generasi
- Hindari tautan yang tidak perlu secara umum (misalnya, alih-alih menduplikasi tautan pegangan ini->, gunakan pegangan yang sudah ada ini-> Layanan->)
- Hindari kode dengan konektivitas tersembunyi. Misalnya, penutupan
Pantau penggunaan segmen
Masalah
Selama pekerjaan intensif, suatu situasi mungkin muncul ketika alokasi objek baru menyebabkan penundaan: alokasi segmen baru di bawah tumpukan dan selanjutnya mereka menonaktifkan ketika membersihkan sampah
Solusi
- Menggunakan PerfMon / Sysinternal Utilities untuk mengontrol titik pemilihan segmen baru dan pelepasan dan pelepasannya
- Jika kita berbicara tentang LOH, yang merupakan lalu lintas buffer padat, gunakan ArrayPool
- Jika kita berbicara tentang SOH, pastikan bahwa objek-objek dengan umur yang sama disorot di dekatnya, memberikan Sweep daripada Collect
- SOH: gunakan kolam objek
Jangan mengalokasikan memori di bagian kode yang dimuat
Masalah
Bagian kode yang dimuat mengalokasikan memori:
- Akibatnya, GC memilih jendela alokasi bukan dari 1Kb, tetapi 8Kb.
- Jika jendela kehabisan ruang, ini mengarah ke GC dan perluasan zona tertutup
- Aliran benda baru yang padat akan membuat benda berumur pendek dari utas lainnya dengan cepat pergi ke generasi yang lebih tua dengan kondisi pengumpulan sampah yang lebih buruk
- Yang akan menambah waktu pengumpulan sampah
- Yang akan menyebabkan Stop Dunia lebih lama bahkan dalam mode Bersamaan
Solusi
- Larangan lengkap tentang penggunaan penutupan di bagian kode kritis
- Larangan tinju sepenuhnya pada bagian-bagian penting dari kode (Anda dapat menggunakan emulasi dengan menarik jika perlu)
- Di mana perlu untuk membuat objek sementara untuk penyimpanan data, gunakan struktur. Lebih baik ref struct. Ketika jumlah bidang lebih dari 2, kirimkan dengan ref
Hindari alokasi memori yang tidak perlu di LOH
Masalah
Menempatkan array di LOH mengarah ke fragmentasi atau pembobotan prosedur GC
Solusi
- Gunakan pembagian array menjadi sub-array dan kelas yang merangkum logika bekerja dengan array seperti itu (yaitu, bukan Daftar <T>, di mana mega-array disimpan, MyList dengan array [] [], membagi array sedikit lebih pendek)
- Array akan menuju ke SOH
- Setelah beberapa pengumpulan sampah, mereka akan berbaring di sebelah benda yang selalu hidup dan berhenti mempengaruhi pengumpulan sampah
- Kontrol penggunaan array ganda dengan panjang lebih dari 1000 elemen.
Jika dibenarkan dan memungkinkan, gunakan tumpukan ulir
Masalah
Ada sejumlah objek ultrashort atau objek yang hidup dalam panggilan metode (termasuk panggilan internal). Mereka menciptakan lalu lintas objek
Solusi
- Gunakan alokasi memori pada tumpukan jika memungkinkan:
- Itu tidak memuat banyak
- Tidak memuat GC
- Melepaskan Memori - Instan
- Gunakan
Span T x = stackalloc T[];
bukannya new T[]
jika memungkinkan - Gunakan
Span/Memory
jika memungkinkan - Konversikan algoritma ke
ref stack
tipe ref stack
(StackList: struct, ValueStringBuilder )
Benda bebas sedini mungkin
Masalah
Dibayangkan sebagai berumur pendek, objek jatuh ke gen1, dan kadang-kadang ke gen2.
Ini menghasilkan GC yang lebih berat yang bertahan lebih lama
Solusi
- Anda harus melepaskan referensi objek sedini mungkin
- Jika algoritme yang panjang berisi kode yang bekerja dengan objek apa pun, spasi dengan kode. Tetapi yang dapat dikelompokkan dalam satu tempat, perlu untuk mengelompokkannya, sehingga memungkinkan mereka untuk dikumpulkan sebelumnya.
- Misalnya, pada baris 10, koleksi dikeluarkan, dan pada baris 120, ia disaring.
Tidak perlu menelepon GC.Collect ()
Masalah
Sering terlihat bahwa jika Anda memanggil GC.Collect (), itu akan memperbaiki situasi
Solusi
- Jauh lebih benar untuk mempelajari algoritma operasi GC, lihat aplikasi di bawah ETW dan alat diagnostik lainnya (JetBrains dotMemory, ...)
- Optimalkan area yang paling bermasalah
Hindari Menjepit
Masalah
Pinning menimbulkan sejumlah masalah:
- Pengumpulan sampah yang rumit
- Membuat ruang memori gratis (node item daftar bebas, meja bata, ember)
- Dapat meninggalkan beberapa objek di generasi yang lebih muda, sambil membentuk tautan dari tabel kartu
Solusi
Jika tidak ada jalan keluar lain, gunakan fixed () {}. Metode komitmen ini tidak membuat komitmen nyata: ini hanya terjadi ketika GC telah bekerja di dalam kurung kurawal.
Hindari finalisasi
Masalah
Finalisasi tidak disebut secara deterministik:
- Buang Tanpa diundang () menghasilkan finalisasi dengan semua tautan keluar dari objek
- Objek dependen tertunda lebih lama dari yang direncanakan
- Penuaan, pindah ke generasi yang lebih tua
- Jika pada saat yang sama mereka memuat tautan ke tautan yang lebih muda, mereka menghasilkan tautan dari tabel kartu
- Mempersulit perakitan generasi yang lebih tua, memecah-mecah mereka dan mengarah ke Compacting bukan Sweep
Solusi
Panggil Buang dengan lembut ()
Hindari terlalu banyak utas
Masalah
Dengan sejumlah besar utas, konteks alokasi bertambah, seperti mereka dialokasikan untuk setiap utas:
- Hasilnya, GC.Collect datang lebih cepat.
- Karena kurangnya ruang di segmen sementara, Kumpulkan akan mengikuti Sapu Kolektif
Solusi
- Kontrol jumlah utas dengan jumlah inti
Hindari lalu lintas benda dengan ukuran berbeda
Masalah
Saat lalu lintas objek dari berbagai ukuran dan masa hidup, fragmentasi terjadi:
- Tingkatkan rasio fragmentasi
- Koleksi memicu dengan fase perubahan alamat di semua objek referensi
Solusi
Jika lalu lintas objek dianggap:
- Periksa keberadaan bidang tambahan, kira-kira ukurannya
- Periksa kurangnya manipulasi string: jika memungkinkan, ganti dengan ReadOnlySpan / ReadOnlyMemory
- Lepaskan tautan sesegera mungkin
- Manfaatkan menarik
- Hangatkan cache dan kolam dengan GC ganda untuk benda padat. Dengan demikian, Anda menghindari masalah dengan tabel kartu.