Tengo dos estructuras con matrices de bytes y 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;
}
Y el siguiente 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();
}
}
Eso me da el siguiente resultado:
sizeof array of bytes: 3
sizeof array of bools: 12
Parece ser que boolean
toma 4 bytes de almacenamiento. Idealmente boolean
, a solo tomaría un bit ( false
o true
, 0
o 1
, etc.).
¿Que está sucediendo aquí? ¿El boolean
tipo es realmente tan ineficiente?
Respuestas:
El tipo bool tiene un historial a cuadros con muchas opciones incompatibles entre los tiempos de ejecución del idioma. Esto comenzó con una elección histórica de diseño hecha por Dennis Ritchie, el tipo que inventó el lenguaje C. No tenía un tipo bool , la alternativa era int donde un valor de 0 representa falso y cualquier otro valor se considera verdadero .
Esta elección se llevó a cabo en Winapi, la razón principal para usar pinvoke, tiene un typedef para el
BOOL
cual es un alias para la palabra clave int del compilador de C. Si no aplica un atributo explícito [MarshalAs], un bool de C # se convierte en BOOL, lo que genera un campo de 4 bytes de longitud.Independientemente de lo que haga, su declaración de estructura debe coincidir con la elección de tiempo de ejecución realizada en el idioma con el que interopera. Como se señaló, BOOL para el winapi, pero la mayoría de las implementaciones de C ++ eligieron el byte , la mayoría de la interoperabilidad de COM utiliza VARIANT_BOOL, que es una abreviatura .
El tamaño real de un C #
bool
es un byte. Un fuerte objetivo de diseño del CLR es que no puede descubrirlo. El diseño es un detalle de implementación que depende demasiado del procesador. Los procesadores son muy exigentes con los tipos de variables y la alineación, las elecciones incorrectas pueden afectar significativamente el rendimiento y causar errores de tiempo de ejecución. Al hacer que el diseño no se pueda descubrir, .NET puede proporcionar un sistema de tipo universal que no depende de la implementación real del tiempo de ejecución.En otras palabras, siempre debe ordenar una estructura en tiempo de ejecución para definir el diseño. En ese momento se realiza la conversión del diseño interno al diseño de interoperabilidad. Eso puede ser muy rápido si el diseño es idéntico, lento cuando los campos necesitan ser reorganizados ya que eso siempre requiere crear una copia de la estructura. El término técnico para esto es blittable , pasar una estructura blittable a código nativo es rápido porque el jefe de orquestación pinvoke simplemente puede pasar un puntero.
El rendimiento también es la razón principal por la cual un bool no es un solo bit. Hay pocos procesadores que hacen que un poco sea directamente direccionable, la unidad más pequeña es un byte. Se requiere una instrucción adicional para extraer un poco del byte, que no es gratis. Y nunca es atómico.
El compilador de C # no es tímido al decirle que toma 1 byte, use
sizeof(bool)
. Este aún no es un predictor fantástico de cuántos bytes ocupa un campo en tiempo de ejecución, el CLR también necesita implementar el modelo de memoria .NET y promete que las actualizaciones simples de variables son atómicas . Eso requiere que las variables estén correctamente alineadas en la memoria para que el procesador pueda actualizarlo con un solo ciclo de bus de memoria. Muy a menudo, un bool realmente requiere 4 u 8 bytes en memoria debido a esto. Acolchado adicional que se agregó para garantizar que el siguiente miembro esté alineado correctamente.El CLR realmente aprovecha el diseño que no se puede descubrir, puede optimizar el diseño de una clase y reorganizar los campos para minimizar el relleno. Entonces, digamos, si tiene una clase con un miembro bool + int + bool, entonces tomaría 1 + (3) + 4 + 1 + (3) bytes de memoria, (3) es el relleno, para un total de 12 bytes 50% de residuos. El diseño automático se reorganiza a 1 + 1 + (2) + 4 = 8 bytes. Solo una clase tiene un diseño automático, las estructuras tienen un diseño secuencial por defecto.
Más sombríamente, un bool puede requerir hasta 32 bytes en un programa C ++ compilado con un compilador moderno de C ++ que admite el conjunto de instrucciones AVX. Lo que impone un requisito de alineación de 32 bytes, la variable bool puede terminar con 31 bytes de relleno. También es la razón principal por la cual un jitter de .NET no emite instrucciones SIMD, a menos que se envuelva explícitamente, no puede obtener la garantía de alineación.
fuente
En primer lugar, este es solo el tamaño para interoperabilidad. No representa el tamaño en el código administrado de la matriz. Eso es 1 byte por
bool
- al menos en mi máquina. Puede probarlo usted mismo con este código:Ahora, para ordenar matrices por valor, tal como eres, la documentación dice:
Entonces miramos
ArraySubType
, y eso tiene documentación de:Ahora mirando
UnmanagedType
, hay:Entonces, ese es el valor predeterminado para
bool
, y es de 4 bytes porque corresponde al tipo BOOL de Win32, por lo que si interopera con un código que espera unaBOOL
matriz, hace exactamente lo que desea.Ahora puede especificar el
ArraySubType
as en suI1
lugar, que se documenta como:Entonces, si el código con el que interopera espera 1 byte por valor, simplemente use:
Su código luego lo mostrará como ocupando 1 byte por valor, como se esperaba.
fuente