Optimalisasi program untuk Pengumpul Sampah

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.

Source: https://habr.com/ru/post/id453082/


All Articles