C # 'da bayt cinsinden nesne örneğinin boyutunu bulun


114

Herhangi bir rastgele örnek için (farklı nesnelerin koleksiyonları, kompozisyonlar, tek nesneler, vb.)

Bayt cinsinden boyutunu nasıl belirleyebilirim?

(Şu anda çeşitli nesnelerden oluşan bir koleksiyonum var ve bunların toplu boyutunu belirlemeye çalışıyorum)

DÜZENLEME: Birisi bunu yapabilecek Object için bir uzatma yöntemi yazdı mı? Bu oldukça temiz bir imo olurdu.



Yanıtlar:


60

Her şeyden önce bir uyarı: Aşağıdakiler kesinlikle çirkin, belgesiz hackler alemindedir. Bu çalışmaya güvenmeyin - şu anda sizin için çalışsa bile, herhangi bir küçük veya büyük .NET güncellemesiyle yarın çalışmayı durdurabilir.

Bu makaledeki bilgileri CLR dahili öğeleri MSDN Magazine Issue 2005 Mayıs - CLR'nin Çalışma Zamanı Nesnelerini Nasıl Oluşturduğunu Görmek İçin .NET Framework Internals'da İnceleyin - son kontrol ettiğimde hala uygulanabilirdi. İşte bu şekilde yapılır ( TypeHandletür aracılığıyla dahili "Temel Örnek Boyutu" alanını alır ).

object obj = new List<int>(); // whatever you want to get the size of
RuntimeTypeHandle th = obj.GetType().TypeHandle;
int size = *(*(int**)&th + 1);
Console.WriteLine(size);

Bu, 3.5 SP1 32-bit üzerinde çalışır. Alan boyutlarının 64 bitte aynı olup olmadığından emin değilim - değilse türleri ve / veya ofsetleri ayarlamanız gerekebilir.

Bu, tüm örneklerin aynı, iyi tanımlanmış türlere sahip olduğu tüm "normal" türler için çalışacaktır. Bunun doğru olmadığı kesin olarak diziler ve dizelerdir ve ben de inanıyorum StringBuilder. Onlar için, içerilen tüm öğelerin boyutunu temel örnek boyutlarına eklemeniz gerekir.


Hayır. Bunu yapmanın "uygun" bir yolu yoktur, çünkü bu iyi huylu bir .NET uygulamasının ilk etapta ilgilenmesi gereken bir şey değildir. Yukarıdaki , belirli bir CLR uygulamasının dahili veri yapıları ile doğrudan eşleşir (örneğin, bir sonraki .NET sürümünde kolayca değişebilir).
Pavel Minaev

3
bunun C # ile mi yoksa sadece yönetilen c ++ 'da mı çalışması gerekiyor? C # şimdiye kadar denediğim için mutlu değil:Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
Maslow

17
Bunun .NET 4 sürümü güvenli olmayan koda bile ihtiyaç duymaz: Marshal.ReadInt32(type.TypeHandle.Value, 4)x86 ve x64 için çalışır. Yalnızca yapı ve sınıf türlerini test ettim. Bunun değer türleri için kutulu boyutu döndürdüğünü unutmayın . @Pavel Belki cevabınızı güncelleyebilirsiniz.
jnm2

2
@ sab669 iyi, örneğinde typeile değiştirin obj.GetType(). Hangi çerçeveyi kullandığınız önemli değil, yalnızca hangi CLR (v2 veya v4 veya CoreCLR). Bunu CoreCLR'de denemedim.
jnm2

2
@SamGoldberg Bunu manuel olarak hesaplamak, milyonlarca uç durumla çok iş gerektirir. Sizeof, nesnelerin çalışma zamanı grafiğinin bellek tüketimini değil, bir nesnenin statik boyutunu söyler. VS2017'nin bellek ve CPU profili, ReSharper ve diğer araçlar gibi çok iyidir ve bunu ölçmek için kullanırdım.
jnm2

21

Eğer serileştirilebilir nesnelerle çalışıyorsanız, bir ikili serileştirici ile seri hale getirerek (ancak çıktıyı unutulacak şekilde yönlendirerek) boyutu yaklaşık olarak tahmin edebilirsiniz.

class Program
{
    static void Main(string[] args)
    {
        A parent;
        parent = new A(1, "Mike");
        parent.AddChild("Greg");
        parent.AddChild("Peter");
        parent.AddChild("Bobby");

        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf =
           new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
        SerializationSizer ss = new SerializationSizer();
        bf.Serialize(ss, parent);
        Console.WriteLine("Size of serialized object is {0}", ss.Length);
    }
}

[Serializable()]
class A
{
    int id;
    string name;
    List<B> children;
    public A(int id, string name)
    {
        this.id = id;
        this.name = name;
        children = new List<B>();
    }

    public B AddChild(string name)
    {
        B newItem = new B(this, name);
        children.Add(newItem);
        return newItem;
    }
}

[Serializable()]
class B
{
    A parent;
    string name;
    public B(A parent, string name)
    {
        this.parent = parent;
        this.name = name;
    }
}

class SerializationSizer : System.IO.Stream
{
    private int totalSize;
    public override void Write(byte[] buffer, int offset, int count)
    {
        this.totalSize += count;
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override bool CanSeek
    {
        get { return false; }
    }

    public override bool CanWrite
    {
        get { return true; }
    }

    public override void Flush()
    {
        // Nothing to do
    }

    public override long Length
    {
        get { return totalSize; }
    }

    public override long Position
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }

    public override long Seek(long offset, System.IO.SeekOrigin origin)
    {
        throw new NotImplementedException();
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }
}

6
Elbette, bu size minimum bir boyut kazandırabilir, ancak size bellekteki boyut hakkında hiçbir şey söylemez.
John Saunders

Lol, yanıtları kontrol etmek için geri dönmeden önce aldığım bir sonraki ampul ikili diziselleştiriciyi kullanıyordu. John, bu sana belleğin gerçek boyutunu nasıl vermez?
Janie

2
"Serileştirici" amaçları için size serileştiricinin istediği boyut olacak serileştirilmiş boyutu verecektir. Bunlar muhtemelen "bellekte oturma" amaçlarından farklıdır. Örneğin serileştirici, daha küçük tam sayıları üç baytta depolar.
John Saunders

5
Dediğim gibi, bu sadece bir tahmin. Mükemmel değil, ama size hafızadaki boyut hakkında "hiçbir şey" söylemediğine katılmıyorum. Bunun size bir fikir verdiğini söyleyebilirim - daha büyük serileştirmeler genellikle daha büyük bellek içi boyutlarla ilişkilendirilir. Orada bazı ilişkisi.
BlueMonkMN

Katılıyorum - .NET nesne grafiğinin boyutunun tahmini bir tahminini almak yararlıdır.
Craig Shearer

8

Yönetilmeyen türler, yani değer türleri için yapılar:

        Marshal.SizeOf(object);

Yönetilen nesneler için yakınlaştığım bir tahmin.

        long start_mem = GC.GetTotalMemory(true);

        aclass[] array = new aclass[1000000];
        for (int n = 0; n < 1000000; n++)
            array[n] = new aclass();

        double used_mem_median = (GC.GetTotalMemory(false) - start_mem)/1000000D;

Serileştirmeyi kullanmayın.Bir ikili biçimlendirici üstbilgiler ekler, böylece sınıfınızı değiştirebilir ve eski bir serileştirilmiş dosyayı değiştirilmiş sınıfa yükleyebilirsiniz.

Ayrıca size bellekteki gerçek boyutu söylemeyecek veya bellek hizalamasını hesaba katmayacaktır.

[Düzenle] Sınıfınızın her özelliğinde BiteConverter.GetBytes (prop-değeri) 'i tekrar tekrar kullanarak, içeriği bayt cinsinden alırsınız, bu, sınıfın veya referansların ağırlığını saymaz, ancak gerçeğe çok daha yakındır. Veriler için bir bayt dizisi ve boyut önemliyse işaretçi dökümünü kullanarak değerlere erişmek için yönetilmeyen bir proxy sınıfı kullanmanızı öneririm, bunun hizalı olmayan bellek olacağını unutmayın, bu nedenle eski bilgisayarlarda yavaş olacaktır, ancak MODERN RAM'deki BÜYÜK veri kümeleri olacaktır. RAM'den okumak için boyutun en aza indirilmesi, hizalanmamış olmaktan daha büyük bir etki olacağından, önemli ölçüde daha hızlıdır.


5

Bu, mevcut .NET uygulaması için geçerli değildir, ancak çöp toplanan / yönetilen çalışma zamanlarıyla ilgili akılda tutulması gereken bir şey, bir nesnenin ayrılan boyutunun programın ömrü boyunca değişebileceğidir. Örneğin, bazı nesilsel çöp toplayıcıların ( Generational / Ulterior Reference Counting Hybrid toplayıcısı gibi ) belirli bilgileri yalnızca bir nesne fidanlıktan olgun alana taşındıktan sonra saklaması gerekir.

Bu, nesne boyutunu açığa çıkarmak için güvenilir, genel bir API oluşturmayı imkansız kılar.


İlginç. Öyleyse insanlar nesnelerinin / nesne koleksiyonlarının boyutunu dinamik olarak belirlemek için ne yapıyor?
Janie

2
Neye ihtiyaç duyduklarına bağlı. P / Invoke (yerel kod birlikte çalışma) için ise Marshal.SizeOf (typeof (T)) kullanırlar. Bellek profili oluşturma için, bilgileri sağlamak için yürütme ortamıyla işbirliği yapan ayrı bir profil oluşturucu kullanırlar. Bir dizideki eleman hizalamayla ilgileniyorsanız, SizeOf IL işlem kodunu bir DynamicMethod içinde kullanabilirsiniz (bunun için .NET çerçevesinde daha kolay bir yol olduğunu düşünmüyorum).
Sam Harwell

5

bazı optimizasyonlar ile güvenli çözüm CyberSaving / MemoryUsage kodu . bazı durumlarda:

/* test nullable type */      
TestSize<int?>.SizeOf(null) //-> 4 B

/* test StringBuilder */    
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) sb.Append("わたしわたしわたしわ");
TestSize<StringBuilder>.SizeOf(sb ) //-> 3132 B

/* test Simple array */    
TestSize<int[]>.SizeOf(new int[100]); //-> 400 B

/* test Empty List<int>*/    
var list = new List<int>();  
TestSize<List<int>>.SizeOf(list); //-> 205 B

/* test List<int> with 100 items*/
for (int i = 0; i < 100; i++) list.Add(i);
TestSize<List<int>>.SizeOf(list); //-> 717 B

Ayrıca sınıflarla da çalışır:

class twostring
{
    public string a { get; set; }
    public string b { get; set; }
}
TestSize<twostring>.SizeOf(new twostring() { a="0123456789", b="0123456789" } //-> 28 B

Benim de alacağım yaklaşım budur. A) sonsuz özyinelemeden ve b) aynı belleği iki kez eklemekten kaçınmak için bir grafiğe önceden karşılaşılan nesneler kümesini ekleyebilirsiniz.
mafu

4

Bunu çalışma zamanında yapmak imkansızdır.

Bununla birlikte, nesne boyutunu görüntüleyen çeşitli bellek profil oluşturucuları vardır.

DÜZENLEME : CLR Profilleme API'sini kullanarak ilkinin profilini çıkaran ve uzaktan veya başka bir şey aracılığıyla onunla iletişim kuran ikinci bir program yazabilirsiniz .


17
Çalışma zamanında yapılması imkansızsa, bellek profil oluşturucu bilgileri nasıl sağlıyor?
Janie

2
Profil Oluşturma API'sini kullanarak. Ancak, bir program kendi profilini çıkaramaz
SLaks

İlginç. Ya kodun nesnelerin çok fazla bellek tükettiği durumlarla ilgilenmesini istersem?
Janie

4
O zaman kendinin farkında olan bir yazılımla uğraşıyorsun ve çok korkarım. :-) Cidden, "tek sorumluluk sorumlusu" - programın program olmasına izin verin, bırakın çok fazla bellek alan nesneleri başka bir kod parçası izlesin.
John Saunders

2
@Janie: Büyüklüğün önemi ve performansla nasıl bir ilişkisi olduğu hakkında da varsayımlar yaparsınız. Bunu yapmadan önce gerçek bir düşük seviye CLR performans uzmanı (Profil Oluşturma API'sini zaten bilen türden) olmak isteyeceğinizi düşünüyorum. Aksi takdirde, önceki deneyimlerinizi, uygulanmadıkları bir duruma uyguluyor olabilirsiniz.
John Saunders


2

AFAIK, aslında her üyenin boyutunu bayt cinsinden derinlemesine saymadan yapamazsınız. Ancak yine, bir üyenin boyutu (bir koleksiyondaki öğeler gibi) nesnenin boyutuna mı yoksa bu üyeye bir işaretçi nesnenin boyutuna göre mi sayılır? Nasıl tanımladığına bağlı.

Önbelleğimdeki nesneleri tükettikleri belleğe göre sınırlamak istediğim yerden daha önce bu durumla karşılaştım.

Bunu yapacak bir numara varsa, bunu bilmekten memnuniyet duyarım!



1

Tüm genel üye veya mülk bilgilerini (nesnenin türüne göre) toplamak için yansımayı kullanabilirsiniz. Bununla birlikte, nesne üzerindeki her bir veri parçasında gezinmeden boyutu belirlemenin bir yolu yoktur.


1

[Serializable]Sınıf gerektirmeyen ve sonucun kesin bilim yerine bir tahmin olduğu bir çözüm arayan herkes için . Bulabildiğim en iyi yöntem, UTF32 kodlamasını kullanarak bir memorystream'de json serileştirmedir.

private static long? GetSizeOfObjectInBytes(object item)
{
    if (item == null) return 0;
    try
    {
        // hackish solution to get an approximation of the size
        var jsonSerializerSettings = new JsonSerializerSettings
        {
            DateFormatHandling = DateFormatHandling.IsoDateFormat,
            DateTimeZoneHandling = DateTimeZoneHandling.Utc,
            MaxDepth = 10,
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore
        };
        var formatter = new JsonMediaTypeFormatter { SerializerSettings = jsonSerializerSettings };
        using (var stream = new MemoryStream()) { 
            formatter.WriteToStream(item.GetType(), item, stream, Encoding.UTF32);
            return stream.Length / 4; // 32 bits per character = 4 bytes per character
        }
    }
    catch (Exception)
    {
        return null;
    }
}

Hayır, bu size bellekte kullanılacak tam boyutu vermez. Daha önce de belirtildiği gibi, bu mümkün değil. Ama size kaba bir tahmin verecektir.

Bunun da oldukça yavaş olduğunu unutmayın.


1

Pavel ve jnm2'den:

private int DumpApproximateObjectSize(object toWeight)
{
   return Marshal.ReadInt32(toWeight.GetType().TypeHandle.Value, 4);
}

Bir yandan dikkat edin çünkü yalnızca bitişik bellek nesneleriyle çalışır.


1

.NET'te farklı koleksiyonlar için karşılaştırma testi oluşturdum: https://github.com/scholtz/TestDotNetCollectionsMemoryAllocation

Sonuçlar, 3 özellik ayrılmış 1.000.000 nesneye sahip .NET Core 2.2 için aşağıdaki gibidir:

Testing with string: 1234567
Hashtable<TestObject>:                                     184 672 704 B
Hashtable<TestObjectRef>:                                  136 668 560 B
Dictionary<int, TestObject>:                               171 448 160 B
Dictionary<int, TestObjectRef>:                            123 445 472 B
ConcurrentDictionary<int, TestObject>:                     200 020 440 B
ConcurrentDictionary<int, TestObjectRef>:                  152 026 208 B
HashSet<TestObject>:                                       149 893 216 B
HashSet<TestObjectRef>:                                    101 894 384 B
ConcurrentBag<TestObject>:                                 112 783 256 B
ConcurrentBag<TestObjectRef>:                               64 777 632 B
Queue<TestObject>:                                         112 777 736 B
Queue<TestObjectRef>:                                       64 780 680 B
ConcurrentQueue<TestObject>:                               112 784 136 B
ConcurrentQueue<TestObjectRef>:                             64 783 536 B
ConcurrentStack<TestObject>:                               128 005 072 B
ConcurrentStack<TestObjectRef>:                             80 004 632 B

Hafıza testi için kullanılacak en iyisini buldum

GC.GetAllocatedBytesForCurrentThread()

1

Yapı / değer dizileri için, şunlarla farklı sonuçlarım var:

first = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0).ToInt64();
second = Marshal.UnsafeAddrOfPinnedArrayElement(array, 1).ToInt64();
arrayElementSize = second - first;

(aşırı basitleştirilmiş örnek)

Yaklaşım ne olursa olsun, .Net'in sonuçları doğru bir şekilde yorumlamak için nasıl çalıştığını gerçekten anlamanız gerekir. Örneğin, döndürülen öğe boyutu, bir miktar dolgu ile "hizalanmış" öğe boyutudur. Ek yük ve dolayısıyla boyut, bir türün kullanımına bağlı olarak farklıdır: GC yığını üzerinde, yığın üzerinde, bir alan olarak, bir dizi öğesi olarak "kutulu".

(Jeneriklerin "isteğe bağlı" argümanlarını taklit etmek için "kukla" boş yapılar (herhangi bir alan olmadan) kullanmanın bellek etkisinin ne olacağını bilmek istedim; boş yapılar içeren farklı düzenlerle testler yaparak, boş bir yapının ( en azından) eleman başına 1 bayt; belirsiz bir şekilde hatırlıyorum çünkü .Net'in her alan için farklı bir adrese ihtiyacı var, bu bir alan gerçekten boş / 0 boyutlu olsaydı işe yaramazdı).


0

En basit yol: int size = *((int*)type.TypeHandle.Value + 1)

Bunun uygulama ayrıntısı olduğunu biliyorum, ancak GC buna dayanıyor ve verimlilik için yöntemlenebilirliğin başlangıcına ve ayrıca GC kod kompleksinin gelecekte kimsenin onu değiştirmeye cesaret edemeyeceğini de hesaba katarak yaklaşması gerekiyor. Aslında, .net çerçevesi + .net çekirdeğinin her küçük / büyük sürümleri için çalışır. (Şu anda 1.0 için test edilemiyor)
Daha güvenilir bir yol istiyorsanız [StructLayout(LayoutKind.Auto)], aynı sırayla aynı alanlara sahip dinamik bir derlemede bir yapı yayınlayın , boyutunu alın Ancak sınıfınız başka bir sınıftan geliyorsa, her boyutu bulmanız gerekir baz sınıfını ayrı ayrı ekleyin ve + 2 * Inptr.Size başlık için tekrar ekleyin. Bunu bayraklı alanlar alarak yapabilirsiniz . sizeof IL komutuyla . Yapı içerisinde basitçe bu değeri döndüren statik bir metot yaymak isteyebilirsiniz. Sonra nesne başlığı için 2 * IntPtr.Size ekleyin. Bu size tam bir değer vermelidir.
BindingFlags.DeclaredOnly
Diziler ve dizeler yalnızca bu boyutu, uzunluğunu * öğe boyutunu ekler. Toplam nesnelerin kümülatif boyutu için, her alanı ziyaret etmeyi ve içeriğini incelemeyi içeren daha karmaşık bir çözüm uygulamanız gerekir.

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.