Praktik Terbaik untuk Memaksa Pengumpulan Sampah di C #

118

Dalam pengalaman saya, tampaknya kebanyakan orang akan memberi tahu Anda bahwa tidak bijaksana untuk memaksa pengumpulan sampah tetapi dalam beberapa kasus di mana Anda bekerja dengan objek besar yang tidak selalu terkumpul dalam generasi 0 tetapi di mana memori menjadi masalah, adalah apakah boleh memaksa pengumpulan? Apakah ada praktik terbaik untuk melakukannya?

Echostorm
sumber

Jawaban:

112

Praktik terbaiknya adalah tidak memaksa pengumpulan sampah.

Menurut MSDN:

"Dimungkinkan untuk memaksa pengumpulan sampah dengan memanggil Kumpulkan, tetapi sebagian besar waktu, ini harus dihindari karena dapat menimbulkan masalah kinerja."

Namun, jika Anda dapat menguji kode Anda dengan andal untuk mengonfirmasi bahwa panggilan Kumpulkan () tidak akan berdampak negatif, lanjutkan ...

Cobalah untuk memastikan objek dibersihkan saat Anda tidak lagi membutuhkannya. Jika Anda memiliki objek khusus, lihat menggunakan "menggunakan pernyataan" dan antarmuka IDisposable.

Tautan ini memiliki beberapa saran praktis yang bagus terkait dengan mengosongkan memori / pengumpulan sampah, dll:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

Tandai Ingram
sumber
3
Selain itu, Anda dapat mengatur msdn.microsoft.com/en-us/library/bb384202.aspx
David d C e Freitas
4
Jika objek Anda mengarah ke memori yang tidak dikelola, Anda dapat memberi tahu pengumpul sampah melalui Api GC.AddMemoryPressure ( msdn.microsoft.com/en-us/library/… ). Ini memberikan lebih banyak informasi kepada pengumpul sampah tentang sistem Anda tanpa mengganggu algoritme pengumpulan.
Govert
+1: * lihatlah penggunaan "menggunakan pernyataan" dan antarmuka IDisposable. * Saya bahkan tidak akan mempertimbangkan untuk memaksa kecuali sebagai upaya terakhir - saran yang baik (baca sebagai 'pelepasan tanggung jawab'). Namun, saya memaksa pengumpulan dalam pengujian unit untuk mensimulasikan kehilangan referensi aktif dalam operasi back-end - yang pada akhirnya melempar file TargetOfInvocationNullException.
IAbstract
33

Anggap saja seperti ini - apakah lebih efisien membuang sampah dapur jika tempat sampah sudah 10% atau dibiarkan terisi sebelum dibuang?

Dengan tidak membiarkannya terisi, Anda membuang-buang waktu berjalan ke dan dari tempat sampah di luar. Ini serupa dengan apa yang terjadi ketika utas GC berjalan - semua utas yang dikelola ditangguhkan saat sedang berjalan. Dan Jika saya tidak salah, utas GC dapat dibagikan di antara beberapa AppDomain, jadi pengumpulan sampah memengaruhi semuanya.

Tentu saja, Anda mungkin menghadapi situasi di mana Anda tidak akan menambahkan apa pun ke tempat sampah dalam waktu dekat - misalnya, jika Anda akan berlibur. Maka, ada baiknya membuang sampah sebelum keluar.

Ini MUNGKIN satu kali yang memaksa GC dapat membantu - jika program Anda menganggur, memori yang digunakan tidak dikumpulkan sampah karena tidak ada alokasi.

Maxam
sumber
5
Jika Anda memiliki bayi yang akan mati jika Anda meninggalkannya lebih dari satu menit, dan Anda hanya punya waktu satu menit untuk menangani sampah, maka Anda ingin melakukannya sedikit demi sedikit alih-alih sekaligus. Sayangnya, metode GC :: Collect () tidak lebih cepat jika semakin sering Anda memanggilnya. Jadi untuk mesin waktu nyata, jika Anda tidak bisa hanya menggunakan mekanisme pembuangan dan membiarkan GC mengumpulkan data Anda, maka Anda tidak boleh menggunakan sistem terkelola - sesuai jawaban saya (mungkin di bawah ini, lol).
Jin
1
Dalam kasus saya, saya menjalankan algoritme A * (jalur terpendek) BERULANG untuk menyempurnakannya ... yang, dalam produksi, hanya akan dijalankan sekali (di setiap "peta"). Jadi saya ingin GC dilakukan sebelum setiap iterasi, di luar "blok pengukuran kinerja" saya, karena saya merasa bahwa lebih dekat memodelkan situasi dalam produksi, di mana GC dapat / harus dipaksa setelah menavigasi setiap "peta".
corlettk
Memanggil GC sebelum blok terukur sebenarnya tidak mencontoh keadaan dalam produksi, karena dalam produksi GC akan dilakukan dalam waktu yang tidak terduga. Untuk menguranginya, Anda harus melakukan pengukuran panjang yang akan mencakup beberapa eksekusi GC, dan faktor puncak selama GC ke dalam analisis dan statistik Anda.
Berlangsung
32

Praktik terbaiknya adalah tidak memaksa pengumpulan sampah dalam banyak kasus. (Setiap sistem yang saya kerjakan yang memaksa pengumpulan sampah, memiliki masalah yang menggarisbawahi yang jika diselesaikan akan menghilangkan kebutuhan untuk memaksa pengumpulan sampah, dan mempercepat sistem dengan sangat cepat.)

Ada beberapa kasus saat Anda mengetahui lebih banyak tentang penggunaan memori daripada pengumpul sampah. Hal ini tidak mungkin terjadi dalam aplikasi multi-pengguna, atau layanan yang merespons lebih dari satu permintaan pada satu waktu.

Namun dalam beberapa pemrosesan tipe batch Anda tahu lebih dari GC. Misalnya pertimbangkan aplikasi itu.

  • Diberikan daftar nama file pada baris perintah
  • Memproses satu file kemudian menulis hasilnya ke file hasil.
  • Saat memproses file, membuat banyak objek yang saling terkait yang tidak dapat dikumpulkan hingga pemrosesan file selesai (mis. Pohon parse)
  • Tidak menyimpan banyak status di antara file yang telah diprosesnya .

Anda mungkin dapat membuat kasus (setelah hati-hati) pengujian yang Anda harus memaksa pengumpulan sampah penuh setelah Anda memproses setiap file.

Kasus lain adalah layanan yang bangun setiap beberapa menit untuk memproses beberapa item, dan tidak menyimpan status apa pun saat tidur . Maka memaksakan koleksi lengkap sebelum tidur mungkin bermanfaat.

Satu-satunya saat saya akan mempertimbangkan untuk memaksa koleksi adalah ketika saya tahu bahwa banyak objek telah dibuat baru-baru ini dan sangat sedikit objek yang saat ini direferensikan.

Saya lebih suka memiliki API pengumpulan sampah ketika saya bisa memberikan petunjuk tentang hal semacam ini tanpa harus memaksakan diri saya sendiri.

Lihat juga "Informasi Pertunjukan Rico Mariani "

Ian Ringrose
sumber
2
Analogi: Playschool (sistem) menyimpan krayon (sumber daya). Bergantung pada jumlah anak (tugas) dan kelangkaan warna, guru (.Net) memutuskan bagaimana mengalokasikan dan berbagi di antara anak-anak. Ketika warna langka diminta, guru dapat mengalokasikan dari kolam atau mencari yang tidak digunakan. Guru memiliki keleluasaan untuk secara berkala mengumpulkan krayon yang tidak terpakai (mengumpulkan sampah) untuk menjaga kerapian (mengoptimalkan penggunaan sumber daya). Umumnya orang tua (programmer) tidak dapat menentukan kebijakan merapikan krayon kelas terbaik. Tidur siang yang dijadwalkan oleh seorang anak sepertinya bukan saat yang tepat untuk mengganggu warna anak-anak lain.
AlanK
1
@AlanK, saya suka ini, karena ketika anak-anak pulang pada hari itu, ini adalah waktu yang sangat tepat bagi guru pembantu untuk melakukan pembersihan yang baik tanpa anak-anak menghalangi. (Sistem terbaru yang saya kerjakan baru saja memulai kembali proses layanan pada saat-saat seperti itu yang memaksa GC.)
Ian Ringrose
21

Menurut saya contoh yang diberikan oleh Rico Mariani bagus: mungkin tepat untuk memicu GC jika ada perubahan signifikan pada status aplikasi. Misalnya, dalam editor dokumen, mungkin OK untuk memicu GC saat dokumen ditutup.

denis phillips
sumber
2
Atau tepat sebelum membuka objek bersebelahan besar yang telah menunjukkan riwayat kegagalan dan tidak memberikan resolusi yang efisien untuk meningkatkan perinciannya.
crokusek
17

Ada beberapa pedoman umum dalam pemrograman yang bersifat mutlak. Separuh waktu, ketika seseorang mengatakan 'Anda melakukan kesalahan', mereka hanya melontarkan sejumlah dogma tertentu. Di C, dulu takut akan hal-hal seperti kode atau utas yang dapat mengubah diri sendiri, dalam bahasa GC itu memaksa GC atau sebaliknya mencegah GC berjalan.

Seperti halnya sebagian besar pedoman dan aturan praktis yang baik (dan praktik desain yang baik), ada kesempatan langka di mana masuk akal untuk bekerja di sekitar norma yang ditetapkan. Anda harus sangat yakin bahwa Anda memahami kasusnya, bahwa kasus Anda benar-benar memerlukan pembatalan praktik umum, dan bahwa Anda memahami risiko dan efek samping yang dapat Anda timbulkan. Tapi ada kasus seperti itu.

Masalah pemrograman sangat bervariasi dan membutuhkan pendekatan yang fleksibel. Saya telah melihat kasus di mana masuk akal untuk memblokir GC dalam bahasa pengumpulan sampah dan tempat-tempat yang masuk akal untuk memicunya daripada menunggu itu terjadi secara alami. 95% dari waktu, salah satu dari ini akan menjadi tanda tidak mendekati masalah dengan benar. Tapi 1 kali dalam 20, mungkin ada kasus valid yang harus dibuat untuk itu.


sumber
12

Saya telah belajar untuk tidak mencoba mengakali pengumpulan sampah. Dengan itu, saya hanya tetap menggunakan usingkata kunci saat berhadapan dengan sumber daya yang tidak terkelola seperti file I / O atau koneksi database.

Kon
sumber
27
penyusun? apa hubungannya compiler dengan GC? :)
KristoferA
1
Tidak ada, itu hanya mengkompilasi dan mengoptimalkan kode. Dan ini pasti tidak ada hubungannya dengan CLR ... atau .NET bahkan.
Kon
1
Objek yang menempati banyak memori, seperti gambar yang sangat besar, bisa jadi tidak terkumpul sampahnya, kecuali Anda secara eksplisit mengumpulkannya. Saya pikir ini (benda besar) adalah masalah OP, kurang lebih.
code4life
1
Membungkusnya dalam penggunaan akan memastikan bahwa itu dijadwalkan untuk GC setelah keluar dari ruang lingkup. Kecuali jika komputer meledak, memori tersebut kemungkinan besar akan dibersihkan.
Kon
9

Tidak yakin apakah ini adalah praktik terbaik, tetapi ketika bekerja dengan sejumlah besar gambar dalam satu loop (yaitu membuat dan membuang banyak objek Grafik / Gambar / Bitmap), saya secara teratur membiarkan GC.Collect.

Saya rasa saya membaca di suatu tempat bahwa GC hanya berjalan ketika program (sebagian besar) menganggur, dan bukan di tengah loop intensif, sehingga bisa terlihat seperti area di mana GC manual bisa masuk akal.

Michael Stum
sumber
Anda yakin membutuhkan ini? GC akan mengumpulkan jika memerlukan memori, meskipun kode Anda tidak menganggur.
Konrad Rudolph
Tidak yakin bagaimana itu di .net 3.5 SP1 sekarang, tetapi sebelumnya (1.1 dan saya yakin saya menguji terhadap 2.0) itu membuat perbedaan dalam penggunaan memori. GC tentu saja akan selalu terkumpul saat dibutuhkan, tetapi Anda mungkin masih akan menghabiskan 100 Megs RAM ketika Anda hanya membutuhkan 20. Akan membutuhkan beberapa 'tes lagi
Michael Stum
2
GC dipicu pada alokasi memori saat generasi 0 mencapai ambang tertentu (misalnya 1 MB), bukan saat "ada yang tidak aktif". Jika tidak, Anda bisa berakhir dengan OutOfMemoryException dalam loop hanya dengan mengalokasikan dan segera membuang objek.
liggett78
5
100megs RAM tidak terbuang percuma jika tidak ada proses lain yang membutuhkannya. Ini memberi Anda peningkatan kinerja yang bagus :-P
Orion Edwards
9

Satu kasus yang baru-baru ini saya temui yang memerlukan panggilan manual GC.Collect()adalah ketika bekerja dengan objek C ++ besar yang dibungkus dalam objek C ++ yang dikelola kecil, yang pada gilirannya diakses dari C #.

Pengumpul sampah tidak pernah dipanggil karena jumlah memori terkelola yang digunakan dapat diabaikan, tetapi jumlah memori tidak terkelola yang digunakan sangat besar. Memanggil objek secara manual Dispose()akan mengharuskan saya melacak kapan objek tidak lagi dibutuhkan sendiri, sedangkan panggilan GC.Collect()akan membersihkan objek yang tidak lagi dirujuk .....

Morten
sumber
6
Cara yang lebih baik untuk menyelesaikan ini adalah dengan memanggil GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)konstruktor dan kemudian GC.RemoveMemoryPressure(addedSize)ke finalizer. Dengan cara ini, pengumpul sampah akan berjalan secara otomatis, dengan mempertimbangkan ukuran struktur tidak terkelola yang dapat dikumpulkan. stackoverflow.com/questions/1149181/…
HugoRune
Dan cara yang lebih baik untuk memecahkan masalah adalah dengan memanggil Dispose (), yang seharusnya Anda lakukan.
fabspro
2
Cara terbaik adalah dengan menggunakan struktur Using. Coba / Akhirnya. Membuang merepotkan
TamusJRoyce
7

Saya pikir Anda sudah membuat daftar praktik terbaik dan BUKAN untuk menggunakannya kecuali SANGAT diperlukan. Saya sangat menyarankan untuk melihat kode Anda secara lebih detail, menggunakan alat pembuatan profil jika diperlukan untuk menjawab pertanyaan ini terlebih dahulu.

  1. Apakah Anda memiliki sesuatu di kode Anda yang mendeklarasikan item pada cakupan yang lebih besar dari yang dibutuhkan
  2. Apakah penggunaan memori terlalu tinggi
  3. Bandingkan kinerja sebelum dan sesudah menggunakan GC.Collect () untuk melihat apakah itu benar-benar membantu.
Penjual Mitchel
sumber
5

Misalkan program Anda tidak mengalami kebocoran memori, objek terakumulasi dan tidak dapat di-GC di Gen 0 karena: 1) Mereka direferensikan untuk waktu yang lama jadi masuk ke Gen1 & Gen2; 2) Mereka adalah objek besar (> 80K) jadi masuklah ke LOH (Large Object Heap). Dan LOH tidak melakukan pemadatan seperti pada Gen0, Gen1 & Gen2.

Periksa penghitung kinerja ".NET Memory" dapatkah Anda melihat bahwa 1) masalah sebenarnya bukan masalah. Umumnya, setiap 10 Gen0 GC akan memicu 1 GC Gen1, dan setiap 10 Gen1 GC akan memicu 1 GC Gen2. Secara teoritis, GC1 & GC2 tidak akan pernah bisa di-GC jika tidak ada tekanan pada GC0 (jika penggunaan memori program benar-benar terhubung). Itu tidak pernah terjadi pada saya.

Untuk masalah 2), Anda dapat memeriksa penghitung kinerja ".NET Memory" untuk memverifikasi apakah LOH semakin membengkak. Jika ini benar-benar masalah Anda, mungkin Anda dapat membuat kumpulan objek besar seperti yang disarankan blog ini http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx .

Morgan Cheng
sumber
4

Objek besar dialokasikan di LOH (tumpukan objek besar), bukan di gen 0. Jika Anda mengatakan bahwa mereka tidak dikumpulkan sampah dengan gen 0, Anda benar. Saya percaya mereka dikumpulkan hanya ketika siklus GC penuh (generasi 0, 1 dan 2) terjadi.

Karena itu, saya percaya di sisi lain GC akan menyesuaikan dan mengumpulkan memori lebih agresif ketika Anda bekerja dengan objek besar dan tekanan memori meningkat.

Sulit untuk mengatakan apakah akan mengumpulkan atau tidak dan dalam keadaan apa. Saya biasa melakukan GC.Collect () setelah membuang jendela / formulir dialog dengan banyak kontrol, dll. (Karena pada saat formulir dan kontrolnya berakhir di gen 2 karena membuat banyak contoh objek bisnis / memuat banyak data - tidak objek besar jelas), tetapi sebenarnya tidak melihat efek positif atau negatif dalam jangka panjang dengan melakukannya.

liggett78.dll
sumber
4

Saya ingin menambahkan bahwa: Memanggil GC.Collect () (+ WaitForPendingFinalizers ()) adalah salah satu bagian dari cerita. Seperti yang disebutkan dengan benar oleh orang lain, GC.COllect () adalah kumpulan non-deterministik dan diserahkan kepada kebijaksanaan GC itu sendiri (CLR). Meskipun Anda menambahkan panggilan ke WaitForPendingFinalizers, itu mungkin tidak bersifat deterministik. Ambil kode dari tautan msdn ini dan jalankan kode dengan iterasi loop objek sebagai 1 atau 2. Anda akan menemukan apa arti non-deterministik (menetapkan titik putus di destruktor objek). Tepatnya, destruktor tidak dipanggil ketika hanya ada 1 (atau 2) objek yang tersisa oleh Wait .. (). [Citation reqd.]

Jika kode Anda berurusan dengan sumber daya yang tidak terkelola (mis .: pegangan file eksternal), Anda harus menerapkan destruktor (atau finalizer).

Berikut ini contoh yang menarik:

Catatan : Jika Anda sudah mencoba contoh di atas dari MSDN, kode berikut akan membersihkan udara.

class Program
{    
    static void Main(string[] args)
        {
            SomePublisher publisher = new SomePublisher();

            for (int i = 0; i < 10; i++)
            {
                SomeSubscriber subscriber = new SomeSubscriber(publisher);
                subscriber = null;
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(SomeSubscriber.Count.ToString());


            Console.ReadLine();
        }
    }

    public class SomePublisher
    {
        public event EventHandler SomeEvent;
    }

    public class SomeSubscriber
    {
        public static int Count;

        public SomeSubscriber(SomePublisher publisher)
        {
            publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
        }

        ~SomeSubscriber()
        {
            SomeSubscriber.Count++;
        }

        private void publisher_SomeEvent(object sender, EventArgs e)
        {
            // TODO: something
            string stub = "";
        }
    }

Saya sarankan, pertama analisis apa outputnya dan kemudian jalankan dan kemudian baca alasannya di bawah ini:

{Destruktor hanya dipanggil secara implisit setelah program berakhir. } Untuk membersihkan objek secara deterministik, seseorang harus mengimplementasikan IDisposable dan membuat panggilan eksplisit ke Dispose (). Itulah intinya! :)

Vaibhav
sumber
2

Satu hal lagi, memicu GC Collect secara eksplisit mungkin TIDAK meningkatkan kinerja program Anda. Sangat mungkin memperburuk keadaan.

.NET GC dirancang dan disetel dengan baik untuk menjadi adaptif, yang berarti dapat menyesuaikan ambang GC0 / 1/2 sesuai dengan "kebiasaan" penggunaan memori program Anda. Jadi, itu akan disesuaikan dengan program Anda setelah beberapa waktu berjalan. Setelah Anda menjalankan GC.Collect secara eksplisit, ambang batas akan disetel ulang! Dan .NET harus menghabiskan waktu untuk beradaptasi dengan "kebiasaan" program Anda lagi.

Saran saya selalu percaya .NET GC. Setiap masalah memori muncul, periksa penghitung kinerja ".NET Memory" dan diagnosis kode saya sendiri.

Morgan Cheng
sumber
6
Saya pikir lebih baik Anda menggabungkan jawaban ini dengan jawaban Anda sebelumnya.
Salamander2007
1

Tidak yakin apakah ini praktik terbaik ...

Saran: jangan menerapkan ini atau apapun jika tidak yakin. Evaluasi kembali saat fakta diketahui, lalu lakukan sebelum / sesudah uji performa untuk memverifikasi.

pengguna957965
sumber
0

Namun, jika Anda dapat menguji kode Anda dengan andal untuk mengonfirmasi bahwa panggilan Kumpulkan () tidak akan berdampak negatif, lanjutkan ...

IMHO, ini mirip dengan mengatakan "Jika Anda dapat membuktikan bahwa program Anda tidak akan pernah memiliki bug di masa mendatang, lanjutkanlah ..."

Secara serius, memaksa GC berguna untuk tujuan debugging / pengujian. Jika Anda merasa perlu melakukannya di lain waktu, maka Anda salah, atau program Anda dibuat salah. Bagaimanapun, solusinya tidak memaksa GC ...

Orion Edwards
sumber
"maka entah Anda salah, atau program Anda telah dibuat salah. Bagaimanapun, solusinya tidak memaksa GC ..." Mutlak hampir selalu tidak benar. Ada beberapa keadaan luar biasa yang masuk akal.
gulungan