Знайти розмір екземпляра об'єкта в байтах в c #


113

Для будь-якого довільного екземпляра (колекції різних об'єктів, композицій, окремих предметів тощо)

Як я можу визначити його розмір у байтах?

(В даний час у мене є колекція різних об'єктів, і я намагаюся визначити сукупний розмір)

EDIT: Хтось написав метод розширення для Object, який міг би це зробити? Це було б досить акуратно.



Відповіді:


60

Перш за все, попередження: наступне суворо стоїть у царині некрасивих, недокументованих хакерів. Не покладайтеся на цю роботу - навіть якщо вона працює для вас зараз, вона може припинити роботу завтра, з будь-яким незначним чи значним оновленням .NET.

Ви можете використовувати інформацію в цій статті про внутрішні записи CLR, випуск журналу MSDN 2005 р., Травень - "Внутрішнє середовище .NET Framework", щоб побачити, як CLR створює об'єкти часу виконання - востаннє я перевірив, чи вона все ще застосовувалася. Ось як це робиться (витягує внутрішнє поле "Основний розмір екземпляра" через TypeHandleтип).

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

Це працює на 3.5 SP1 32-розрядному. Я не впевнений, чи розміри полів однакові у 64-розрядних - вам, можливо, доведеться відрегулювати типи та / або зміщення, якщо їх немає.

Це буде працювати для всіх "звичайних" типів, для яких усі екземпляри мають однакові, чітко визначені типи. Ті, для яких це неправда - це масиви та рядки точно, і я теж вірю StringBuilder. Для них вам доведеться додати розмір всіх елементів, що містяться, до їх базового розміру екземпляра.


Ні. "Правильного" способу це зробити не існує, оскільки це не те, що в першу чергу слід турбувати добре застосований .NET-додаток. Вищезазначене безпосередньо взаємодіє із внутрішніми структурами даних певної реалізації CLR (що може легко змінитись, наприклад, у наступній версії .NET).
Павло Мінаєв

3
це повинно працювати в C # або лише керується c ++? поки що в C # це не задоволено, що я спробував це:Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
Маслоу

17
Версія .NET 4 для цього навіть не потребує небезпечного коду: Marshal.ReadInt32(type.TypeHandle.Value, 4)працює для x86 та x64. Я перевіряв лише типи структур та класів. Майте на увазі , що це повертає коробочну розмір для типів значень. @Pavel Можливо, ви могли б оновити свою відповідь.
jnm2

2
@ Sab669 добре, замінити typeз obj.GetType()його приклад. Не має значення, яку рамку ви використовуєте, лише те, що CLR (v2 або v4 або CoreCLR). Я не пробував цього на CoreCLR.
jnm2

2
@SamGoldberg Розрахунок цього вручну - це велика робота з мільйоном крайових справ. Sizeof повідомляє вам про статичний розмір об'єкта, а не про витрату пам'яті на графіку виконання об'єктів. Пам'ять VS2017 та профілювання процесора дуже хороші, як і ReSharper та інші інструменти, і це те, що я використовував би для вимірювання.
jnm2

20

Ви можете бути в змозі наблизити розмір, роблячи вигляд, що серіалізує його за допомогою двійкового серіалізатора (але направляючи вихід у забуття), якщо ви працюєте з об'єктами, що серіалізуються.

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
Звичайно, це може отримати мінімальний розмір, але нічого не говорить про розмір у пам'яті.
Джон Сондерс

Лол, наступна лампочка, яку я мав перед поверненням, щоб перевірити відповіді, використовувала двійковий серіалізатор. Джон, як би це не дало тобі фактичного розміру в пам'яті?
Janie

2
Це дасть вам серіалізований розмір, який буде таким розміром, який він хотів, щоб серіалізатор хотів. Вони, ймовірно, відрізняються від цілей "сидіння в пам'яті". Можливо, серіалізатор зберігає менші цілі числа, наприклад, у три байти.
Джон Сондерс

4
Як я вже сказав, це лише наближення. Це не ідеально, але я б не погоджувався, що він говорить вам "нічого" про розмір в пам'яті. Я б сказав, що це дало вам деяку ідею - більша серіалізація, як правило, співвідноситься з більшими розмірами в пам'яті. Є певні стосунки.
BlueMonkMN

Я згоден - корисно отримати бальну оцінку розміру графіка об’єкта .NET.
Крейг Ширер

8

Для некерованих типів, відомих як типи значень, structs:

        Marshal.SizeOf(object);

Для керованих об'єктів ближче, що я отримав - це наближення.

        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;

Не використовуйте серіалізацію. Бінарний форматер додає заголовки, тому ви можете змінити свій клас та завантажити старий серіалізований файл у модифікований клас.

Крім того, він не скаже вам реальний розмір пам'яті, а також не врахує вирівнювання пам'яті.

[Редагувати] Використовуючи BiteConverter.GetBytes (prop-value) рекурсивно на кожному властивості вашого класу, ви отримаєте вміст у байтах, який не враховує вагу класу чи посилань, але набагато ближче до реальності. Я рекомендую використовувати байтовий масив для даних та некерований проксі-клас для доступу до значень за допомогою кастингу покажчиків, якщо розмір має значення, зауважте, що ця пам'ять буде вирівняною, тому на старих комп'ютерах буде повільно, але ВЕЛИЧНІ набори даних у СУЧАСНІЙ ОЗУ будуть значно швидше, оскільки мінімізація розміру для читання з оперативної пам’яті матиме більший вплив, ніж нестандартне.


5

Це не стосується поточної реалізації .NET, але слід пам’ятати про зібраний / керований сміттям час виконання - розмір об'єкта може змінюватися протягом усього життя програми. Наприклад, деяким поколінням сміттєзбірників (наприклад, гібридний збірник генераційного / останнього підрахунку ) потрібно зберігати певну інформацію лише після переміщення об'єкта з розплідника на зрілий простір.

Це робить неможливим створення надійного, загального API для викриття розміру об'єкта.


Цікаво. Отже, що люди роблять, щоб динамічно визначати розмір своїх об'єктів / колекцій предметів?
Janie

2
Це залежить від того, для чого вони потрібні. Якщо для P / Invoke (власний код interop), вони використовують Marshal.SizeOf (typeof (T)). Якщо для профілювання пам'яті, вони використовують окремий профілер, який співпрацює із середовищем виконання для надання інформації. Якщо вас цікавить вирівнювання елементів у масиві, ви можете використовувати кодекс SizeOf IL в DynamicMethod (я не думаю, що в .NET-рамках для цього є простіший спосіб).
Сем Харвелл

5

безпечне рішення з деякими оптимізаціями коду CyberSaving / MemoryUsage . певний випадок:

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

Він також працює з класами:

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

Такий підхід я б і застосував. Ви можете додати набір раніше зустрічаються об’єктів у графіку, щоб уникнути а) нескінченної рекурсії та б) уникати додавання однієї і тієї ж пам’яті двічі.
мафу

4

Це неможливо зробити під час виконання.

Однак існують різні профілі пам'яті, які відображають розмір об'єкта.

EDIT : Ви можете написати другу програму, яка профілює першу за допомогою API CLR Profiling і спілкується з нею через видалення або щось подібне.


17
Якщо це неможливо зробити під час виконання, як профілі пам'яті надають інформацію?
Janie

2
За допомогою API профілювання. Однак програма не може профілювати себе
SLaks

Цікаво. Що робити, якщо я хотів би мати справу з кодом у випадках, коли об’єкти споживають занадто багато пам'яті?
Janie

4
Тоді ви матимете справу із самосвідомим програмним забезпеченням, і я дуже боюся. :-) Серйозно, "головна відповідальність за одну відповідальність" - нехай програма буде програмою, нехай якийсь інший фрагмент коду слідкує за об'єктами, що займають занадто багато пам'яті.
Джон Сондерс

2
@Janie: ви також будете робити припущення щодо значущості розміру та того, як це стосується продуктивності. Я думаю, що ви хочете бути справжнім експертом з низьких рівнів CLR (тим, хто вже знає про API профілю), перш ніж це зробити. В іншому випадку ви можете застосувати свій попередній досвід до ситуації, в якій вони не застосовуються.
Джон Сондерс

3

Використовуйте Son Of Strike, який має команду ObjSize.

Зауважте, що фактично споживана пам'ять завжди більша, ніж ObjSizeзвіти, завдяки synkblkякій знаходиться безпосередньо перед об'єктними даними.

Прочитайте більше про обидва тут випуску журналу MSDN 2005 року, травень - проаналізуйте внутрішню структуру .NET Framework, щоб побачити, як CLR створює об'єкти виконання .


2

AFAIK, ви не можете, не фактично рахуючи розмір кожного члена в байтах. Але знову ж, чи враховується розмір члена (як елементи всередині колекції) до розміру об'єкта, або покажчик на цей член зараховується до розміру об'єкта? Залежить від того, як ви це визначите.

Я раніше стикався з цією ситуацією, коли хотів обмежити об'єкти в своєму кеші, виходячи з споживаної ними пам'яті.

Ну, якщо є якийсь трюк для цього, я б із задоволенням дізнався про це!


2

Для типів значень можна використовувати Marshal.SizeOf. Звичайно, він повертає кількість байтів, необхідних для маршалювання структури в некерованій пам'яті, що не обов’язково використовується CLR.


SizeOf (Object) може бути недоступним у майбутніх випусках. Замість цього використовуйте SizeOf <T> (). Для отримання додаткової інформації перейдіть на go.microsoft.com/fwlink/?LinkID=296514
Vinigas

1

Ви можете використовувати відображення для збору всієї інформації про публічного члена або про властивості (з урахуванням типу об'єкта). Неможливо визначити розмір, не проходячи через кожен окремий фрагмент даних про об'єкт.


1

Для тих, хто шукає рішення, яке не потребує [Serializable]класів, і результат якого є наближенням замість точної науки. Найкращий метод, який я міг знайти, - це серіалізація json в потік пам'яті за допомогою кодування 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;
    }
}

Ні, це не дасть точного розміру, який би використовувався в пам'яті. Як було сказано раніше, це неможливо. Але це дасть вам приблизну оцінку.

Зауважте, що це також досить повільно.


1

Від Павла та jnm2:

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

З боку слід бути обережним, оскільки він працює лише з суміжними об'єктами пам'яті


1

Я створив контрольний тест для різних колекцій у .NET: https://github.com/scholtz/TestDotNetCollectionsMemoryAllocation

Результати наступні для .NET Core 2.2 з виділеними 1 000 000 об'єктів з 3 властивостями:

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

Для тесту пам'яті я знайшов найкраще використовувати

GC.GetAllocatedBytesForCurrentThread()

1

Для масивів структур / значень я маю різні результати з:

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

(спрощений приклад)

Який би не був підхід, вам дійсно потрібно зрозуміти, як .Net працює, щоб правильно інтерпретувати результати. Наприклад, розмір повернутого елемента - це "вирівняний" розмір елемента з деякими накладками. Розмір накладних витрат і, отже, розмір відрізняється залежно від використання типу: "boxed" на купі GC, на стеці, як поле, як елемент масиву.

(Мені хотілося знати, яким буде вплив на пам'ять використання "пустуючих" порожніх структур (без будь-якого поля) для імітації "необов'язкових" аргументів дженериків; роблячи тести з різними макетами, що включають порожні структури, я можу побачити, що порожня структура використовує ( принаймні) 1 байт на елемент; я неясно пам'ятаю, що це тому, що .Net потрібна інша адреса для кожного поля, яка не працюватиме, якби поле справді було порожнім / розміром 0).


0

Найпростіший спосіб: int size = *((int*)type.TypeHandle.Value + 1)

Я знаю, що це деталізація щодо впровадження, але GC покладається на неї, і вона повинна бути максимально близькою до початку методології для ефективності, а також враховувати, як комплекс GC-коду ніхто не наважиться змінити це в майбутньому. Насправді він працює для кожної другорядної / основної версії ядра .net Framework + .net core. (Наразі не вдається перевірити 1,0)
Якщо ви хочете більш надійного способу, випромініть структуру в динамічній збірці з [StructLayout(LayoutKind.Auto)]точно такими ж полями в одному порядку, прийміть її розмір з sizeof інструкції IL. Ви можете випромінити статичний метод у структурі, який просто повертає це значення. Потім додайте 2 * IntPtr.Size для заголовка об'єкта. Це має дати точне значення.
Але якщо ваш клас походить з іншого класу, вам потрібно знайти кожен розмір базового класу окремо і додайте їх + 2 * Inptr.Займіть ще раз для заголовка. Це можна зробити, отримавши поля з BindingFlags.DeclaredOnlyпрапором.
Масиви та рядки просто додають, що розмір його довжина * розмір елемента. Для накопичувального розміру об'єднаних об'єктів потрібно реалізувати більш складне рішення, яке передбачає відвідування кожного поля та огляд його вмісту.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.