.NET uygulamalarının bellek kullanımını azaltmak mı?


108

.NET uygulamalarının bellek kullanımını azaltmak için bazı ipuçları nelerdir? Aşağıdaki basit C # programını düşünün.

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

X64 için yayın modunda derlenen ve Visual Studio dışında çalışan görev yöneticisi şunları bildirir:

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

Sadece x86 için derlenmişse biraz daha iyi :

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

Daha sonra, aynı şeyi yapan ancak çalışma zamanı başlatıldıktan sonra işlem boyutunu kırpmaya çalışan aşağıdaki programı denedim:

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);
}

Visual Studio dışında x86 Sürümündeki sonuçlar :

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

Bu biraz daha iyi, ancak yine de böylesine basit bir program için aşırı görünüyor. C # sürecini biraz daha zayıf hale getirmenin püf noktaları var mı? Çoğu zaman arka planda çalışmak üzere tasarlanmış bir program yazıyorum. Zaten ayrı bir Uygulama Alanında herhangi bir kullanıcı arayüzü işi yapıyorum , bu da kullanıcı arayüzü öğelerinin güvenli bir şekilde kaldırılabileceği anlamına geliyor, ancak arka planda oturduğunda 10 MB yer kaplaması aşırı görünüyor.

Not : Neden umursayacağıma gelince --- (Güç) kullanıcıları bu şeyler hakkında endişelenme eğilimindedir. Performans üzerinde neredeyse hiçbir etkisi olmasa bile, yarı teknoloji meraklısı kullanıcılar (hedef kitlenim) arka plan uygulama belleği kullanımı hakkında tedirginlik uyandırıyor. Adobe Updater'ın 11 MB bellek aldığını gördüğümde ve oyun oynarken bile 6 MB'nin altına düşebilen Foobar2000'in sakinleştirici dokunuşuyla rahatlamış hissettiğimde bile çıldırıyorum. Modern işletim sistemlerinde biliyorum, bu şey teknik olarak o kadar önemli değil, ama bu algı üzerinde bir etkisi olmadığı anlamına gelmez.


14
Neden umursuyorsun? Özel çalışma seti oldukça düşük. Bellek gereksizse, modern işletim sistemleri diske gidecektir. Yıl 2009. Gömülü sistemler üzerinde bir şeyler inşa etmiyorsanız, 10MB ile ilgilenmemelisiniz.
Mehrdad Afshari

8
.NET'i kullanmayı bırakın ve küçük programlara sahip olabilirsiniz. .NET çerçevesini yüklemek için, birçok büyük DLL'nin belleğe yüklenmesi gerekir.
Adam Sills

2
Bellek Fiyatları katlanarak düşüyor (evet, şimdi 24 GB RAM'li bir Dell ev bilgisayarı sistemi sipariş edebilirsiniz). Uygulamanız> 500MB optimizasyonu kullanmadığı sürece gereksizdir.
Alex

28
@LeakyCode Modern programcıların bu şekilde düşünmesinden gerçekten nefret ediyorum, uygulamanızın bellek kullanımını önemsemelisiniz. Çoğunlukla java veya c # ile yazılan modern uygulamaların çoğunun kaynak yönetimi söz konusu olduğunda oldukça etkisiz olduğunu söyleyebilirim ve bu sayede 2014'te 1998'de win95 ve 64mb ram üzerinde yapabildiğimiz kadar çok uygulamayı çalıştırabiliriz. ... tarayıcının sadece 1 örneği artık 2 gb ram ve 1 gb civarında basit bir IDE tüketiyor. Ram ucuz, ama bu onu boşa harcaman gerektiği anlamına gelmez.
Petr

6
@Petr Kaynak yönetimini önemsemelisiniz. Programcı zamanı da bir kaynaktır. Lütfen 10MB ile 2GB arasında boşa harcamayın.
Mehrdad Afshari

Yanıtlar:


33
  1. Stack Overflow sorusu .NET EXE bellek ayak izine göz atmak isteyebilirsiniz .
  2. MSDN blog gönderisi Working set! = Gerçek bellek ayak izi , çalışma kümesini, işlem belleğini aydınlatmak ve toplam RAM içi tüketiminizde doğru hesaplamaları nasıl yapacağınızla ilgilidir.

Uygulamanızın bellek ayak izini göz ardı etmeniz gerektiğini söylemeyeceğim - açıkçası, daha küçük ve daha verimli olması arzu edilir olma eğilimindedir. Ancak, gerçek ihtiyaçlarınızın ne olduğunu düşünmelisiniz.

Bir bireyin bilgisayarında çalışacak standart bir Windows Formları ve WPF istemci uygulamaları yazıyorsanız ve büyük olasılıkla kullanıcının çalıştığı birincil uygulama olacaksa, bellek ayırma konusunda daha anlamsız davranmaktan kurtulabilirsiniz. (Her şey ayrıldığı sürece.)

Bununla birlikte, burada endişelenmemenizi söyleyen bazı insanlara hitap etmek için: Terminal hizmetleri ortamında, muhtemelen 10, 20 veya daha fazla kullanıcı tarafından kullanılan paylaşılan bir sunucuda çalışacak bir Windows Forms uygulaması yazıyorsanız, o zaman evet , kesinlikle bellek kullanımını düşünmelisiniz. Ve tetikte olmanız gerekecek. Bunu ele almanın en iyi yolu, iyi veri yapısı tasarımı ve ne zaman ve neyi ayırdığınızla ilgili en iyi uygulamaları takip etmektir.


45

.NET uygulamaları, işlem sırasında hem çalışma zamanını hem de uygulamayı yüklemeleri gerektiğinden, yerel uygulamalara kıyasla daha büyük bir ayak izine sahip olacaktır. Gerçekten düzenli bir şey istiyorsanız, .NET en iyi seçenek olmayabilir.

Bununla birlikte, eğer uygulamanız çoğunlukla uyku halindeyse, gerekli bellek sayfalarının hafızadan çıkarılacağını ve bu nedenle çoğu zaman sisteme gerçekten çok fazla yük oluşturmayacağını unutmayın.

Ayak izini küçük tutmak istiyorsanız, bellek kullanımı hakkında düşünmeniz gerekecektir. İşte birkaç fikir:

  • Nesne sayısını azaltın ve herhangi bir örneği gereğinden uzun süre tutmamaya dikkat edin.
  • Farkında olun List<T>onlar% 50 kadar israf edilmesine yol açabilecek gibi çift kapasite ihtiyacı olduğunu ve benzeri türde.
  • Yığın üzerinde daha fazla bellek zorlamak için referans türleri yerine değer türleri kullanmayı düşünebilirsiniz, ancak varsayılan yığın alanının yalnızca 1 MB olduğunu unutmayın.
  • Sıkıştırılmamış ve bu nedenle kolayca parçalanabilen LOH'ye gidecekleri için 85000 bayttan fazla nesnelerden kaçının.

Bu muhtemelen herhangi bir şekilde kapsamlı bir liste değil, sadece birkaç fikir.


IOW, yerel kod boyutunu azaltmak için çalışan aynı tür teknikler .NET'te çalışır mı?
Robert Fraser

Sanırım biraz örtüşme var, ancak yerel kodla bellek kullanımı söz konusu olduğunda daha fazla seçeneğiniz var.
Brian Rasmussen

17

Bu durumda dikkate almanız gereken bir şey, CLR'nin bellek maliyetidir. CLR, her .Net işlemi için yüklenir ve bu nedenle, bellek faktörleri dikkate alınır. Böylesine basit / küçük bir program için CLR'nin maliyeti bellek ayak izinize hakim olacaktır.

Bu temel programın maliyetine kıyasla gerçek bir uygulama oluşturmak ve bunun maliyetini görüntülemek çok daha öğretici olacaktır.


7

Tek başına belirli bir öneri yoktur, ancak CLR Profiler'a (Microsoft'tan ücretsiz indirilebilir) bir göz atabilirsiniz .
Yükledikten sonra, bu nasıl yapılır sayfasına bir göz atın .

Nasıl yapılır:

Bu Nasıl Yapılır, uygulamanızın bellek ayırma profilini araştırmak için CLR Profiler aracını nasıl kullanacağınızı gösterir. CLR Profiler'ı bellek sızıntıları ve aşırı veya verimsiz çöp toplama gibi bellek sorunlarına neden olan kodu tanımlamak için kullanabilirsiniz.


7

"Gerçek" bir uygulamanın bellek kullanımına bakmak isteyebilirsiniz.

Java'ya benzer şekilde, programın boyutuna bakılmaksızın çalışma zamanı için bir miktar sabit ek yük vardır, ancak bu noktadan sonra bellek tüketimi çok daha makul olacaktır.


4

Bu basit programın özel çalışma kümesini azaltmanın hala yolları vardır:

  1. Uygulamanızı NGEN. Bu, JIT derleme maliyetini işleminizden kaldırır.

  2. Uygulamanızı bellek kullanımını azaltarak MPGO kullanarak eğitin ve ardından onu NGEN yapın.


2

Ayak izinizi azaltmanın birçok yolu vardır.

Her zaman .NET'te yaşamak zorunda kalacağınız bir şey , IL kodunuzun yerel görüntüsünün boyutunun çok büyük olmasıdır.

Ve bu kod, uygulama örnekleri arasında tamamen paylaşılamaz. NGEN'ed montajları bile tamamen statik değiller, yine de JITting gerektiren bazı küçük parçalara sahipler.

İnsanlar ayrıca belleği gerekenden çok daha uzun süre engelleyen kod yazma eğilimindedir.

Sık görülen bir örnek: Bir Datareader almak, içeriği sadece bir XML dosyasına yazmak için DataTable'a yüklemek. Bir OutOfMemoryException ile kolayca karşılaşabilirsiniz. OTOH, bir XmlTextWriter kullanabilir ve Datareader'da gezinebilir, veritabanı imlecinde ilerlerken XmlNodes yayınlayabilirsiniz. Bu şekilde, yalnızca geçerli veritabanı kaydına ve onun XML çıktısına bellekte sahip olursunuz. Asla (veya olasılığı düşük olan) daha yüksek bir çöp toplama üretimi elde etmeyecek ve bu nedenle yeniden kullanılabilir.

Aynısı, bazı örneklerin bir listesini almak, bazı şeyler yapmak için de geçerlidir (bir yerde referans olarak kalabilecek binlerce yeni örnek ortaya çıkarır) ve daha sonra bunlara ihtiyacınız olmasa bile, yine de foreach sonrasına kadar her şeye referans verirsiniz. Giriş listenizi ve geçici yan ürünlerinizi açıkça boş bırakmak, bu belleğin siz döngünüzden çıkmadan önce yeniden kullanılabileceği anlamına gelir.

C #, yineleyiciler adı verilen mükemmel bir özelliğe sahiptir. Girişinizi kaydırarak nesneleri akışa almanıza izin verir ve yalnızca bir sonrakini alana kadar geçerli örneği saklar. LINQ kullanıyor olsanız bile, sırf filtrelenmesini istediğiniz için hala hepsini saklamanıza gerek yok.


1

Spesifik soruyu değil, başlıktaki genel soruyu ele almak:

Çok fazla veri döndüren bir COM bileşeni kullanıyorsanız (örneğin, büyük 2xN dizileri çiftler) ve yalnızca küçük bir kesire ihtiyaç duyuluyorsa, belleği .NET'ten gizleyen ve yalnızca veriyi döndüren bir sarmalayıcı COM bileşeni yazabilir. gerekli.

Ana uygulamamda yaptığım buydu ve bellek tüketimini önemli ölçüde artırdı.


0

SetProcessWorkingSetSize veya EmptyWorkingSet API'lerinin, uzun süre çalışan bir işlemde bellek sayfalarını düzenli olarak diske zorlamak için kullanılmasının, makine yeniden başlatılıncaya kadar bir makinedeki mevcut tüm fiziksel belleğin etkin bir şekilde kaybolmasına neden olabileceğini buldum. Bellek yoğun bir görev gerçekleştirdikten sonra çalışma kümesini azaltmak için EmptyWorkingSet API'sini (SetProcessWorkingSetSize kullanmaya alternatif) kullanan yerel bir işleme bir .NET DLL yükledik. 1 günden bir haftaya kadar herhangi bir yerden sonra, bir makinenin Görev Yöneticisi'nde% 99 fiziksel bellek kullanımı göstereceğini, ancak hiçbir işlemin önemli bir bellek kullanımı kullandığı gösterilmediğini buldum. Kısa bir süre sonra makine yanıt vermez hale gelir ve sert bir yeniden başlatma gerektirir. Söz konusu makineler, hem fiziksel hem de sanal donanım üzerinde çalışan 2 düzineden fazla Windows Server 2008 R2 ve 2012 R2 sunucusuydu.

Belki de .NET kodunun yerel bir işleme yüklenmesinin bununla bir ilgisi vardır, ancak riski size ait olmak üzere EmptyWorkingSet (veya SetProcessWorkingSetSize) kullanın. Belki de uygulamanızın ilk lansmanından sonra yalnızca bir kez kullanın. Kodu devre dışı bırakmaya ve Çöp Toplayıcının bellek kullanımını kendi başına yönetmesine izin vermeye karar verdim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.