2013-07-06 25 views
4

我想尽可能以最快的方式将数组Struct1复制到Struct2(相同的二进制表示)数组。 我已经定义了一个联合在Struct1[]Struct2[]之间转换,但是当我调用Array.Copy时,我得到一个异常,说数组是错误的类型。我怎样才能绕过这个? Buffer.BlockCopy只接受原始类型。 下面是代码:C#工会和阵列副本

[StructLayout(LayoutKind.Explicit)] 
public struct Struct12Converter 
{ 
    [FieldOffset(0)] 
    public Struct1[] S1Array; 
    [FieldOffset(0)] 
    public Struct2[] S2Array; 
} 

public void ConversionTest() 
{ 
    var s1Array = new{new Struct1()} 
    var converter = new Struct12Converter{S1Array = s1Array}; 
    var s2Array = new Struct2[1]; 
    Array.Copy(converter.S2Array,0,s2Array,0,1) //throws here 
    //if you check with the debugger, it says converter.S2Array is a Struct1[], 
    //although the compiler lets you use it like a Struct2[] 
    //this has me baffled as well. 
} 

透露更多的细节: 我想尝试看看是否有工作一个可变的结构,并改变其字段的值都与使用所有的工作时间不同的性能特点相同的不可变结构。我认为它应该是相似的,但我认为这是值得的测量。底层应用程序将是一个低延迟套接字库,我目前使用基于ArraySegment<byte>的套接字API。恰巧在SocketAsyncEventArgs api中,设置BufferList属性会触发一个数组副本,这是我的“实验”失败的地方(我有一个MutableArraySegment的数组,我无法通过与以前相同的方法将其转换为ArraySegment[],从而使我的比较毫无意义)。

+4

你能不能创建一个'Struct3'这实际上是Struct1'和'Struct2'的'工会?然后你可以创建一个'Struct3 []'开始,分配所有'Struct1'值,然后读出'Struct2'值。 –

+1

如果你可以给更多的上下文,那也会有帮助。主要的问题是数组对象知道它的具体类型。 'Struct1 []'*不是'Struct2 []',反之亦然。有可能你可以使用指针来分类......我们知道得越多,我们就可以提供更多帮助。 –

+0

我可以使用Struct3,但我想复制数组而不迭代它(低延迟应用程序),并且我测量了Array.Copy方法比简单的迭代/赋值循环(对于特定大小的数组)更快, – JJ15k

回答

-1

警告:这可能是一个危险的伎俩,因为它确实规避了类型系统,并且程序集不可验证。尽管如此 - 一些肤浅的测试并没有引起任何明显的问题,而对于你的“实验”来说,这可能是值得一试的。通过@usr在评论检查通过警告,虽然...

在你的假设(如果你能容忍一个无法证实的输出组件),你不需要Marshal.XXXArray.Copymemcpy的。您可以从联合类型中读取数值作为Struct1数组或Struct2数组。我的猜测,尽管我没有证据可以支持它,但是运行时和GC不会注意到数组类型和使用元素的方式之间的差异。

这是一个将在LinqPad中运行的独立示例。默认包装意味着您实际上不需要Struct1和Struct2中的LayoutKindFieldOffset注释(尽管当然您在联合类型Struct12Converter中执行),但它有助于明确显示。

[StructLayout(LayoutKind.Explicit)] 
public struct Struct1 
{ 
    [FieldOffset(0)] 
    public int Int1; 
    [FieldOffset(4)] 
    public int Int2; 
} 

[StructLayout(LayoutKind.Explicit)] 
public struct Struct2 
{ 
    [FieldOffset(0)] 
    public long Long; 
} 

[StructLayout(LayoutKind.Explicit)] 
public struct Struct12Converter 
{ 
    [FieldOffset(0)] 
    public Struct1[] S1Array; 
    [FieldOffset(0)] 
    public Struct2[] S2Array; 
} 


public void ConversionTest() 
{ 
    var int1 = 987; 
    var int2 = 456; 
    var int3 = 123456; 
    var int4 = 789123; 

    var s1Array = new[] 
    { 
     new Struct1 {Int1 = int1, Int2 = int2}, 
     new Struct1 {Int1 = int3, Int2 = int4}, 
    }; 

    // Write as Struct1s 
    var converter = new Struct12Converter { S1Array = s1Array }; 

    // Read as Struct2s 
    var s2Array = converter.S2Array; 

    // Check: Int2 is the high part, so that must shift up 
    var check0 = ((long)int2 << 32) + int1; 
    Debug.Assert(check0 == s2Array[0].Long); 
    // And check the second element 
    var check1 = ((long)int4 << 32) + int3; 
    Debug.Assert(check1 == s2Array[1].Long); 

    // Using LinqPad Dump: 
    check0.Dump(); 
    s2Array[0].Dump(); 

    check1.Dump(); 
    s2Array[1].Dump(); 

} 

void Main() 
{ 
    ConversionTest(); 
} 
+0

我不同意miscasting的对象引用是安全的(暗示你说这是一个标准的把戏)。联合*结构的单个实例*是安全的,但如果您打破了类型系统,则CLR可能表现不正常。对象引用不仅仅是像C中的指针,它是CLR信任的。 – usr

+0

您能举一个例子说明这可能会如何行为不当,或者如何显示类型系统危险? – Govert

+0

强制将S1投射到S2并调用GetType。 JIT可以对返回值进行硬编码,因为它是静态已知的。除此之外,这现在返回错误的结果。这是我能想到的最无害的例子。什么关于GC崩溃?!毕竟,GC会得到意想不到的对象类型。你确信CLR中的* nothing *依赖于静态类型是准确的吗?很多*依赖于此。 (更新:我现在低估了,因为我真的认为这不是一个好主意。) – usr

0

如果Structs完全相同,则可以使用Marshal.PtrToStructure方法完成此操作。你需要获得一个指向你的结构体的指针,然后你可以“反序列化”它返回到另一个结构体(它应该有相同的布局)。

您可以看到一个示例here

希望这会有帮助, Ofir。

0

您是否知道您通过将Struct1[]作为Struct2[]不安全地处理了类型系统?这将CLR置于未定义状态。它可以假设Struct1[]类型的变量确实指向Struct1[]的实例。你现在可以看到几乎任何奇怪的行为。 (这不是安全问题,该代码不可验证,并且需要完全信任)。

换句话说,您没有转换数组内容,但获得了转换的对象引用。

使用memcpy通常以最快的方式完成复制可擦除对象数组。手动复制循环等同于此,但我不相信JIT将其优化为memcpy。 JIT仅对当前版本进行基本优化。

+0

我的目标是避免任何记忆复制 – JJ15k

0

此代码是故意不安全的(因为你想做的事是不安全的,据我所知的CLR/JIT可以重新排序因为性能原因结构)

另请注意,memcpy的签名可以根据改变在框架的版本(它毕竟是内部)

出于性能方面的考虑,您应缓存代表正确

Idea from this question here

unsafe delegate void MemCpyImpl(byte* src, byte* dest, int len); 

    static MemCpyImpl memcpyimpl; 

    public unsafe static void Copy(void* src, void* dst, int count) 
    { 
     byte* source = (byte*)src; 
     byte* dest = (byte*)dst; 
     memcpyimpl(source, dest, count); 
    } 

然后迫使你的数组​​为字节数组(实际上是无效*,但心中永远的细节)

public static void ConversionTest() 
    { 
     var bufferType = typeof(Buffer); 

     unsafe 
     { 
      var paramList = new Type[3] { typeof(byte*), typeof(byte*), typeof(int) }; 
      var memcpyimplMethod = bufferType.GetMethod("Memcpy", BindingFlags.Static | BindingFlags.NonPublic, null, paramList, null); 

      memcpyimpl = (MemCpyImpl)Delegate.CreateDelegate(typeof(MemCpyImpl), memcpyimplMethod); 
     } 

     Struct1[] s1Array = { new Struct1() { value = 123456789 } }; 
     var converter = new Struct12Converter { S1Array = s1Array }; 
     var s2Array = new Struct2[1]; 
     unsafe 
     { 
      fixed (void* bad = s2Array) 
      { 
       fixed (void* idea = converter.S2Array) 
       { 
        Copy(bad, idea, 4); 
       } 
      } 
     } 
    } 
+0

LayoutKind.Explicit与FieldOffset显式确定托管内存布局以及(对于blittable类型),所以CLR/JIT无法重新排序这些结构成员。 – Govert