Eu tenho duas estruturas com matrizes de bytes e booleanos:
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct1
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] values;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct2
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public bool[] values;
}
E o seguinte código:
class main
{
public static void Main()
{
Console.WriteLine("sizeof array of bytes: "+Marshal.SizeOf(typeof(struct1)));
Console.WriteLine("sizeof array of bools: " + Marshal.SizeOf(typeof(struct2)));
Console.ReadKey();
}
}
Isso me dá a seguinte saída:
sizeof array of bytes: 3
sizeof array of bools: 12
Parece que um boolean
leva 4 bytes de armazenamento. Idealmente, um boolean
levaria apenas um bit ( false
ou true
, 0
ou 1
, etc.).
O que esta acontecendo aqui? O boolean
tipo é realmente tão ineficiente?
Respostas:
O tipo bool tem um histórico quadriculado com muitas opções incompatíveis entre os tempos de execução do idioma. Isso começou com uma escolha histórica de design feita por Dennis Ritchie, o cara que inventou a linguagem C. Não tinha um tipo booleano , a alternativa era int em que um valor 0 representa false e qualquer outro valor foi considerado verdadeiro .
Essa opção foi levada adiante no Winapi, o principal motivo para usar o pinvoke, pois possui um typedef para o
BOOL
qual é um alias da palavra-chave int do compilador C. Se você não aplicar um atributo [MarshalAs] explícito, um booleano C # será convertido em BOOL, produzindo um campo com 4 bytes de comprimento.Faça o que fizer, sua declaração de estrutura precisa corresponder à escolha do tempo de execução feita no idioma com o qual você interage. Como observado, o BOOL para o winapi, mas a maioria das implementações de C ++ escolheu o byte , a maior interoperabilidade de automação do COM usa VARIANT_BOOL, que é curto .
O tamanho real de um C #
bool
é um byte. Um forte objetivo de design do CLR é que você não pode descobrir. O layout é um detalhe de implementação que depende muito do processador. Os processadores são muito exigentes quanto aos tipos de variáveis e alinhamento; escolhas erradas podem afetar significativamente o desempenho e causar erros de tempo de execução. Ao tornar o layout não detectável, o .NET pode fornecer um sistema de tipo universal que não depende da implementação real do tempo de execução.Em outras palavras, você sempre precisa organizar uma estrutura em tempo de execução para definir o layout. Nesse momento, é feita a conversão do layout interno para o layout de interoperabilidade. Isso pode ser muito rápido se o layout for idêntico, lento quando os campos precisarem ser reorganizados, pois isso sempre exige a criação de uma cópia da estrutura. O termo técnico para isso é blittable , passar uma estrutura blittable para o código nativo é rápido porque o empacotador pinvoke pode simplesmente passar um ponteiro.
O desempenho também é a principal razão pela qual um bool não é um único bit. Existem poucos processadores que tornam um pouco diretamente endereçável, a menor unidade é um byte. É necessária uma instrução extra para pescar o bit fora do byte, que não é de graça. E nunca é atômico.
O compilador C # não tem vergonha de dizer que é necessário 1 byte, use
sizeof(bool)
. Ainda não é um preditor fantástico para quantos bytes um campo leva em tempo de execução, o CLR também precisa implementar o modelo de memória .NET e promete que atualizações simples de variáveis sejam atômicas . Isso requer que as variáveis sejam alinhadas corretamente na memória para que o processador possa atualizá-lo com um único ciclo de barramento de memória. Com bastante frequência, um bool exige 4 ou 8 bytes de memória por causa disso. Preenchimento extra que foi adicionado para garantir que o próximo membro esteja alinhado corretamente.O CLR realmente aproveita o fato de o layout não ser descoberto, ele pode otimizar o layout de uma classe e reorganizar os campos para que o preenchimento seja minimizado. Então, digamos, se você tem uma classe com um membro bool + int + bool, seriam necessários 1 + (3) + 4 + 1 + (3) bytes de memória, (3) é o preenchimento, para um total de 12 bytes. 50% de resíduos. O layout automático é reorganizado para 1 + 1 + (2) + 4 = 8 bytes. Somente uma classe tem layout automático, as estruturas têm layout seqüencial por padrão.
Mais sombriamente, um bool pode exigir até 32 bytes em um programa C ++ compilado com um compilador C ++ moderno que suporta o conjunto de instruções AVX. O que impõe um requisito de alinhamento de 32 bytes, a variável bool pode acabar com 31 bytes de preenchimento. Além disso, a principal razão pela qual uma instabilidade .NET não emite instruções SIMD, a menos que explicitamente agrupada, não pode obter a garantia de alinhamento.
fonte
Em primeiro lugar, esse é apenas o tamanho da interoperabilidade. Não representa o tamanho no código gerenciado da matriz. São 1 byte por
bool
- pelo menos na minha máquina. Você pode testá-lo por si mesmo com este código:Agora, para organizar matrizes por valor, como você é, a documentação diz:
Então, olhamos
ArraySubType
, e isso tem documentação de:Agora, olhando
UnmanagedType
, há:Portanto, esse é o padrão para
bool
, e são 4 bytes porque corresponde ao tipo BO32 do Win32 - portanto, se você estiver interoperando com o código que espera umaBOOL
matriz, ele faz exatamente o que deseja.Agora você pode especificar o
ArraySubType
asI1
, que está documentado como:Portanto, se o código com o qual você interopera espera 1 byte por valor, basta usar:
Seu código mostrará isso como ocupando 1 byte por valor, conforme o esperado.
fonte