字节优化给您带来多大的性能提升(使它们成为 8、32、64 等的倍数...)?
这是一个示例结构:
[StructLayout(LayoutKind.Explicit)]
public struct RenderItem
{
[FieldOffset(0)] byte[] mCoordinates = new byte[3]; //(x,y,z)
[FieldOffset(3)] short mUnitType;
}
所以我的问题是,做这样的事情有多重要:
[StructLayout(LayoutKind.Explicit)]
public struct RenderItem
{
[FieldOffset(0)] byte[] mCoordinates = new byte[3]; //(x,y,z)
[FieldOffset(4)] short mUnitType;
[FieldOffset(6)] byte[] mPadding = new byte[2]; //make total to 8 bytes
}
我确定它是那些“随大小缩放”的东西之一,所以我特别好奇会看到这个结构被使用大约 150,000 次来创建 VertexBuffer 对象的操作:
//int objType[,,] 3 dimensional int with object type information stored in it
int i = 0;
RenderItem vboItems[16 * 16 * 16 * 36] //x - 16, y - 16, z - 16, 36 verticies per object
For(int x = 0; x < 16; x++)
{
For(int y = 0; y < 16; y++)
{
For(int z = 0; z < 16; z++)
{
vboItems[i++] = (x,y,z,objType[x,y,z]);
}
}
}
//Put vboItems into a VBO
最佳答案
我假设您应用了 [MarshalAs] 属性使数组成为 ByValArray,只有这样的结构才有意义。实际上,您通过将 struct 增加 2 个字节来使其变慢。这将降低处理器缓存的使用效率,当您在数组中使用它们时,将适合更少的结构,非常对性能很重要。
默认的 StructLayoutAttribute.Pack 值 8 已经过优化,可以提供最佳的结构布局。它实际上对您的结构没有任何影响,无论 Pack 值如何,成员都已经最佳对齐。任何现代处理器获得最佳性能的规则:
成员应与可被成员大小整除的地址对齐。这可能会在成员之间添加填充字节。此规则防止处理器必须多路复用来自内存读取的字节值或执行两次读取并将字节粘合在一起。在你的结构上不是问题,唯一需要对齐的成员是 mUnitType,它必须在 2 处对齐并且它已经在 4 处对齐。另请注意,你不必使用 [FieldOffset],默认布局已经很好.
在数组中使用结构时,成员应该正确对齐。这可能会将包装添加到结构的末尾,以使数组中的下一个元素正确对齐。同样不是你的结构的问题,它有 6 个字节长,所以数组中的下一个元素将对齐它的 mUnitType,因为它只需要 2 个。如果你实际上声明了没有 [MarshalAs] 的数组,那么抖动将自动添加 2 个字节在没有您帮助的情况下进行填充,以确保数组指针正确对齐。
成员永远不应跨越 cpu 缓存行。在我所知道的任何现代处理器上都是 64 字节。对 perf 非常不利,cpu 必须读取两个缓存行的数据并且总是将字节粘合在一起,perf hit 慢了大约 x3。当结构包含大小为 8 或更大的成员时,这可能会在 32 位计算机上发生。所以很长, double 或十进制。不仅成员的对齐很重要,结构在内存中的分配位置也很重要。这在x86版本的.NET上有点问题,对于从栈或者GC堆分配的数据,只能保证起始地址对齐到4的倍数。对于 x64 不是问题。对于您的结构来说不是问题,它只包含永远不会跨越 cpu 缓存行的小成员。
因此,根据这些规则,您无需提供帮助,即使没有 LayoutKind.Explicit,该结构也已经是最优的。
还有一个考虑因素适用,与对齐无关。 short 不是 32 位或 64 位处理器的最佳数据类型。如果您做任何超出简单加载和存储的操作,则需要额外的开销才能将其从 16 位转换为 32 位。那背后的背景故事is here .您现在需要在更好的 CPU 缓存使用与效率更低的操作之间取得平衡,而这只能通过分析器可靠地完成。
关于c# - 结构布局优化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13609515/