Mengurangi penggunaan memori aplikasi .NET?

108

Apa sajakah tip untuk mengurangi penggunaan memori aplikasi .NET? Pertimbangkan program C # sederhana berikut.

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();
    }
}

Dikompilasi dalam mode rilis untuk x64 dan berjalan di luar Visual Studio, pengelola tugas melaporkan hal berikut:

Working Set:          9364k
Private Working Set:  2500k
Commit Size:         17480k

Sedikit lebih baik jika dikompilasi hanya untuk x86 :

Working Set:          5888k
Private Working Set:  1280k
Commit Size:          7012k

Saya kemudian mencoba program berikut, yang melakukan hal yang sama tetapi mencoba untuk memangkas ukuran proses setelah inisialisasi runtime:

class Program
{
    static void Main(string[] args)
    {
        minimizeMemory();
        Console.ReadLine();
    }

    private static void minimizeMemory()
    {
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
        SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle,
            (UIntPtr) 0xFFFFFFFF, (UIntPtr)0xFFFFFFFF);
    }

    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetProcessWorkingSetSize(IntPtr process,
        UIntPtr minimumWorkingSetSize, UIntPtr maximumWorkingSetSize);
}

Hasil pada x86 Rilis di luar Visual Studio:

Working Set:          2300k
Private Working Set:   964k
Commit Size:          8408k

Mana yang sedikit lebih baik, tetapi masih tampak berlebihan untuk program sederhana seperti itu. Apakah ada trik untuk membuat proses C # sedikit lebih ramping? Saya sedang menulis program yang dirancang untuk berjalan di latar belakang hampir sepanjang waktu. Saya sudah melakukan hal-hal antarmuka pengguna di Domain Aplikasi terpisah yang berarti hal-hal antarmuka pengguna dapat dibongkar dengan aman, tetapi menghabiskan 10 MB ketika hanya duduk di latar belakang tampaknya berlebihan.

NB Tentang mengapa saya akan peduli --- Pengguna (Daya) cenderung khawatir tentang hal-hal ini. Bahkan jika itu hampir tidak berpengaruh pada kinerja, pengguna semi-tech-savvy (audiens target saya) cenderung masuk ke hissy fit tentang penggunaan memori aplikasi latar belakang. Bahkan saya merasa aneh ketika melihat Adobe Updater menggunakan memori 11 MB dan merasa terhibur dengan sentuhan menenangkan dari Foobar2000, yang dapat berukuran di bawah 6 MB bahkan saat bermain. Saya tahu dalam sistem operasi modern, hal-hal ini sebenarnya tidak terlalu penting secara teknis, tetapi itu tidak berarti tidak memengaruhi persepsi.

Robert Fraser
sumber
14
Mengapa Anda peduli? Perangkat kerja pribadi cukup rendah. OS modern akan beralih ke disk jika memori tidak diperlukan. Ini tahun 2009. Kecuali Anda membangun sesuatu pada sistem tertanam, Anda tidak perlu peduli tentang 10MB.
Mehrdad Afshari
8
Berhenti menggunakan .NET dan Anda dapat memiliki program kecil. Untuk memuat kerangka .NET, banyak DLL besar yang perlu dimuat ke dalam memori.
Adam Sills
2
Harga Memori turun secara eksponensial (ya, Anda dapat memesan sistem komputer rumah Dell dengan RAM 24 GB sekarang). Kecuali jika aplikasi Anda menggunakan> 500MB, pengoptimalan tidak diperlukan.
Alex
28
@LeakyCode Saya benar-benar benci programmer modern berpikir seperti ini, Anda harus peduli dengan penggunaan memori aplikasi Anda. Saya dapat mengatakan bahwa sebagian besar aplikasi modern, sebagian besar ditulis dalam java atau c # cukup tidak efektif dalam hal manajemen sumber daya, dan berkat itu pada tahun 2014 kami dapat menjalankan aplikasi sebanyak yang kami bisa pada tahun 1998 pada win95 dan 64mb ram ... hanya 1 browser sekarang memakan 2gb ram dan IDE sederhana sekitar 1gb. Ram itu murah, tapi bukan berarti Anda harus menyia-nyiakannya.
Petr
6
@Petr Anda harus peduli tentang manajemen sumber daya. Waktu pemrogram juga merupakan sumber daya. Harap jangan menggeneralisasi pemborosan 10MB hingga 2GB secara berlebihan.
Mehrdad Afshari

Jawaban:

33
  1. Anda mungkin ingin memeriksa pertanyaan Stack Overflow jejak memori .NET EXE .
  2. Postingan blog MSDN Working set! = Footprint memori aktual adalah tentang mendemistifikasi set kerja, memproses memori, dan cara melakukan kalkulasi akurat pada total konsumsi dalam RAM Anda.

Saya tidak akan mengatakan bahwa Anda harus mengabaikan jejak memori aplikasi Anda - jelas, lebih kecil dan lebih efisien memang cenderung diinginkan. Namun, Anda harus mempertimbangkan kebutuhan Anda yang sebenarnya.

Jika Anda menulis aplikasi klien Windows Forms dan WPF standar yang ditakdirkan untuk dijalankan pada PC individu, dan kemungkinan besar merupakan aplikasi utama tempat pengguna beroperasi, Anda dapat menghindari menjadi lebih lesu tentang alokasi memori. (Selama semuanya dibatalkan alokasinya.)

Namun, untuk menjawab beberapa orang di sini yang mengatakan tidak perlu khawatir tentang itu: Jika Anda menulis aplikasi Windows Forms yang akan berjalan di lingkungan layanan terminal, di server bersama yang mungkin digunakan oleh 10, 20 atau lebih pengguna, maka ya , Anda benar-benar harus mempertimbangkan penggunaan memori. Dan Anda harus waspada. Cara terbaik untuk mengatasinya adalah dengan desain struktur data yang baik dan dengan mengikuti praktik terbaik terkait waktu dan apa yang Anda alokasikan.

John Rudy
sumber
45

Aplikasi .NET akan memiliki footprint yang lebih besar dibandingkan dengan aplikasi asli karena keduanya harus memuat runtime dan aplikasi dalam prosesnya. Jika Anda menginginkan sesuatu yang sangat rapi, .NET mungkin bukan pilihan terbaik.

Namun, perlu diingat bahwa jika aplikasi Anda sebagian besar tertidur, halaman memori yang diperlukan akan ditukar dari memori dan dengan demikian tidak terlalu menjadi beban sistem pada umumnya.

Jika Anda ingin membuat footprint kecil, Anda harus memikirkan penggunaan memori. Berikut beberapa ide:

  • Kurangi jumlah objek dan pastikan untuk tidak menyimpan contoh lebih lama dari yang dibutuhkan.
  • Ketahuilah List<T>dan jenis serupa yang menggandakan kapasitas saat dibutuhkan karena dapat menyebabkan hingga 50% limbah.
  • Anda dapat mempertimbangkan untuk menggunakan tipe nilai daripada tipe referensi untuk memaksa lebih banyak memori pada tumpukan, tetapi perlu diingat bahwa ruang tumpukan default hanya 1 MB.
  • Hindari objek lebih dari 85000 byte, karena akan menuju ke LOH yang tidak dipadatkan dan dengan demikian dapat dengan mudah terfragmentasi.

Itu mungkin bukan daftar lengkap dengan cara apa pun, tetapi hanya beberapa ide.

Brian Rasmussen
sumber
IOW, jenis teknik yang sama yang bekerja untuk mengurangi ukuran kode asli bekerja di .NET?
Robert Fraser
Saya kira ada beberapa tumpang tindih, tetapi dengan kode asli Anda memiliki lebih banyak pilihan dalam hal penggunaan memori.
Brian Rasmussen
17

Satu hal yang perlu Anda pertimbangkan dalam hal ini adalah biaya memori CLR. CLR dimuat untuk setiap proses .Net dan karenanya menjadi faktor pertimbangan memori. Untuk program yang sederhana / kecil, biaya CLR akan mendominasi jejak memori Anda.

Akan jauh lebih instruktif untuk membuat aplikasi nyata dan melihat biayanya dibandingkan dengan biaya program baseline ini.

JaredPar
sumber
7

Tidak ada saran khusus, tetapi Anda dapat melihat CLR Profiler (unduh gratis dari Microsoft).
Setelah Anda menginstalnya, lihat halaman petunjuk ini .

Dari cara-untuk:

Ini Cara menunjukkan kepada Anda cara menggunakan alat CLR Profiler untuk menyelidiki profil alokasi memori aplikasi Anda. Anda dapat menggunakan CLR Profiler untuk mengidentifikasi kode yang menyebabkan masalah memori, seperti kebocoran memori dan pengumpulan sampah yang berlebihan atau tidak efisien.

Donat
sumber
7

Mungkin ingin melihat penggunaan memori dari aplikasi "nyata".

Mirip dengan Java, ada sejumlah overhead tetap untuk runtime terlepas dari ukuran programnya, tetapi konsumsi memori akan jauh lebih masuk akal setelah titik itu.

Eric Petroelje
sumber
4

Masih ada cara untuk mengurangi set kerja privat dari program sederhana ini:

  1. NGEN aplikasi Anda. Ini menghapus biaya kompilasi JIT dari proses Anda.

  2. Latih aplikasi Anda menggunakan MPGO yang mengurangi penggunaan memori dan kemudian NGEN.

Feng Yuan
sumber
2

Ada banyak cara untuk mengurangi jejak kaki Anda.

Satu hal yang harus selalu Anda lakukan di .NET adalah ukuran gambar asli kode IL Anda sangat besar

Dan kode ini tidak dapat dibagikan sepenuhnya di antara instance aplikasi. Bahkan rakitan NGEN tidak sepenuhnya statis, mereka masih memiliki beberapa bagian kecil yang perlu JITting.

Orang juga cenderung menulis kode yang memblokir memori jauh lebih lama dari yang diperlukan.

Contoh yang sering terlihat: Mengambil Datareader, memuat konten ke DataTable hanya untuk menulisnya ke dalam file XML. Anda dapat dengan mudah mengalami OutOfMemoryException. OTOH, Anda dapat menggunakan XmlTextWriter dan menelusuri Datareader, memancarkan XmlNodes saat Anda menggulir kursor database. Dengan begitu, Anda hanya memiliki catatan database saat ini dan keluaran XML-nya di memori. Yang tidak akan pernah (atau tidak mungkin) mendapatkan generasi pengumpulan sampah yang lebih tinggi dan dengan demikian dapat digunakan kembali.

Hal yang sama berlaku untuk mendapatkan daftar beberapa contoh, melakukan beberapa hal (yang memunculkan ribuan contoh baru, yang mungkin tetap dirujuk di suatu tempat), dan meskipun Anda tidak membutuhkannya setelah itu, Anda masih mereferensikan semuanya sampai setelah pendahuluan. Secara eksplisit membatalkan daftar input Anda dan produk sampingan sementara Anda berarti, memori ini dapat digunakan kembali bahkan sebelum Anda keluar dari loop Anda.

C # memiliki fitur unggulan yang disebut iterator. Mereka memungkinkan Anda untuk mengalirkan objek dengan menggulir melalui masukan Anda dan hanya menyimpan contoh saat ini sampai Anda mendapatkan yang berikutnya. Bahkan dengan menggunakan LINQ, Anda tetap tidak perlu menyimpan semuanya hanya karena Anda ingin memfilternya.

Robert Giesecke
sumber
1

Mengatasi pertanyaan umum dalam judul dan bukan pertanyaan spesifik:

Jika Anda menggunakan komponen COM yang mengembalikan banyak data (katakanlah array 2xN besar ganda) dan hanya sebagian kecil yang dibutuhkan maka seseorang dapat menulis komponen COM pembungkus yang menyembunyikan memori dari .NET dan hanya mengembalikan data yang dibutuhkan.

Itulah yang saya lakukan di aplikasi utama saya dan secara signifikan meningkatkan konsumsi memori.

Peter Mortensen
sumber
0

Saya telah menemukan bahwa menggunakan API SetProcessWorkingSetSize atau EmptyWorkingSet untuk memaksa halaman memori ke disk secara berkala dalam proses yang berjalan lama dapat mengakibatkan semua memori fisik yang tersedia pada mesin menghilang secara efektif hingga mesin di-boot ulang. Kami memiliki DLL .NET yang dimuat ke dalam proses asli yang akan menggunakan EmptyWorkingSet API (alternatif untuk menggunakan SetProcessWorkingSetSize) untuk mengurangi set kerja setelah melakukan tugas intensif memori. Saya menemukan bahwa setelah antara 1 hari hingga seminggu, mesin akan menunjukkan 99% penggunaan memori fisik di Task Manager sementara tidak ada proses yang terbukti menggunakan penggunaan memori yang signifikan. Segera setelah mesin menjadi tidak responsif, membutuhkan booting ulang yang sulit. Mesin tersebut lebih dari 2 lusin server Windows Server 2008 R2 dan 2012 R2 yang berjalan pada perangkat keras fisik dan virtual.

Mungkin memiliki kode .NET yang dimuat ke dalam proses asli ada hubungannya dengan itu, tetapi gunakan EmptyWorkingSet (atau SetProcessWorkingSetSize) dengan risiko Anda sendiri. Mungkin hanya menggunakannya sekali setelah peluncuran awal aplikasi Anda. Saya telah memutuskan untuk menonaktifkan kode dan membiarkan Pengumpul Sampah mengelola penggunaan memori sendiri.

Stuart Welch
sumber