我有两个带有字节数组和布尔值的结构:
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;
}
和以下代码:
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();
}
}
这给出了以下输出:
sizeof array of bytes: 3
sizeof array of bools: 12
似乎一个boolean
需要4个字节的存储空间。理想情况下,boolean
只需要一个比特(false
或true
、0
或1
等)。
这是怎么回事?boolean
类型真的这么低效吗?
发布于 2015-02-14 19:53:00
bool类型有一个曲折的历史,在语言运行时之间有许多不兼容的选择。这始于Dennis Ritchie的一个历史性的设计选择,他发明了C语言。它没有bool类型,可选的是int,其中值0表示false,任何其他值都被认为是true。
这一选择在Winapi中得到了延续,这是使用pinvoke的主要原因,它有一个BOOL
的类型定义函数,它是C编译器的int关键字的别名。如果您不应用显式的MarshalAs属性,那么C#布尔值将被转换为BOOL值,从而生成一个4字节长的字段。
无论您做什么,您的struct声明都需要与您所使用的语言中的运行时选择相匹配。如上所述,BOOL用于winapi,但大多数C++实现选择字节,大多数COM自动化互操作使用VARIANT_BOOL,它是一个缩写。
C# bool
的实际大小是一个字节。CLR的一个强大的设计目标是你找不到答案。布局是一个在很大程度上依赖处理器的实现细节。处理器对变量类型和对齐非常挑剔,错误的选择会严重影响性能并导致运行时错误。通过使布局不可发现,.NET可以提供不依赖于实际运行时实现的通用类型系统。
换句话说,你总是需要在运行时编组一个结构来确定布局。此时进行从内部布局到互操作布局的转换。如果布局相同,这可能会非常快;当字段需要重新排列时,速度会很慢,因为这总是需要创建结构的副本。这方面的技术术语是blittable,将blittable结构传递给本机代码是很快的,因为pinvoke编组程序可以简单地传递一个指针。
性能也是bool不是单比特的核心原因。很少有处理器可以让位直接寻址,最小的单位是一个字节。需要额外的指令来从字节中提取位,这不是免费的。而且它从来不是原子的。
使用sizeof(bool)
,C#编译器不会害羞地告诉您它需要1个字节。这仍然不能很好地预测一个字段在运行时需要多少字节,CLR还需要实现.NET内存模型,并且它承诺简单的变量更新是原子的。这要求变量在内存中正确对齐,以便处理器可以在单个内存总线周期内对其进行更新。通常情况下,bool实际上需要4或8个字节的内存。为确保下一个成员正确对齐而添加的额外填充。
CLR实际上利用了布局不可发现的优势,它可以优化类的布局并重新排列字段,从而使填充最小化。比方说,如果你有一个包含bool + int + bool成员的类,那么它将占用1+ (3) +4+1+ (3)字节的内存,(3)是填充,总共12字节。50%的浪费。自动布局重新排列为1+1+ (2) +4=8字节。只有类具有自动布局,默认情况下结构具有顺序布局。
更糟糕的是,在使用支持AVX指令集的现代C++编译器编译的C++程序中,bool可能需要多达32个字节。这施加了32字节的对齐要求,所以布尔变量可能以31字节的填充结束。这也是为什么.NET抖动不会发出单指令多数据指令的核心原因,除非显式包装,否则它不能获得对齐保证。
https://stackoverflow.com/questions/28514373
复制相似问题