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

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

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

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

EDIT: Кто-нибудь написал метод расширения для объекта, который мог бы это сделать? Это было бы довольно аккуратно imo.

Ответ 1

Прежде всего, предупреждение: то, что следует, строго в области уродливых, незарегистрированных хаков. Не полагайтесь на эту работу - даже если она работает для вас сейчас, она может перестать работать завтра, с любым незначительным или крупным обновлением .NET.

Вы можете использовать информацию в этой статье, посвященную внутренним компонентам CLR. Выпуск MSDN Magazine, 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-bit. Я не уверен, что размеры полей одинаковы для 64-битных систем - возможно, вам придется настроить типы и/или смещения, если они не совпадают.

Это будет работать для всех "нормальных" типов, для которых все экземпляры имеют одинаковые, четко определенные типы. Те, для которых это не так, наверняка являются массивами и строками, и я верю также StringBuilder. Для них вы должны будете добавить размер всех содержащихся элементов к их базовому размеру.

Ответ 2

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

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

Ответ 3

Для неуправляемых типов aka типов значений, 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) recursivelly по каждому свойству вашего класса, вы получите содержимое в байтах, которое не учитывает вес класса или ссылок, но намного ближе к реальности. Я бы рекомендовал использовать массив байтов для данных и неуправляемый класс прокси для доступа к значениям, используя листинг указателя, если размер имеет значение, обратите внимание, что это будет некорректированная память, поэтому на старых компьютерах будет медленным, но огромные наборы данных в MODERN RAM будут значительно быстрее, так как минимизация размера для чтения из ОЗУ будет иметь больший эффект, чем неувязка.

Ответ 4

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

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

Ответ 5

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

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

EDIT. Вы можете написать вторую программу, которая профилирует первый, используя API профилирования CLR и сообщает с ним через удаленный доступ или что-то в этом роде.

Ответ 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

Ответ 7

AFAIK, вы не можете, без глубокого подсчета размера каждого члена в байтах. Но опять-таки, размер члена (например, элементы внутри коллекции) подсчитывается в сторону размера объекта или указатель на этот член рассчитывается по размеру объекта? Зависит от того, как вы его определяете.

Я столкнулся с этой ситуацией раньше, когда хотел ограничить объекты в моем кеше на основе памяти, которую они потребляли.

Хорошо, если есть какой-то трюк, чтобы сделать это, я был бы рад узнать об этом!

Ответ 8

Для типов значений вы можете использовать Marshal.SizeOf. Конечно, он возвращает количество байтов, необходимых для маршалирования структуры в неуправляемой памяти, что не обязательно используется CLR.

Ответ 9

Используйте Son Of Strike, у которого есть команда ObjSize.

Обратите внимание, что фактическая потребляемая память всегда больше, чем отчеты ObjSize из-за synkblk который находится непосредственно перед данными объекта.

Подробнее об этом читайте здесь MSDN Magazine Issue 2005 Май - подробное описание внутренних компонентов .NET Framework, чтобы увидеть, как CLR создает объекты времени выполнения.

Ответ 10

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

Ответ 11

Для тех, кто ищет решение, которое не требует [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;
    }
}

Нет, это не даст вам точный размер, который будет использоваться в памяти. Как упоминалось ранее, это невозможно. Но это даст вам приблизительную оценку.

Обратите внимание, что это также довольно медленно.

Ответ 12

От Павла и JNM2:

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

Обратите внимание, что он работает только с непрерывными объектами памяти.

Ответ 13

int size = *((int*)type.TypeHandle.Value + 1) способ: int size = *((int*)type.TypeHandle.Value + 1)

Я знаю, что это детали реализации, но GC полагается на это, и это должно быть как можно ближе к началу методической таблицы для эффективности, а также принимая во внимание, как сложный код GC никто не осмелится изменить в будущем. Фактически это работает для каждой младшей/основной версии ядра .net framework+.net. (В настоящее время не может проверить на 1.0)
Если вы хотите более надежный способ, [StructLayout(LayoutKind.Auto)] структуру в динамической сборке с помощью [StructLayout(LayoutKind.Auto)] с точно [StructLayout(LayoutKind.Auto)] же полями в том же порядке, примите его размер с инструкцией sizeof IL. Вы можете использовать статический метод внутри структуры, который просто возвращает это значение. Затем добавьте 2 * IntPtr.Size для заголовка объекта. Это должно дать вам точное значение.
Но если ваш класс является производным от другого класса, вам нужно отдельно найти каждый размер базового класса и снова добавить их + 2 * Inptr.Size для заголовка. Вы можете сделать это, получив поля с флагом BindingFlags.DeclaredOnly.
Массивы и строки просто добавляют этот размер к его длине * к размеру элемента. Для совокупного размера объектов агрегации вам необходимо реализовать более сложное решение, которое включает в себя посещение каждого поля и проверку его содержимого.

Ответ 14

Я создал тест производительности для различных коллекций в .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()

Ответ 15

Мой класс не был помечен как сериализуемый, поэтому некоторые из ответов здесь не работали. Я обошел это так.

var dump =  JsonConvert.SerializeObject(obj);