3

字节优化给你带来了多大的性能提升(使它们成为 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
4

2 回答 2

13

我假设您应用了 [MarshalAs] 属性来使数组成为 ByValArray,这对这样的结构才有意义。实际上,通过使结构变大 2 个字节,它会变慢。这将使用处理器的缓存效率较低,当您在数组中使用它们时,更少的结构将适合它们,这对性能非常重要。

默认的 StructLayoutAttribute.Pack 值 8 已经过优化,可以提供最佳的结构布局。它实际上对您的结构没有任何影响,无论 Pack 值如何,成员已经最佳对齐。任何现代处理器获得最佳性能的规则:

  • 成员应与可被成员大小整除的地址对齐。这可能会在成员之间添加填充字节。此规则防止处理器必须从内存读取中复用字节值或执行两次读取并将字节粘合在一起。在您的结构上不是问题,唯一需要对齐的成员是 mUnitType,它必须在 2 处对齐,并且已经在 4 处对齐。另外请注意,您不必使用 [FieldOffset],默认布局已经很好.

  • 在数组中使用结构时,成员应该正确对齐。这可能会将包装添加到结构的末尾,以使数组中的下一个元素正确对齐。同样不是你的结构的问题,它有 6 个字节长,因此数组中的下一个元素将对齐其 mUnitType,因为它只需要 2 个。如果你实际上声明了没有 [MarshalAs] 的数组,那么抖动将自动添加 2 个字节在没有您帮助的情况下进行填充以确保数组指针正确对齐。

  • 成员不应跨越 cpu 缓存行。在我知道的任何现代处理器上都是 64 字节。对性能非常不利,cpu 必须读取两个缓存行的数据并始终将字节粘合在一起,性能命中大约慢 x3。当结构包含大小为 8 或更大的成员时,这可能会在 32 位机器上发生。所以长,双或小数。不仅成员的对齐方式很重要,结构在内存中的分配位置也很重要。这在 x86 版本的 .NET 上有点问题,它只能保证从堆栈或 GC 堆分配的数据的起始地址对齐为 4 的倍数。对于 x64 来说不是问题。这对您的结构来说不是问题,它只包含永远不会跨越 cpu 缓存线的小成员。

因此,根据这些规则,您不必提供帮助,即使没有 LayoutKind.Explicit,该结构也已经是最佳的。

另一个考虑因素适用,与对齐没有任何关系。对于32 位或 64 位处理器, short不是最佳数据类型。如果您执行的操作超出了简单的加载和存储,则需要额外的开销将其从 16 位转换为 32 位。那个背后的背景故事就在这里。您现在需要平衡更好的 CPU 缓存使用率和效率较低的操作,而您只能使用分析器可靠地做到这一点。

于 2012-11-28T17:40:41.363 回答
2

这并不像您认为的那样起作用。

[StructLayout(LayoutKind.Explicit)]
public struct RenderItem
{
   [FieldOffset(0)] byte[] mCoordinates = new byte[3]; //(x,y,z)
   [FieldOffset(3)] short  mUnitType;            

}

数组是一种引用类型。的存储要求mCoordinates是 IntPtr.Size(即 x86 上为 4 个字节,x64 上为 8 个字节)。3 个字节的元素存储在堆上。

我不知道如果您 FieldOffset 与这样的参考重叠,可能(或可能不会)发生什么坏事。

如果您需要具有该精确布局的结构,则需要创建另一个值类型

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Coordinate
{
    byte x;
    byte y;
    byte z;
};


[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct RenderItem
{
   Coordinate coord;
   short  mUnitType;            

}

这不能回答您关于对齐的问题,但 Matthew 提供的链接可以。

于 2012-11-28T17:07:21.777 回答