寻找处理具有重叠字段的结构的构造函数的最佳方法

本文关键字:结构 构造函数 最佳 方法 字段 处理 重叠 寻找 | 更新日期: 2023-09-27 18:16:14

我已经创建了一个自定义结构来处理将被封送到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;
}

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

是否有一种方法使编译器认识到这些字段重叠,不需要显式地分配R,G,B和a,如果packkedvalue被分配,反之亦然?我假设这可以通过手动调整生成的IL来完成,但我想知道是否有一种方法可以更优化地直接在c#中处理这个问题。

任何想法?

寻找处理具有重叠字段的结构的构造函数的最佳方法

从这里:

Struct成员自动初始化为其默认值。因此,不需要在任何构造函数中将它们初始化为默认值。

然而,这并不适用于您的情况。它仅适用于非重叠字段,并且仅在使用默认构造函数时有效。无论如何,请参阅答案的最后一部分,了解基于此的另一种选择。

查看单参数构造函数的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的建议之后,在jit之后看起来是一样的(这也是发布模式,成员单独分配):

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;

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