Encontre o tamanho da instância do objeto em bytes em c #


113

Para qualquer instância arbitrária (coleções de diferentes objetos, composições, objetos individuais, etc)

Como posso determinar seu tamanho em bytes?

(Atualmente, tenho uma coleção de vários objetos e estou tentando determinar o tamanho agregado dela)

EDIT: Alguém escreveu um método de extensão para Object que poderia fazer isso? Isso seria muito legal, imo.



Respostas:


60

Em primeiro lugar, um aviso: o que se segue está estritamente no reino dos hacks feios e não documentados. Não confie que isso funcione - mesmo que funcione para você agora, pode parar de funcionar amanhã, com qualquer atualização pequena ou importante do .NET.

Você pode usar as informações neste artigo sobre os componentes internos do CLR. MSDN Magazine Edição de 2005, maio - Faça uma análise detalhada do .NET Framework para ver como o CLR cria objetos de tempo de execução - pela última vez que verifiquei, ele ainda era aplicável. Veja como isso é feito (ele recupera o campo interno "Tamanho da Instância Básica" via TypeHandledo 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);

Isso funciona no 3.5 SP1 de 32 bits. Não tenho certeza se os tamanhos dos campos são iguais em 64 bits - talvez seja necessário ajustar os tipos e / ou deslocamentos se não forem.

Isso funcionará para todos os tipos "normais", para os quais todas as instâncias têm os mesmos tipos bem definidos. Aqueles para os quais isso não é verdade são arrays e strings com certeza, e acredito também StringBuilder. Para eles, você terá que adicionar o tamanho de todos os elementos contidos ao tamanho de sua instância base.


Não. Não há uma maneira "adequada" de fazer isso, porque não é algo com que um aplicativo .NET bem-comportado deva se preocupar em primeiro lugar. O acima mucks diretamente com as estruturas de dados internas de uma implementação particular de CLR (que pode mudar facilmente na próxima versão do .NET, por exemplo).
Pavel Minaev,

3
isso deveria funcionar em C # ou apenas em c ++ gerenciado? não estou feliz em C # até agora que eu tentei:Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
Maslow

17
A versão .NET 4 disso nem mesmo precisa de código inseguro: Marshal.ReadInt32(type.TypeHandle.Value, 4)funciona para x86 e x64. Eu apenas testei tipos de estruturas e classes. Lembre-se de que isso retorna o tamanho em caixa para os tipos de valor. @Pavel Talvez você possa atualizar sua resposta.
jnm2

2
@ sab669 bem, substitua typepor obj.GetType()em seu exemplo. Não importa qual framework você está usando, apenas qual CLR (v2 ou v4 ou CoreCLR). Eu não tentei isso no CoreCLR.
jnm2 01 de

2
@SamGoldberg Calcular isso manualmente dá muito trabalho com um milhão de casos extremos. Sizeof informa o tamanho estático de um objeto, não o consumo de memória de um gráfico de tempo de execução de objetos. Os perfis de memória e CPU do VS2017 são muito bons, assim como o ReSharper e outras ferramentas, e é isso que eu usaria para medir.
jnm2

20

Você pode ser capaz de aproximar o tamanho fingindo serializá-lo com um serializador binário (mas direcionando a saída para o esquecimento) se estiver trabalhando com objetos serializáveis.

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
Claro, isso pode fornecer a você um tamanho mínimo, mas não diz nada sobre o tamanho na memória.
John Saunders

Lol, a próxima lâmpada que tive antes de voltar para verificar as respostas estava usando o serializador binário. John, como isso não lhe daria o tamanho real na memória?
Janie

2
Ele forneceria o tamanho serializado, que será o tamanho desejado pelo serializador, para fins de "serializador". Esses são provavelmente diferentes dos propósitos de "sentar na memória". Talvez o serializador armazene inteiros menores em três bytes, por exemplo.
John Saunders

4
Como eu disse, é apenas uma aproximação. Não é perfeito, mas eu discordo que não diz "nada" sobre o tamanho na memória. Eu diria que isso lhe deu uma ideia - serializações maiores geralmente seriam correlacionadas com tamanhos maiores na memória. Existe algum relacionamento.
BlueMonkMN

Eu concordo - é útil obter uma estimativa aproximada do tamanho de um gráfico de objeto .NET.
Craig Shearer

8

Para tipos não gerenciados, também conhecidos como tipos de valor, structs:

        Marshal.SizeOf(object);

Para objetos gerenciados, quanto mais perto eu chego, é uma aproximação.

        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;

Não use serialização. Um formatador binário adiciona cabeçalhos, para que você possa alterar sua classe e carregar um arquivo serializado antigo na classe modificada.

Também não informa o tamanho real da memória nem leva em consideração o alinhamento da memória.

[Edit] Usando BiteConverter.GetBytes (prop-value) recursivamente em cada propriedade de sua classe, você obteria o conteúdo em bytes, que não conta o peso da classe ou referências, mas está muito mais próximo da realidade. Eu recomendaria usar uma matriz de bytes para dados e uma classe de proxy não gerenciada para acessar valores usando projeção de ponteiro se o tamanho for importante, observe que seria memória não alinhada, então em computadores antigos será lento, mas conjuntos de dados ENORMES na RAM MODERNA serão consideravelmente mais rápido, pois minimizar o tamanho para ler da RAM terá um impacto maior do que o desalinhado.


5

Isso não se aplica à implementação .NET atual, mas uma coisa a se ter em mente com tempos de execução gerenciados / coletados de lixo é que o tamanho alocado de um objeto pode mudar durante a vida útil do programa. Por exemplo, alguns coletores de lixo geracionais (como o coletor Híbrido de Contagem de Referência Geracional / Ulterior ) só precisam armazenar certas informações depois que um objeto é movido do berçário para o espaço maduro.

Isso torna impossível criar uma API genérica confiável para expor o tamanho do objeto.


Interessante. Então, o que as pessoas fazem para determinar dinamicamente o tamanho de seus objetos / coleções de objetos?
Janie

2
Depende do que eles precisam. Se for para P / Invoke (interoperabilidade de código nativo), eles usam Marshal.SizeOf (typeof (T)). Se for para criação de perfil de memória, eles usam um criador de perfil separado que coopera com o ambiente de execução para fornecer as informações. Se você estiver interessado no alinhamento do elemento em uma matriz, pode usar o opcode SizeOf IL em um DynamicMethod (não acho que haja uma maneira mais fácil no .NET framework para isso).
Sam Harwell

5

solução segura com algumas otimizações de código CyberSaving / MemoryUsage . algum 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 também com aulas:

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

Essa é a abordagem que eu faria também. Você pode adicionar um conjunto de objetos encontrados anteriormente em um gráfico para evitar a) recursão infinita eb) evitar adicionar a mesma memória duas vezes.
mafu

4

Isso é impossível de fazer em tempo de execução.

No entanto, existem vários perfis de memória que exibem o tamanho do objeto.

EDIT : Você poderia escrever um segundo programa que perfis o primeiro usando a API CLR Profiling e se comunicar com ele por meio de comunicação remota ou algo assim.


17
Se for impossível fazer em tempo de execução, como os criadores de perfil de memória estão fornecendo as informações?
Janie

2
Usando a API de criação de perfil. No entanto, um programa não pode
criar seu

Interessante. E se eu quisesse que o código lidasse com casos em que os objetos estivessem consumindo muita memória?
Janie

4
Então você estaria lidando com software autoconsciente e eu teria muito medo. :-) Sério, "principal de responsabilidade única" - deixe o programa ser o programa, deixe alguma outra parte do código observar se há objetos que ocupam muita memória.
John Saunders

2
@Janie: você também faria suposições sobre a importância do tamanho e como ele se relaciona com o desempenho. Acho que você gostaria de ser um verdadeiro especialista em desempenho CLR de baixo nível (o tipo que já conhece a API de criação de perfil) antes de fazer isso. Caso contrário, você pode estar aplicando suas experiências anteriores a uma situação na qual elas não se aplicam.
John Saunders


2

AFAIK, você não pode, sem realmente contar profundamente o tamanho de cada membro em bytes. Mas, novamente, o tamanho de um membro (como os elementos dentro de uma coleção) conta para o tamanho do objeto, ou um ponteiro para esse membro conta para o tamanho do objeto? Depende de como você o define.

Já passei por essa situação antes em que queria limitar os objetos em meu cache com base na memória que eles consumiam.

Bem, se houver algum truque para fazer isso, eu ficaria muito feliz em saber sobre ele!


2

Para tipos de valor, você pode usar Marshal.SizeOf. Claro, ele retorna o número de bytes necessários para empacotar a estrutura na memória não gerenciada, o que não é necessariamente o que o CLR usa.


SizeOf (Object) pode não estar disponível em versões futuras. Em vez disso, use SizeOf <T> (). Para obter mais informações, acesse go.microsoft.com/fwlink/?LinkID=296514
Vinigas

1

Você pode usar a reflexão para reunir todos os membros públicos ou informações de propriedade (de acordo com o tipo do objeto). No entanto, não há como determinar o tamanho sem percorrer cada dado individual no objeto.


1

Para quem procura uma solução que não requeira [Serializable]aulas e cujo resultado seja uma aproximação em vez de ciência exacta. O melhor método que encontrei é a serialização json em um fluxo de memória usando a codificação 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;
    }
}

Não, isso não fornecerá o tamanho exato que seria usado na memória. Como mencionado anteriormente, isso não é possível. Mas isso lhe dará uma estimativa aproximada.

Observe que isso também é muito lento.


1

De Pavel e jnm2:

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

Por outro lado, tenha cuidado porque ele só funciona com objetos de memória contíguos


1

Eu criei testes de benchmark para diferentes coleções em .NET: https://github.com/scholtz/TestDotNetCollectionsMemoryAllocation

Os resultados são os seguintes para .NET Core 2.2 com 1.000.000 de objetos com 3 propriedades alocadas:

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 teste de memória achei o melhor para ser usado

GC.GetAllocatedBytesForCurrentThread()

1

Para matrizes de estruturas / valores, tenho resultados diferentes com:

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

(exemplo simplificado)

Seja qual for a abordagem, você realmente precisa entender como o .Net funciona para interpretar corretamente os resultados. Por exemplo, o tamanho do elemento retornado é o tamanho do elemento "alinhado", com algum preenchimento. A sobrecarga e, portanto, o tamanho são diferentes dependendo do uso de um tipo: "encaixotado" no heap do GC, na pilha, como um campo, como um elemento de matriz.

(Eu queria saber qual seria o impacto na memória de usar estruturas vazias "fictícias" (sem nenhum campo) para imitar argumentos "opcionais" de genéricos; fazendo testes com diferentes layouts envolvendo estruturas vazias, posso ver que uma estrutura vazia usa ( pelo menos) 1 byte por elemento; lembro vagamente que é porque .Net precisa de um endereço diferente para cada campo, o que não funcionaria se um campo realmente estivesse vazio / tamanho 0).


0

A maneira mais simples é: int size = *((int*)type.TypeHandle.Value + 1)

Eu sei que este é um detalhe de implementação, mas o GC depende disso e precisa estar o mais próximo do início da tabela de métodos para eficiência, além de levar em consideração como o código do GC é complexo, ninguém ousará alterá-lo no futuro. Na verdade, ele funciona para todas as versões secundárias / principais do .net framework + .net core. (Atualmente não é possível testar para 1.0)
Se você quiser uma maneira mais confiável, emita um struct em um assembly dinâmico [StructLayout(LayoutKind.Auto)]com exatamente os mesmos campos na mesma ordem, tome seu tamanho com a instrução sizeof IL. Você pode querer emitir um método estático dentro de struct que simplesmente retorna esse valor. Em seguida, adicione 2 * IntPtr.Size para o cabeçalho do objeto. Isso deve fornecer o valor exato.
Mas se sua classe deriva de outra classe, você precisa encontrar cada tamanho da classe base separadamente e adicioná-los + 2 * Inptr.Size novamente para o cabeçalho. Você pode fazer isso obtendo campos com BindingFlags.DeclaredOnlysinalizador.
Arrays e strings apenas adicionam esse tamanho ao tamanho do elemento length *. Para o tamanho cumulativo de objetos aggreagate, você precisa implementar uma solução mais sofisticada que envolve visitar cada campo e inspecionar seu conteúdo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.