C#并集和数组副本
本文关键字:数组 副本 | 更新日期: 2023-09-27 18:21:30
我正试图以最快的方式将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
数组,我无法通过与以前相同的方法将其转换为ArraySegment[]
,因此我的比较毫无意义)。
如果结构完全相同,则可以通过使用Marshal.PtrToStructure方法来实现。
您需要获得一个指向结构的指针,然后可以将其"反序列化"回另一个结构(该结构应该具有完全相同的布局)。
你可以在这里看到一个例子。
希望这能有所帮助,Ofir。
您是否意识到您不安全地将Struct1[]
视为Struct2[]
,从而破坏了类型系统?这会使CLR处于未定义状态。可以假设Struct1[]
类型的变量确实指向Struct1[]
的实例。你现在几乎可以看到任何奇怪的行为。(这不是安全问题。此代码不可验证,需要完全信任。)
换句话说,您没有转换数组内容,而是获得了转换后的对象引用。
通常使用memcpy
以最快的方式复制一个可blitable对象数组。手动复制循环相当于此,但我不相信JIT会将其优化为memcpy
。JIT只执行当前版本的基本优化。
此代码故意不安全(由于您想要做的是不安全的,AFAIK CLR/JIT可以出于性能原因重新排序结构)
还要注意,MemCpy的签名可能会根据框架版本而更改(毕竟是内部的)
出于性能原因,您应该正确缓存该委托
这个问题的想法在这里
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);
}
}
}
}
警告: 这可能是一个危险的技巧,因为它确实绕过了类型系统,并且程序集是不可验证的。尽管如此,一些肤浅的测试并没有引起任何明显的问题,对于你的"实验"来说,这可能值得一试。不过,请查看@usr在评论中的警告
在您的假设下(如果您可以容忍不可验证的输出程序集),您根本不需要Marshal.XXX
、Array.Copy
或memcpy
。可以将联合类型中的值读取为Struct1
数组或Struct2
数组。我的猜测是,尽管我没有证据支持它,但运行时和GC不会注意到数组类型和使用元素的方式之间的差异。
下面是一个将在LinqPad中运行的独立示例。默认的打包意味着您实际上不需要Struct1和Struct2中的LayoutKind
和FieldOffset
注释(当然您在联合类型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();
}