【问题标题】:Seeking optimal way to handle constructors of struct with overlapping fields寻求处理具有重叠字段的结构构造函数的最佳方法
【发布时间】:2014-08-01 21:58:47
【问题描述】:

我创建了一个自定义结构来处理将被编组到 GPU 的 RGBA 值。

在我的类型中,我将单独的 R、G、B 和 A 组件保存为字节值,并重叠一个 32 位无符号整数 (Uint32) 以轻松传递和分配打包值。我知道这个概念很明显,但这里有一个很好的结构示例:

[StructLayout(LayoutKind.Explicit, Size = 4)]
public struct RGBA
{
    [FieldOffset(0)]
    public uint32 PackedValue;

    [FieldOffset(0)]
    public byte R;

    [FieldOffset(1)]
    public byte G;

    [FieldOffset(2)]
    public byte B;

    [FieldOffset(3)]
    public byte A;
}

由于 c# 处理结构的方式,每个字段都必须在定义的任何构造函数中显式分配。就我而言,这意味着由于字段重叠,我必须在任何构造函数中分配两次值。

我可以使用:

public RGBA(uint packed value)
{
    R = G = B = A = 0;    //  initialize all to defaults before assigning packed value
    PackedValue = packedValue;
}

public RGBA(byte r, byte g, byte b, byte a)
{
    PackedValue = 0;    // initialize to default before assigning components
    R = r;
    G = g;
    B = b;
    A = a;
}

或者我可以像这样首先在每个构造函数上调用基本构造函数:

public RGBA(uint packedValue) : this()
{
    PackedValue = packedValue;
}

public RGBA(byte r, byte g, byte b, byte a) : this()
{
    R = r;
    G = g;
    B = b;
    A = a;
}

由于这是用于图形代码,因此性能至关重要,我正在尝试找到在这种情况下处理构建的最佳方法。使用第一个示例似乎是两个示例中开销最小的,因为虽然它涉及分配所有字段两次(一次用于 PackedValue,一次用于 R、G、B 和 A 字段),但另一个示例涉及分配所有值 3 次(两次在默认构造函数中,一次在定义的构造函数中)。

有没有办法让编译器认识到这些字段重叠并且如果 PackedValue 被分配,则不需要明确分配 R、G、B 和 A,反之亦然?我假设这可以通过手动调整生成的 IL 来完成,但我想知道是否有办法直接在 c# 中更优化地处理这个问题。

有什么想法吗?

【问题讨论】:

  • 你能使用属性来避免双重初始化吗?例如。 public byte A { get { return (byte)(PackedValue >> 24); } }?
  • 好问题。 . .您是否尝试过任何基准测试?也许编译器足够聪明,可以忽略一些无用的调用。
  • 我想我可以对打包值使用按位操作,这样就不必分配 R、G、B 和 A 字段。 get/set 代码增加的开销是不希望的,但如果编译器不能很好地运行,我可能不得不这样做。不确定它在 GPU 上的行为,但一旦编组
  • 您必须对此进行基准测试。尚不清楚 JIT 的作用。 CLR JIT 并不擅长优化东西。查看已开启优化的 Release 模式下的反汇编。

标签: c# memory data-structures constructor


【解决方案1】:

来自here

结构成员会自动初始化为其默认值。因此,无需在任一构造函数中将它们中的任何一个初始化为其默认值。

但是,这不适用于您的情况。它仅适用于非重叠字段,并且仅在您使用默认构造函数时才有效。无论如何,请参阅答案的最后一部分以获取基于此的替代方案。

查看单参数构造函数的 IL 代码,我们可以看到编译器什么都不做(没有优化,这是默认设置的发布模式):

.method public hidebysig specialname rtspecialname 
        instance void  .ctor(uint32 packedValue) cil managed
{
  // Code size       42 (0x2a)
  .maxstack  6
  .locals init ([0] uint8 CS$0$0000,
           [1] uint8 CS$0$0001,
           [2] uint8 CS$0$0002)
  IL_0000:  ldarg.0
  IL_0001:  ldarg.0
  IL_0002:  ldarg.0
  IL_0003:  ldarg.0
  IL_0004:  ldc.i4.0
  IL_0005:  dup
  IL_0006:  stloc.0
  IL_0007:  stfld      uint8 ConsoleApplication2.Program/RGBA::A
  IL_000c:  ldloc.0
  IL_000d:  dup
  IL_000e:  stloc.1
  IL_000f:  stfld      uint8 ConsoleApplication2.Program/RGBA::B
  IL_0014:  ldloc.1
  IL_0015:  dup
  IL_0016:  stloc.2
  IL_0017:  stfld      uint8 ConsoleApplication2.Program/RGBA::G
  IL_001c:  ldloc.2
  IL_001d:  stfld      uint8 ConsoleApplication2.Program/RGBA::R
  IL_0022:  ldarg.0
  IL_0023:  ldarg.1
  IL_0024:  stfld      uint32 ConsoleApplication2.Program/RGBA::PackedValue
  IL_0029:  ret
} // end of method RGBA::.ctor

经过@usr的建议,jitting后看起来一样(这也是发布模式,成员单独分配):

007400B5  call        750586FE  
007400BA  mov         eax,dword ptr [ebp-8]  
007400BD  mov         byte ptr [eax],0  
                G = 0;
007400C0  mov         eax,dword ptr [ebp-8]  
007400C3  mov         byte ptr [eax+1],0  
                B = 0; 
007400C7  mov         eax,dword ptr [ebp-8]  
007400CA  mov         byte ptr [eax+2],0  
                A = 0;
007400CE  mov         eax,dword ptr [ebp-8]  
007400D1  mov         byte ptr [eax+3],0  
                PackedValue = packedValue;
007400D5  mov         eax,dword ptr [ebp-8]  
007400D8  mov         edx,dword ptr [ebp-4]  
007400DB  mov         dword ptr [eax],edx  

也许对它进行基准测试是现在最好的方法。或者,使用默认构造函数并在获得结构实例后手动分配PackedValue。在这种情况下,文章中描述的默认行为将适用。

var rgba = new RGBA { PackedValue = 2556 };

var rgba = new RGBA();
rgba.PackedValue = 2556;

【讨论】:

  • @marceln 同意。在真正研究了问题之后,我认为基准测试和取其较小的弊端是这里的唯一方法。这是在 c# 中模拟联合的权衡,我想哈哈。由于它是用于编组到本机图形 api,因此这些函数通常提供接受打包整数的重载,所以我猜在我的情况下,组件字段可以被视为托管端的语法糖。
  • 谢谢你顺便提供那个IL。
  • @MikeJohnson:没问题。
【解决方案2】:

我遇到了这个问题,最终使用不安全的代码将我的 Color 结构转换为 32 位整数并返回。不确定这是否适合您。

【讨论】:

    猜你喜欢
    • 2023-03-25
    • 2010-10-09
    • 2019-04-11
    • 1970-01-01
    • 2016-07-25
    • 2019-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多