Este artigo é um artigo espelhado de tradução automática, por favor clique aqui para ir para o artigo original.

Vista: 537|Resposta: 1

[Fonte] [Virar]. Como o NET/C# calcula quanta memória uma instância ocupa?

[Copiar link]
Postado em 27-08-2025 09:00:22 | | | |
Todos sabemos que CPU e memória são as duas métricas mais importantes para um programa, então quantas pessoas realmente pensaram na questão: Quantos bytes uma instância de um tipo (tipo valor ou tipo referência) ocupa na memória? Muitos de nós não podem responder. O C# fornece alguns operadores e APIs para calcular tamanhos, mas nenhum resolve completamente o problema que acabei de perguntar. Este artigo fornece um método para calcular o número de bytes de memória ocupados por instâncias de tipos de valor e tipos de referência. O código-fonte é baixado daqui.

1. Tamanho do operador
2. Método Marshal. Tamanho do
3. Inseguro. Tamanho do método >
4. Pode ser calculado com base no tipo de membro do campo?
5. Disposição dos tipos de valor e tipos de aplicação
6. Diretiva LDFLDA
7. Calcular o número de bytes do tipo de valor
8. Conte o número de bytes do tipo de citação
9. Cálculo completo

1. Tamanho do operador

A operação sizeof é usada para determinar o número de bytes ocupados por uma instância de um tipo, mas só pode ser aplicada a tipos não gerenciados. O chamado tipo Não Gerenciado é limitado a:

Tipos Primitivos: Booleano, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Duplo e Simples)
Tipo decimal
Tipo de enumeração
Tipo de ponteiro
Structs que contêm apenas membros de dados do tipo Não gerenciado
Como o nome sugere, um tipo Não gerenciado é um tipo de valor, e a instância correspondente não pode conter nenhuma referência ao objeto gerenciado. Se definirmos um método genérico como este para chamar o operador sizeof, o parâmetro genérico T deve adicionar uma restrição não gerenciada e uma tag insegura ao método.

Apenas os tipos nativos e enum podem usar diretamente o operador sizeof, que deve ser adicionado se aplicado a outros tipos (ponteiros e estruturas personalizadas)./unsafeetiquetas de compilação, e também precisam ser colocadas eminsegurono contexto.

Como a seguinte estrutura Foobar não é do tipo Não gerenciado, o programa apresentará um erro de compilação.

2. Método Marshal. Tamanho do

Tipos estáticos O Marshal define uma série de APIs que nos ajudam a alocar e copiar memória não gerenciada, converter entre tipos gerenciados e não gerenciados, e realizar uma série de outras operações em memória não gerenciada (Marshal, em ciência computacional, refere-se à operação de converter objetos de memória para o formato correspondente para armazenamento ou transferência de dados). Estática, que inclui as seguintes 4 sobrecargas de métodos SizeOf para determinar o número de bytes de um dado tipo ou objeto.

O método Marshal.SizeOf não possui uma restrição sobre o tipo especificado para o tipo Não Gerenciado, mas ainda exige que uma seja especificadaTipo de valor。 Se o objeto que entra é um objeto, ele também deve ser uma caixa para um tipo de valor.

Como o seguinte Foobar é definido como:tipo, então chamadas para ambos os métodos SizeOf lançam uma exceção ArgumentException e um prompt: O tipo 'Foobar' não pode ser marshalado como uma estrutura não gerenciada; nenhum tamanho ou deslocamento significativo pode ser calculado.

Método Marshal. TamanhoDoGenéricos não são suportados, mas também possui requisitos para o layout da estrutura, que suporta suporteSequencialeExplícitoModo layout. Como a estrutura Foobar mostrada abaixo adota o modo Auto layout (Auto, que não suporta "planejamento dinâmico" do layout de memória baseado em membros de campo devido aos requisitos mais rigorosos de layout de memória em ambientes não gerenciados), chamadas para o método SizeOf ainda gerarão a mesma exceção ArgumentException que foi mencionada acima.

3. Inseguro. Método TamanhoDo

O Static Unsafe oferece operações mais de baixo nível para memória não gerenciada, e métodos SizeIOf semelhantes também são definidos nesse tipo. O método não tem restrições sobre o tipo especificado, mas se você especificar um tipo de referência, ele retorna oNúmero de bytes do ponteiro"(IntPtr.Size)。

4. Pode ser calculado com base no tipo de membro do campo?

Sabemos que tanto os tipos de valor quanto os de referência são mapeados como um fragmento contínuo (ou armazenados diretamente em um registrador). O propósito de um tipo é especificar o layout de memória de um objeto, e instâncias do mesmo tipo têm o mesmo layout e o número de bytes é naturalmente o mesmo (para campos de tipo de referência, ele armazena apenas o endereço referenciado nessa sequência de bytes). Como o comprimento do byte é determinado pelo tipo, se pudermos determinar o tipo de cada membro do campo, não conseguiríamos calcular o número de bytes correspondentes a esse tipo? Na verdade, isso não é possível.

Por exemplo, sabemos que os bytes de byte, short, int e long são 1, 2, 4 e 8, então o número de bytes para um binário de byte é 2, mas para uma combinação de tipos de byte + short, byte + int e byte + long, os bytes correspondentes não são 3, 5 e 9, mas 3, 8 e 16. Porque isso envolve a questão do alinhamento da memória.

5. Disposição dos tipos de valor e tipos de referência

O número de bytes ocupados por instâncias do tipo e subtipo de referência também é diferente para exatamente o mesmo membro de dados. Como mostrado na imagem a seguir, a sequência de bytes da instância do tipo de valorTodos são membros de campo usados para armazená-lo。 Para instâncias de tipos de referência, o endereço da tabela de métodos correspondente ao tipo também é armazenado na frente da sequência de bytes de campo. A tabela de métodos fornece quase todos os metadados que descrevem o tipo, e usamos essa referência para determinar a qual tipo a instância pertence. Bem na frente, também há bytes extras, que chamaremos deCabeçalho do objetoEle não é usado apenas para armazenar o estado travado do objeto, mas o valor do hash também pode ser armazenado em cache aqui. Quando criamos uma variável do tipo de referência, essa variávelEle não aponta para o primeiro byte de memória ocupado pela instância, mas para o local onde o endereço da tabela de métodos é armazenado



6. Diretiva LDFLDA

Como introduzimos acima, o operador sizeof e o método SizeOf fornecidos pelo tipo estático Marshal/Unsafe não podem realmente resolver o cálculo do comprimento do byte ocupado pelas instâncias. Pelo que sei, esse problema não pode ser resolvido apenas no campo C#, mas é fornecido no nível de ILLDFLDAInstruções podem nos ajudar a resolver esse problema. Como o nome sugere, Ldflda significa Load Field Address, que nos ajuda a obter o endereço de um campo na instância. Como essa instrução IL não possui API correspondente em C#, só podemos usá-la na seguinte forma usando IL Emit.

Como mostrado no trecho de código acima, temos um método GenerateFieldAddressAccessor no tipo SizeCalculator, que gera um delegado do tipo Func<object?, long[]> baseado na lista de campos do tipo especificado, que nos ajuda a retornar o endereço de memória do objeto especificado e de todos os seus campos. Com o endereço do próprio objeto e o endereço de cada campo, podemos obter naturalmente o deslocamento de cada campo e, em seguida, calcular facilmente o número de bytes de memória ocupados por toda a instância.

7. Calcular o número de bytes do tipo de valor

Como os tipos de valor e os tipos de referência têm layouts diferentes na memória, também precisamos usar cálculos diferentes. Como o byte da estrutura é o conteúdo de todos os campos na memória, usamos uma forma inteligente de calculá-lo. Suponha que precisemos definir o número de bytes de uma estrutura do tipo T, então criamos uma tupla ValueTuple<T,T>, e o deslocamento do seu segundo campo Item2 seja o número de bytes da estrutura T. O método de cálculo específico está refletido no seguinte método CalculateValueTypeInstance.

Como mostrado no trecho de código acima, assumindo que o tipo de struct que precisamos calcular seja T, chamamos o método GetDefaultAsObject para obter o objeto padrão(T) na forma de uma reflexão, e então criamos um ValueTuple<T,T>tuple. Após chamar o método GenerateFieldAddressAccessor para obter o delegado Func<object?, long[]> para calcular a instância e seus endereços de campo, chamamos esse delegado como um argumento. Para os três endereços de memória que obtemos, a tupla de código e os endereços dos campos 1 e 2 são os mesmos, usamos o terceiro endereço representando o Item2 menos o primeiro endereço, e obtemos o resultado que queremos.

8. Conte o número de bytes do tipo de citação

O cálculo de bytes para tipos de referência é mais complicado, usando essa ideia: depois de obter o endereço da própria instância e de cada campo, ordenamos os endereços para obter o deslocamento do último campo. Vamos adicionar esse deslocamento ao número de bytes do último campo em si, e então adicionar o "primeiro e último bytes" necessários ao resultado que queremos, o que é refletido no seguinte método CalculateReferneceTypeInstance.

Como mostrado no trecho de código acima, se o tipo especificado não tiver campos definidos, CalculateReferneceTypeInstance retorna o número mínimo de bytes da instância do tipo de referência: 3 vezes o número de bytes do ponteiro de endereço. Para arquiteturas x86, um objeto tipo aplicação ocupa pelo menos 12 bytes, incluindo ObjectHeader (4 bytes), ponteiros da tabela de métodos (bytes) e pelo menos 4 bytes de conteúdo de campo (esses 4 bytes são necessários mesmo que nenhum tipo seja definido sem campos). No caso da arquitetura x64, esse número mínimo de bytes será 24, pois o ponteiro da tabela de métodos e o conteúdo mínimo do campo se tornarão 8 bytes, embora o conteúdo válido do ObjectHeader ocupe apenas 4 bytes, mas 4 bytes de preenchimento serão adicionados na frente.

A distribuição de bytes ocupados pelo último campo também é muito simples: se o tipo for um tipo de valor, então o método CalculateValueTypeInstance definido anteriormente é chamado para calcular; se for um tipo de referência, o conteúdo armazenado no campo é apenas o endereço de memória do objeto alvo, então o comprimento é IntPtr.Size. Como as instâncias de tipo de referência são alinhadas com o IntPtr.Size na memória por padrão, isso também é feito aqui. Por fim, não se esqueça que a referência da instância do tipo de referência não aponta para o primeiro byte de memória, mas sim para o byte que armazena o ponteiro da tabela de métodos, então você precisa somar o número de bytes do ObjecthHeader (IntPtr.Size).

9. Cálculo completo

Os dois métodos usados para calcular o número de bytes de instâncias de tipo valor e tipo de referência são usados no seguinte método SizeOf. Como a chamada da instrução Ldflda precisa fornecer uma instância correspondente, esse método fornece um delegado para obter a instância correspondente, além de fornecer o tipo de destino. Os parâmetros correspondentes a esse delegado podem ser definidos por padrão, e usaremos o valor padrão para o tipo de valor. Para tipos de referência, também tentaremos criar o objeto alvo usando o construtor padrão. Se esse objeto delegado não for fornecido e a instância alvo não puder ser criada, o método SizeOf lança uma exceção. Embora precisemos fornecer a instância alvo, o resultado calculado está relacionado apenas ao tipo, então armazenamos em cache o resultado calculado. Para facilitar a ligação, também oferecemos outro método genérico de <T>SizeOf.

No trecho de código abaixo, usamos para mostrar o número de bytes de duas estruturas e tipos com a mesma definição de campo. No próximo artigo, vamos obter ainda o conteúdo binário completo da instância na memória com base no número calculado de bytes, então fique ligado.

Link original:O login do hiperlink está visível.




Anterior:O framework front-end aprende o projeto open source Component-Party
Próximo:Armazenamento MinIO (iii) Copiar-enviar (migrar) arquivos locais para o bucket minio
 Senhorio| Postado em 27-08-2025 09:33:22 |
A coleção C# insere 10.000 pedaços de dados, que ocupam memória




Código:


Disclaimer:
Todo software, material de programação ou artigos publicados pela Code Farmer Network são apenas para fins de aprendizado e pesquisa; O conteúdo acima não deve ser usado para fins comerciais ou ilegais, caso contrário, os usuários terão todas as consequências. As informações deste site vêm da Internet, e disputas de direitos autorais não têm nada a ver com este site. Você deve deletar completamente o conteúdo acima do seu computador em até 24 horas após o download. Se você gosta do programa, por favor, apoie um software genuíno, compre o registro e obtenha serviços genuínos melhores. Se houver qualquer infração, por favor, entre em contato conosco por e-mail.

Mail To:help@itsvse.com