4

我正在尝试以最快的方式将数组复制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。碰巧在SocketAsyncEventArgsapi中,设置BufferList属性会触发一个数组副本,这是我的“实验”失败的地方(我有一个MutableArraySegment无法ArraySegment[]通过与以前相同的方法转换为的数组,因此我的比较毫无意义)。

4

4 回答 4

0

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

您需要获得一个指向您的结构的指针,然后您可以将其“反序列化”回另一个结构(应该具有完全相同的布局)。

你可以在这里看到一个例子。

希望这会有所帮助,Ofir。

于 2014-06-06T10:41:37.260 回答
0

您是否知道您通过不安全地将 aStruct1[]视为 a来破坏类型系统Struct2[]?这会将 CLR 置于未定义状态。它可以假设类型变量Struct1[]确实指向Struct1[]. 您现在几乎可以看到任何奇怪的行为。(这不是安全问题。此代码不可验证,需要完全信任。)

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

复制 blittable 对象数组通常使用memcpy. 手动复制循环与此等效,但我不相信 JIT 会将其优化为memcpy. JIT 仅对当前版本进行基本优化。

于 2014-06-06T11:44:56.217 回答
0

这段代码是故意不安全的(因为你想做的是不安全的,AFAIK CLR/JIT 可以出于性能原因重新排序结构)

另请注意,MemCpy 的签名可能会根据框架版本而改变(毕竟它是内部的)

出于性能原因,您应该正确缓存该委托

来自这个问题的想法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);
    }

然后强制你的数组是字节数组(实际上是 void* 但不要介意细节)

    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);
                }
            }
        }
    }
于 2014-06-06T13:40:27.717 回答
-1

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

根据您的假设(如果您可以容忍无法验证的输出程序集),您不需要Marshal.XXXArray.Copy或者根本不需要memcpy。您可以从联合类型中读取值作为Struct1数组或Struct2数组。我的猜测是,虽然我没有证据支持它,但运行时和 GC 不会注意到数组类型和您使用元素的方式之间的差异。

这是一个将在 LinqPad 中运行的独立示例。默认打包意味着您实际上不需要 Struct1 和 Struct2 中的LayoutKindandFieldOffset注释(当然您在 union type 中需要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();
}
于 2014-06-06T12:29:54.837 回答