Найти размер экземпляра объекта в байтах в с #


115

Для любого произвольного экземпляра (коллекции разных объектов, композиций, отдельных объектов и т. Д.)

Как я могу определить его размер в байтах?

(В настоящее время у меня есть коллекция различных объектов, и я пытаюсь определить ее совокупный размер)

РЕДАКТИРОВАТЬ: Кто-нибудь написал метод расширения для объекта, который мог бы это сделать? Это было бы неплохо, имо.



Ответы:


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

Это работает на 32-разрядной версии 3.5 SP1. Я не уверен, одинаковы ли размеры полей в 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')
— Maslow

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

2
@ sab669 ну замените typeна obj.GetType()в его примере. Не имеет значения, какой фреймворк вы используете, только то, что CLR (v2 или v4 или CoreCLR). Я не пробовал это на CoreCLR.
— jnm2 01

2
@SamGoldberg Вычислить это вручную - это большая работа с миллионом крайних случаев. Sizeof сообщает вам статический размер объекта, а не потребление памяти графом объектов во время выполнения. Профилирование памяти и процессора VS2017 очень хорошее, как и ReSharper и другие инструменты, и это то, что я бы использовал для измерения.
— jnm2 09

21

Если вы работаете с сериализуемыми объектами, вы можете приблизительно оценить размер, притворившись сериализовать его с помощью двоичного сериализатора (но направив вывод в небытие).

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
Конечно, это может дать вам минимальный размер, но ничего не говорит о размере в памяти.
— Джон Сондерс,

Лол, следующая лампочка, которая у меня была перед тем, как вернуться, чтобы проверить ответы, была с использованием двоичного сериализатора. Джон, как это не дать вам реальный размер памяти?
— Джени

2
Это даст вам сериализованный размер, который будет размером, который хотел сериализатор, для целей "сериализатора". Они, вероятно, отличаются от целей «сидячей памяти». Может быть, сериализатор хранит, например, целые числа меньшего размера в трех байтах.
— Джон Сондерс,

5
Как я уже сказал, это всего лишь приближение. Это не идеально, но я бы не согласился, что он «ничего не говорит» о размере памяти. Я бы сказал, что это дало вам некоторую идею - большие сериализации обычно коррелируют с большими размерами в памяти. Есть какие-то отношения.
— BlueMonkMN

Я согласен - полезно получить приблизительную оценку размера графа объекта .NET.
— Craig Shearer

8

Для неуправляемых типов, также называемых типами значений, структуры:

        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) для каждого свойства вашего класса, вы получите содержимое в байтах, которое не учитывает вес класса или ссылок, но намного ближе к реальности. Я бы рекомендовал использовать байтовый массив для данных и неуправляемый прокси-класс для доступа к значениям с использованием литья указателя, если размер имеет значение, обратите внимание, что это будет невыровненная память, поэтому на старых компьютерах будет медленно, но ОГРОМНЫЕ наборы данных в СОВРЕМЕННОЙ ОЗУ будут значительно быстрее, поскольку минимизация размера для чтения из ОЗУ окажет большее влияние, чем невыровненный.


6

безопасное решение с некоторыми оптимизациями кода 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

Я бы тоже применил этот подход. Вы можете добавить набор ранее встреченных объектов в граф, чтобы избежать а) бесконечной рекурсии и б) избежать добавления одной и той же памяти дважды.
— mafu

5

Это не относится к текущей реализации .NET, но при работе со сборщиком мусора / управляемой средой выполнения следует помнить о том, что выделенный размер объекта может изменяться на протяжении всего времени существования программы. Например, некоторым сборщикам мусора поколений (например, сборщику гибридного подсчета ссылок поколений ) необходимо сохранять определенную информацию только после того, как объект перемещен из питомника в зрелое пространство.

Это делает невозможным создание надежного универсального API для отображения размера объекта.


Интересный. Итак, что делают люди, чтобы динамически определять размер своих объектов / коллекций объектов?
— Janie

2
Смотря для чего им это нужно. Если для P / Invoke (взаимодействие с собственным кодом), они используют Marshal.SizeOf (typeof (T)). Если для профилирования памяти, они используют отдельный профилировщик, который взаимодействует со средой выполнения для предоставления информации. Если вас интересует выравнивание элементов в массиве, вы можете использовать код операции SizeOf IL в DynamicMethod (я не думаю, что в .NET framework для этого есть более простой способ).
— Сэм Харвелл,

4

Это невозможно сделать во время выполнения.

Однако существуют различные профилировщики памяти, отображающие размер объекта.

РЕДАКТИРОВАТЬ : вы можете написать вторую программу, которая профилирует первую с помощью CLR Profiling API и взаимодействует с ним через удаленное взаимодействие или что-то в этом роде.


17
Если это невозможно сделать во время выполнения, как профилировщики памяти предоставляют информацию?
— Janie

2
Используя API профилирования. Однако программа не может профилировать себя
— SLaks

Интересный. Что, если бы я хотел, чтобы код обрабатывал случаи, когда объекты потребляли слишком много памяти?
— Janie

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

2
@Janie: вы также будете делать предположения о значении размера и о том, как он связан с производительностью. Я думаю, вы захотите стать настоящим низкоуровневым экспертом по производительности CLR (из тех, кто уже знает о Profiling 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
— Винигас,

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, чтобы правильно интерпретировать результаты. Например, возвращаемый размер элемента - это размер «выровненного» элемента с некоторым отступом. Накладные расходы и, следовательно, размер различаются в зависимости от использования типа: «в коробке» в куче сборщика мусора, в стеке, как поле, как элемент массива.

(Я хотел знать, какое влияние на память окажет использование «фиктивных» пустых структур (без какого-либо поля) для имитации «необязательных» аргументов универсальных шаблонов; выполняя тесты с различными макетами, включающими пустые структуры, я вижу, что пустая структура использует ( по крайней мере) 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.Size для заголовка. Вы можете сделать это, получив поля с BindingFlags.DeclaredOnlyфлагом.
Массивы и строки просто добавляют этот размер к размеру элемента length *. Для совокупного размера агрегированных объектов вам необходимо реализовать более сложное решение, которое включает посещение каждого поля и проверку его содержимого.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.