C#Unions和数组副本

时间:2013-07-06 13:05:23

标签: c# arrays performance struct union

我试图以最快的方式将Struct1数组复制到Struct2(相同的二进制表示)数组中。 我已经定义了一个在Struct1[]Struct2[]之间转换的联合,但是当我调用Array.Copy时,我得到一个异常,说该数组是错误的类型。我怎么能绕过那个? Buffer.BlockCopy只接受原始类型。 这是代码:

[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数组,我无法将其转换为{ {1}}通过与之前相同的方法,从而使我的比较毫无意义。)

4 个答案:

答案 0 :(得分:0)

如果Structs完全相同,您可以使用Marshal.PtrToStructure方法完成此操作。

你需要获得一个指向你的结构的指针,然后你可以&#34;反序列化&#34;它回到另一个结构(它应该具有完全相同的布局)。

您可以看到示例here

希望这有帮助, 奥菲尔。

答案 1 :(得分:0)

您是否意识到您已将Struct1[]作为Struct2[]进行不安全处理,从而打破了类型系统?这使CLR进入未定义状态。它可以假设Struct1[]类型的变量确实指向Struct1[]的实例。你现在几乎可以看到任何奇怪的行为。 (这不是安全问题。此代码不可验证,需要完全信任。)

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

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

答案 2 :(得分:0)

此代码故意不安全(因为您要做的是不安全的,AFAIK 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);
                }
            }
        }
    }

答案 3 :(得分:-1)

警告: 这可能是一个危险的伎俩,因为它确实绕过了类型系统,并且程序集无法验证。仍然 - 一些肤浅的测试并没有引发任何明显的问题,而且对于你的实验也是如此。它可能值得一去。请查看评论中@usr的警告,但是......

根据您的假设(如果您可以容忍无法验证的输出程序集),您根本不需要Marshal.XXXArray.Copymemcpy。您可以将union类型中的值读取为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();
}