Encuentre el tamaño de la instancia del objeto en bytes en c #


114

Para cualquier instancia arbitraria (colecciones de diferentes objetos, composiciones, objetos individuales, etc.)

¿Cómo puedo determinar su tamaño en bytes?

(Actualmente tengo una colección de varios objetos y estoy tratando de determinar el tamaño agregado)

EDITAR: ¿Alguien ha escrito un método de extensión para Object que podría hacer esto? Eso sería bastante bueno en mi opinión.



Respuestas:


60

En primer lugar, una advertencia: lo que sigue está estrictamente en el ámbito de los hacks feos e indocumentados. No confíe en que esto funcione, incluso si funciona para usted ahora, puede dejar de funcionar mañana, con cualquier actualización menor o importante de .NET.

Puede utilizar la información de este artículo sobre aspectos internos de CLR MSDN Magazine Edición 2005 Mayo: profundice en los elementos internos de .NET Framework para ver cómo CLR crea objetos en tiempo de ejecución ; la última vez que lo comprobé, aún era aplicable. Así es como se hace esto (recupera el campo interno "Tamaño de instancia básico" a través TypeHandledel tipo).

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

Esto funciona en 3.5 SP1 de 32 bits. No estoy seguro de si los tamaños de campo son los mismos en 64 bits; es posible que deba ajustar los tipos y / o compensaciones si no lo son.

Esto funcionará para todos los tipos "normales", para los cuales todas las instancias tienen los mismos tipos bien definidos. Aquellos para los que esto no es cierto son matrices y cadenas de seguro, y creo que también StringBuilder. Para ellos, tendrá que agregar el tamaño de todos los elementos contenidos a su tamaño de instancia base.


No. No hay una forma "adecuada" de hacer esto, porque no es algo de lo que una aplicación .NET de buen comportamiento deba preocuparse en primer lugar. Lo anterior se confunde directamente con las estructuras de datos internas de una implementación particular de CLR (que puede cambiar fácilmente en la próxima versión de .NET, por ejemplo).
Pavel Minaev

3
¿Se supone que esto funciona en C # o solo administrado en C ++? no está contento en C # hasta ahora que lo he probado:Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
Maslow

17
La versión .NET 4 de esto ni siquiera necesita un código inseguro: Marshal.ReadInt32(type.TypeHandle.Value, 4)funciona para x86 y x64. Solo probé tipos de estructura y clase. Tenga en cuenta que esto devuelve el tamaño en caja para los tipos de valor. @Pavel Tal vez puedas actualizar tu respuesta.
jnm2

2
@ sab669 bueno, reemplácelo typecon obj.GetType()en su ejemplo. No importa qué marco esté usando, solo qué CLR (v2 o v4 o CoreCLR). No he probado esto en CoreCLR.
jnm2

2
@SamGoldberg Calcular esto manualmente es mucho trabajo con un millón de casos extremos. Sizeof le dice el tamaño estático de un objeto, no el consumo de memoria de un gráfico de tiempo de ejecución de objetos. La memoria y el perfil de CPU de VS2017 son muy buenos, al igual que ReSharper y otras herramientas, y eso es lo que usaría para medir.
jnm2

20

Es posible que pueda aproximar el tamaño pretendiendo serializarlo con un serializador binario (pero enrutando la salida al olvido) si está trabajando con objetos serializables.

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
Por supuesto, esto puede darle un tamaño mínimo, pero no le dice nada sobre el tamaño en la memoria.
John Saunders

Lol, la siguiente bombilla que tenía antes de volver para comprobar las respuestas estaba usando el serializador binario. John, ¿cómo podría esto no darte el tamaño real en la memoria?
Janie

2
Le daría el tamaño serializado, que será el tamaño que el serializador quería, para propósitos de "serializador". Es probable que sean diferentes de los propósitos de "sentarse en la memoria". Quizás el serializador almacena enteros más pequeños en tres bytes, por ejemplo.
John Saunders

4
Como dije, es solo una aproximación. No es perfecto, pero no estaría de acuerdo en que no le diga "nada" sobre el tamaño en la memoria. Diría que le dio una idea: las serializaciones más grandes generalmente se correlacionarían con tamaños en memoria más grandes. Hay alguna relación.
BlueMonkMN

Estoy de acuerdo, es útil obtener una estimación aproximada del tamaño de un gráfico de objeto .NET.
Craig Shearer

8

Para tipos no administrados, también conocidos como tipos de valor, estructuras:

        Marshal.SizeOf(object);

Para los objetos administrados, cuanto más me acerco es una aproximación.

        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;

No use serialización. Un formateador binario agrega encabezados, por lo que puede cambiar su clase y cargar un archivo serializado antiguo en la clase modificada.

Además, no le dirá el tamaño real en la memoria ni tendrá en cuenta la alineación de la memoria.

[Editar] Al usar BiteConverter.GetBytes (prop-value) de forma recursiva en cada propiedad de su clase, obtendría el contenido en bytes, que no cuenta el peso de la clase o referencias, pero está mucho más cerca de la realidad. Recomendaría usar una matriz de bytes para los datos y una clase de proxy no administrada para acceder a los valores usando la conversión de puntero si el tamaño importa, tenga en cuenta que sería memoria no alineada, por lo que en las computadoras antiguas será lenta, pero los conjuntos de datos ENORMES en la RAM MODERNA lo serán considerablemente más rápido, ya que minimizar el tamaño para leer desde la RAM tendrá un impacto mayor que no alineado.


5

Esto no se aplica a la implementación actual de .NET, pero una cosa a tener en cuenta con los tiempos de ejecución recolectados / administrados de basura es que el tamaño asignado de un objeto puede cambiar durante la vida útil del programa. Por ejemplo, algunos recolectores de basura generacionales (como el recolector híbrido de recuento de referencias generacionales / ulteriores ) solo necesitan almacenar cierta información después de que un objeto se mueve del vivero al espacio maduro.

Esto hace que sea imposible crear una API genérica confiable para exponer el tamaño del objeto.


Interesante. Entonces, ¿qué hacen las personas para determinar dinámicamente el tamaño de sus objetos / colecciones de objetos?
Janie

2
Depende de para qué lo necesiten. Si para P / Invoke (interoperabilidad de código nativo), usan Marshal.SizeOf (typeof (T)). Si se trata de crear perfiles de memoria, utilizan un generador de perfiles independiente que coopera con el entorno de ejecución para proporcionar la información. Si está interesado en la alineación de elementos en una matriz, puede usar el código de operación SizeOf IL en un método dinámico (no creo que haya una forma más fácil en el marco .NET para esto).
Sam Harwell

5

solución segura con algunas optimizaciones del código CyberSaving / MemoryUsage . algún caso:

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

Funciona también con clases:

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

Este es el enfoque que yo también tomaría. Puede agregar un conjunto de objetos encontrados previamente en un gráfico para evitar a) la recursividad infinita yb) evitar agregar la misma memoria dos veces.
mafu

4

Esto es imposible de hacer en tiempo de ejecución.

Sin embargo, existen varios perfiladores de memoria que muestran el tamaño del objeto.

EDITAR : Puede escribir un segundo programa que perfile el primero utilizando la API de creación de perfiles CLR y se comunique con él a través de comunicación remota o algo así.


17
Si es imposible hacerlo en tiempo de ejecución, ¿cómo están proporcionando la información los perfiladores de memoria?
Janie

2
Mediante el uso de la API de creación de perfiles. Sin embargo, un programa no puede perfilarse a sí mismo
SLaks

Interesante. ¿Qué pasa si quisiera que el código se ocupara de casos en los que los objetos consumieran demasiada memoria?
Janie

4
Entonces tendrías que lidiar con un software autoconsciente y yo tendría mucho miedo. :-) En serio, "principal de responsabilidad única": deje que el programa sea el programa, deje que otra pieza de código observe si hay objetos que ocupen demasiada memoria.
John Saunders

2
@Janie: también harías suposiciones sobre la importancia del tamaño y cómo se relaciona con el rendimiento. Creo que le gustaría ser un verdadero experto en rendimiento de CLR de bajo nivel (del tipo que ya conoce la API de creación de perfiles) antes de hacerlo. De lo contrario, podría estar aplicando sus experiencias anteriores a una situación en la que no se aplican.
John Saunders


2

AFAIK, no se puede, sin contar en profundidad el tamaño de cada miembro en bytes. Pero, de nuevo, ¿el tamaño de un miembro (como los elementos dentro de una colección) cuenta para el tamaño del objeto, o un puntero a ese miembro cuenta para el tamaño del objeto? Depende de cómo lo definas.

Me he encontrado con esta situación antes en la que quería limitar los objetos en mi caché en función de la memoria que consumían.

Bueno, si hay algún truco para hacer eso, ¡estaría encantado de saberlo!


2

Para tipos de valor, puede usar Marshal.SizeOf. Por supuesto, devuelve el número de bytes necesarios para ordenar la estructura en la memoria no administrada, que no es necesariamente lo que utiliza CLR.


SizeOf (Object) puede no estar disponible en versiones futuras. En su lugar, use SizeOf <T> (). Para obtener más información, visite go.microsoft.com/fwlink/?LinkID=296514
Vinigas

1

Puede utilizar la reflexión para recopilar toda la información pública de propiedad o miembros (dado el tipo de objeto). Sin embargo, no hay forma de determinar el tamaño sin recorrer cada dato individual del objeto.


1

Para cualquiera que busque una solución que no requiera [Serializable]clases y donde el resultado sea una aproximación en lugar de ciencia exacta. El mejor método que pude encontrar es la serialización json en un flujo de memoria usando codificación 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;
    }
}

No, esto no le dará el tamaño exacto que se usaría en la memoria. Como se mencionó anteriormente, eso no es posible. Pero te dará una estimación aproximada.

Tenga en cuenta que esto también es bastante lento.


1

Desde Pavel y jnm2:

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

En una nota al margen, tenga cuidado porque solo funciona con objetos de memoria contiguos


1

He creado una prueba de referencia para diferentes colecciones en .NET: https://github.com/scholtz/TestDotNetCollectionsMemoryAllocation

Los resultados son los siguientes para .NET Core 2.2 con 1,000,000 de objetos con 3 propiedades asignadas:

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

Para la prueba de memoria encontré lo mejor para usar

GC.GetAllocatedBytesForCurrentThread()

1

Para matrices de estructuras / valores, tengo diferentes resultados con:

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

(ejemplo simplificado)

Cualquiera que sea el enfoque, realmente necesita comprender cómo funciona .Net para interpretar correctamente los resultados. Por ejemplo, el tamaño del elemento devuelto es el tamaño del elemento "alineado", con algo de relleno. La sobrecarga y, por lo tanto, el tamaño es diferente según el uso de un tipo: "en caja" en el montón de GC, en la pila, como un campo, como un elemento de matriz.

(Quería saber cuál sería el impacto en la memoria de usar estructuras vacías "ficticias" (sin ningún campo) para imitar argumentos "opcionales" de genéricos; haciendo pruebas con diferentes diseños que involucren estructuras vacías, puedo ver que una estructura vacía usa ( al menos) 1 byte por elemento; recuerdo vagamente que se debe a que .Net necesita una dirección diferente para cada campo, lo que no funcionaría si un campo realmente estuviera vacío / de tamaño 0).


0

La forma más sencilla es: int size = *((int*)type.TypeHandle.Value + 1)

Sé que esto es un detalle de implementación, pero GC se basa en él y debe estar lo más cerca posible del inicio de la tabla de métodos para la eficiencia, además de tener en cuenta cómo el código GC es complejo y nadie se atreverá a cambiarlo en el futuro. De hecho, funciona para todas las versiones menores / mayores de .net framework + .net core. (Actualmente no se puede probar para 1.0)
Si desea una forma más confiable, emita una estructura en un ensamblado dinámico [StructLayout(LayoutKind.Auto)]con exactamente los mismos campos en el mismo orden, tome su tamaño con sizeof instrucciones IL. Es posible que desee emitir un método estático dentro de la estructura que simplemente devuelve este valor. Luego agregue 2 * IntPtr.Size para el encabezado del objeto. Esto debería darte un valor exacto.
Pero si su clase deriva de otra clase, necesita encontrar cada tamaño de clase base por separado y agregarlos + 2 * Inptr.Size nuevamente para el encabezado. Puede hacer esto obteniendo campos con BindingFlags.DeclaredOnlybandera.
Las matrices y cadenas solo agregan ese tamaño a su longitud * tamaño de elemento. Para el tamaño acumulativo de los objetos agregados, debe implementar una solución más sofisticada que implique visitar cada campo e inspeccionar su contenido.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.