Temukan ukuran instance objek dalam byte di c #

114

Untuk sembarang contoh (kumpulan objek, komposisi, objek tunggal, dll yang berbeda)

Bagaimana cara menentukan ukurannya dalam byte?

(Saat ini saya memiliki koleksi berbagai objek dan saya mencoba menentukan ukuran agregatnya)

EDIT: Apakah seseorang telah menulis metode ekstensi untuk Object yang dapat melakukan ini? Itu akan sangat bagus.

Janie
sumber

Jawaban:

60

Pertama-tama, peringatan: yang berikut ini benar-benar berada di ranah peretasan yang buruk dan tidak berdokumen. Jangan mengandalkan cara ini berfungsi - bahkan jika berfungsi untuk Anda sekarang, ini mungkin berhenti berfungsi besok, dengan pembaruan .NET kecil atau besar.

Anda dapat menggunakan informasi dalam artikel ini tentang internal CLR Majalah MSDN Edisi 2005 Mei - Telusuri Internal .NET Framework untuk Melihat Bagaimana CLR Membuat Objek Waktu Proses - terakhir saya periksa, itu masih berlaku. Berikut adalah cara melakukannya (mengambil bidang internal "Ukuran Instans Dasar" melalui TypeHandletipe).

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

Ini bekerja pada 3.5 SP1 32-bit. Saya tidak yakin apakah ukuran bidang sama pada 64-bit - Anda mungkin harus menyesuaikan jenis dan / atau offset jika tidak.

Ini akan bekerja untuk semua jenis "normal", yang semua instansinya memiliki jenis yang sama dan terdefinisi dengan baik. Yang tidak benar ini adalah array dan string, dan saya percaya juga StringBuilder. Untuk mereka, Anda harus menambahkan ukuran semua elemen yang dimuat ke ukuran instance dasarnya.

Pavel Minaev
sumber
Tidak. Tidak ada cara yang "tepat" untuk melakukan ini, karena ini bukanlah sesuatu yang harus diperhatikan oleh aplikasi .NET yang berperilaku baik. Di atas mucks secara langsung dengan struktur data internal dari implementasi CLR tertentu (yang dapat dengan mudah berubah di versi .NET berikutnya, misalnya).
Pavel Minaev
3
apakah ini seharusnya bekerja di C # atau hanya dikelola c ++? tidak senang di C # sejauh ini yang saya coba:Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
Maslow
17
Versi .NET 4 ini bahkan tidak memerlukan kode yang tidak aman: Marshal.ReadInt32(type.TypeHandle.Value, 4)berfungsi untuk x86 dan x64. Saya hanya menguji tipe struct dan kelas. Ingatlah bahwa ini mengembalikan ukuran kotak untuk tipe nilai. @Pavel Mungkin Anda bisa memperbarui jawaban Anda.
jnm2
2
@ sab669 yah, ganti typedengan obj.GetType()contoh nya. Tidak masalah kerangka kerja mana yang Anda gunakan, hanya CLR (v2 atau v4 atau CoreCLR) apa. Saya belum mencoba ini di CoreCLR.
jnm2
2
@SamGoldberg Menghitung ini secara manual membutuhkan banyak pekerjaan dengan jutaan kasus tepi. Sizeof memberi tahu Anda ukuran statis suatu objek, bukan konsumsi memori dari grafik runtime objek. Memori VS2017 dan profil CPU sangat bagus, seperti ReSharper dan alat lainnya, dan itulah yang akan saya gunakan untuk mengukur.
jnm2
20

Anda mungkin dapat memperkirakan ukuran dengan berpura-pura menserialisasinya dengan serializer biner (tetapi merutekan keluaran ke terlupakan) jika Anda bekerja dengan objek yang dapat diserialkan.

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();
    }
}
BlueMonkMN
sumber
6
Tentu saja, ini dapat memberi Anda ukuran minimum, tetapi tidak memberi tahu Anda apa pun tentang ukuran dalam memori.
John Saunders
Lol, bola lampu berikutnya yang saya miliki sebelum kembali untuk memeriksa balasannya menggunakan binary serializer. John, bagaimana ini tidak memberi Anda ukuran sebenarnya dalam memori?
Janie
2
Ini akan memberi Anda ukuran serial, yang akan menjadi ukuran yang diinginkan pembuat serial itu, untuk tujuan "serializer". Itu mungkin berbeda dari tujuan "sit-in-memory". Mungkin serializer menyimpan bilangan bulat yang lebih kecil dalam tiga byte, misalnya.
John Saunders
4
Seperti saya katakan, itu hanya perkiraan. Ini tidak sempurna, tetapi saya tidak setuju bahwa ini memberi tahu Anda "tidak ada" tentang ukuran dalam memori. Saya akan mengatakan bahwa itu memberi Anda beberapa ide - serialisasi yang lebih besar umumnya akan berkorelasi dengan ukuran dalam memori yang lebih besar. Ada beberapa hubungan.
BlueMonkMN
Saya setuju - ini berguna untuk mendapatkan perkiraan kasar dari ukuran grafik objek .NET.
Craig Shearer
8

Untuk tipe tidak terkelola alias tipe nilai, struct:

        Marshal.SizeOf(object);

Untuk objek yang dikelola, semakin dekat saya adalah sebuah pendekatan.

        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;

Jangan gunakan serialisasi. Pemformat biner menambahkan header, sehingga Anda dapat mengubah kelas dan memuat file berseri lama ke dalam kelas yang dimodifikasi.

Juga tidak akan memberi tahu Anda ukuran sebenarnya dalam memori juga tidak akan memperhitungkan keselarasan memori akun.

[Sunting] Dengan menggunakan BiteConverter.GetBytes (prop-value) secara rekursif pada setiap properti kelas Anda, Anda akan mendapatkan konten dalam byte, yang tidak menghitung bobot kelas atau referensi tetapi lebih mendekati kenyataan. Saya akan merekomendasikan untuk menggunakan array byte untuk data dan kelas proxy yang tidak terkelola untuk mengakses nilai menggunakan pengecoran penunjuk jika ukuran penting, perhatikan bahwa itu akan menjadi memori non-aligned sehingga pada komputer lama akan lambat tetapi dataset BESAR pada RAM MODERN akan menjadi jauh lebih cepat, karena meminimalkan ukuran untuk membaca dari RAM akan berdampak lebih besar daripada tidak selaras.

Aridane Álamo
sumber
5

Ini tidak berlaku untuk implementasi .NET saat ini, tetapi satu hal yang perlu diingat dengan runtime yang dikumpulkan / dikelola sampah adalah ukuran yang dialokasikan dari suatu objek dapat berubah sepanjang masa program. Misalnya, beberapa pengumpul sampah generasi (seperti pengumpul Hibrid Penghitungan Referensi Generasi / Tersembunyi ) hanya perlu menyimpan informasi tertentu setelah suatu objek dipindahkan dari pembibitan ke ruang dewasa.

Ini membuatnya tidak mungkin untuk membuat API generik yang andal untuk mengekspos ukuran objek.

Sam Harwell
sumber
Menarik. Jadi apa yang dilakukan orang-orang untuk secara dinamis menentukan ukuran objek / koleksi objek mereka?
Janie
2
Itu tergantung untuk apa mereka membutuhkannya. Jika untuk P / Invoke (native code interop), mereka menggunakan Marshal.SizeOf (typeof (T)). Jika untuk profil memori, mereka menggunakan profiler terpisah yang bekerja sama dengan lingkungan eksekusi untuk memberikan informasi. Jika Anda tertarik dengan penyelarasan elemen dalam sebuah array, Anda dapat menggunakan opcode SizeOf IL dalam DynamicMethod (Saya rasa tidak ada cara yang lebih mudah dalam kerangka .NET untuk ini).
Sam Harwell
5

solusi aman dengan beberapa pengoptimalan kode CyberSaving / MemoryUsage . beberapa kasus:

/* 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

Ia bekerja juga dengan kelas:

class twostring
{
    public string a { get; set; }
    public string b { get; set; }
}
TestSize<twostring>.SizeOf(new twostring() { a="0123456789", b="0123456789" } //-> 28 B
AlexPalla
sumber
Ini adalah pendekatan yang akan saya ambil juga. Anda dapat menambahkan sekumpulan objek yang sebelumnya ditemui dalam grafik untuk menghindari a) rekursi tak terbatas dan b) menghindari penambahan memori yang sama dua kali.
mafu
4

Ini tidak mungkin dilakukan saat runtime.

Ada berbagai profiler memori yang menampilkan ukuran objek.

EDIT : Anda dapat menulis program kedua yang membuat profil yang pertama menggunakan API Profil CLR dan berkomunikasi dengannya melalui remote atau sesuatu.

SLaks
sumber
17
Jika tidak mungkin dilakukan pada waktu proses, bagaimana profiler memori memberikan informasi?
Janie
2
Dengan menggunakan API Profil. Namun, sebuah program tidak dapat membuat profil itu sendiri
SLaks
Menarik. Bagaimana jika saya ingin kode menangani kasus-kasus ketika objek memakan terlalu banyak memori?
Janie
4
Kemudian Anda akan berurusan dengan perangkat lunak yang sadar diri, dan saya akan sangat takut. :-) Serius, "prinsip tanggung jawab tunggal" - biarkan program menjadi programnya, biarkan beberapa bagian kode lainnya mengawasi objek yang menggunakan terlalu banyak memori.
John Saunders
2
@ Janie: Anda juga akan membuat asumsi tentang pentingnya ukuran dan kaitannya dengan kinerja. Saya pikir Anda ingin menjadi ahli kinerja CLR tingkat rendah yang sebenarnya (jenis yang sudah tahu tentang API Profil) sebelum Anda melakukannya. Jika tidak, Anda mungkin menerapkan pengalaman Anda sebelumnya pada situasi di mana pengalaman itu tidak berlaku.
John Saunders
2

AFAIK, Anda tidak bisa, tanpa benar-benar menghitung secara mendalam ukuran setiap anggota dalam byte. Tetapi sekali lagi, apakah ukuran anggota (seperti elemen di dalam koleksi) diperhitungkan terhadap ukuran objek, atau penunjuk ke anggota tersebut diperhitungkan terhadap ukuran objek? Tergantung bagaimana Anda mendefinisikannya.

Saya telah mengalami situasi ini sebelumnya di mana saya ingin membatasi objek di cache saya berdasarkan memori yang mereka konsumsi.

Nah, jika ada trik untuk melakukan itu, saya akan senang mengetahuinya!

Charles Prakash Dasari
sumber
2

Untuk tipe nilai, Anda bisa menggunakan Marshal.SizeOf. Tentu saja, ia mengembalikan jumlah byte yang diperlukan untuk menyusun struktur dalam memori yang tidak dikelola, yang belum tentu digunakan oleh CLR.

Mehrdad Afshari
sumber
SizeOf (Object) mungkin tidak tersedia di rilis mendatang. Sebagai gantinya, gunakan SizeOf <T> (). Untuk info lebih lanjut, kunjungi go.microsoft.com/fwlink/?LinkID=296514
Vinigas
1

Anda dapat menggunakan refleksi untuk mengumpulkan semua anggota publik atau informasi properti (berdasarkan tipe objek). Tidak ada cara untuk menentukan ukuran tanpa menelusuri setiap bagian data pada objek.

Charlie
sumber
1

Untuk siapa pun yang mencari solusi yang tidak memerlukan [Serializable]kelas dan hasilnya adalah perkiraan, bukan ilmu pasti. Metode terbaik yang bisa saya temukan adalah serialisasi json ke dalam aliran memori menggunakan pengkodean UTF32.

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

Tidak, ini tidak akan memberi Anda ukuran pasti yang akan digunakan dalam memori. Seperti yang disebutkan sebelumnya, itu tidak mungkin. Tapi itu akan memberi Anda perkiraan kasar.

Perhatikan bahwa ini juga sangat lambat.

Peter
sumber
1

Dari Pavel dan jnm2:

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

Di samping catatan hati-hati karena hanya bekerja dengan objek memori yang berdekatan

Antonin GAVREL
sumber
1

Saya telah membuat tes benchmark untuk berbagai koleksi di .NET: https://github.com/scholtz/TestDotNetCollectionsMemoryAllocation

Hasilnya adalah sebagai berikut untuk .NET Core 2.2 dengan 1.000.000 objek dengan 3 properti dialokasikan:

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

Untuk tes memori saya menemukan yang terbaik untuk digunakan

GC.GetAllocatedBytesForCurrentThread()
Scholtz
sumber
1

Untuk array struct / nilai, saya memiliki hasil yang berbeda dengan:

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

(contoh yang terlalu disederhanakan)

Apa pun pendekatannya, Anda benar-benar perlu memahami cara kerja .Net untuk menafsirkan hasil dengan benar. Misalnya, ukuran elemen yang dikembalikan adalah ukuran elemen "selaras", dengan beberapa padding. Overhead dan ukurannya berbeda-beda bergantung pada penggunaan tipe: "boxed" pada GC heap, di stack, sebagai field, sebagai elemen array.

(Saya ingin tahu apa yang akan menjadi dampak memori menggunakan struct kosong "dummy" (tanpa bidang apa pun) untuk meniru argumen "opsional" generik; membuat tes dengan tata letak berbeda yang melibatkan struct kosong, saya dapat melihat bahwa struct kosong menggunakan ( setidaknya) 1 byte per elemen; saya samar-samar ingat itu karena .Net membutuhkan alamat yang berbeda untuk setiap bidang, yang tidak akan berfungsi jika bidang benar-benar kosong / berukuran 0).

Luc Rogge
sumber
0

Cara paling sederhana adalah: int size = *((int*)type.TypeHandle.Value + 1)

Saya tahu ini adalah detail implementasi tetapi GC mengandalkannya dan itu harus sedekat mungkin dengan tabel metodologi untuk efisiensi plus mempertimbangkan bagaimana kompleks kode GC tidak ada yang berani mengubahnya di masa mendatang. Sebenarnya ini berfungsi untuk setiap versi minor / mayor dari .net framework + .net core. (Saat ini tidak dapat menguji 1.0)
Jika Anda ingin cara yang lebih andal, keluarkan struct dalam rakitan dinamis dengan [StructLayout(LayoutKind.Auto)]bidang yang sama persis dalam urutan yang sama, ambil ukurannya dengan Tetapi jika kelas Anda berasal dari kelas lain, Anda perlu menemukan setiap ukuran dari kelas dasar secara terpisah dan tambahkan + 2 * Inptr.Size lagi untuk header. Anda dapat melakukan ini dengan mendapatkan bidang dengan bendera. sizeof instruksi IL. Anda mungkin ingin mengeluarkan metode statis dalam struct yang hanya mengembalikan nilai ini. Kemudian tambahkan 2 * IntPtr.Size untuk header objek. Ini akan memberi Anda nilai yang tepat.
BindingFlags.DeclaredOnly
Array dan string hanya menambahkan ukuran itu panjang * ukuran elemennya. Untuk ukuran kumulatif objek aggreagate Anda perlu menerapkan solusi yang lebih canggih yang melibatkan mengunjungi setiap bidang dan memeriksa isinya.

TakeMeAsAGuest
sumber