【问题标题】:structure layout optimization结构布局优化
【发布时间】:2012-11-29 22:24:16
【问题描述】:

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

【问题讨论】:

  • 查看alexonlinux.com/aligned-vs-unaligned-memory-access。另外,如果你不指定FieldOffset,编译器会自动为你对齐结构。
  • 它会自动对齐它们,但它会保证它们的顺序吗?我在几个地方看到它不能保证(因为它要进入 OpenGL,所以它的发送顺序很重要)
  • 和哇,这是一个巨大的性能差异。

标签: c# opengl optimization structure


【解决方案1】:

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

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

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

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

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

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

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

【讨论】:

  • 我知道欢呼是不赞成的,但答案很好。
【解决方案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 提供的链接可以。

【讨论】:

  • 但是,从 C#2 开始,我们可以定义一个“不安全”结构并在数组字段上使用“固定”关键字,长度在编译时定义。这会产生一个包含数组元素/数据的结构,而不是数组对象的引用。
猜你喜欢
  • 2016-04-01
  • 1970-01-01
  • 2014-12-17
  • 2012-12-21
  • 2014-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多