F#中的不安全转换,零复制语义

时间:2015-11-26 13:59:30

标签: f#

我正在尝试实现像强制一样的静态强制转换,不会导致复制任何数据。 天真的静态演员不起作用

let pkt = byte_buffer :> PktHeader
  

FS0193:类型约束不匹配。类型byte []与类型PktHeader不兼容'byte []'类型与'PktHeader'(FS0193)(程序)类型不兼容

由于System.Net.Sockets.Socket.Receive()的定义方式,数据包最初保存在字节数组中。 低级数据包结构定义类似于

[<Struct; StructLayout(LayoutKind.Explicit)>]
type PktHeader =
  [<FieldOffset(0)>] val mutable field1: uint16
  [<FieldOffset(2)>] val mutable field2: uint16
  [<FieldOffset(4)>] val mutable field3: uint32
   .... many more fields follow ....

效率在这个真实场景中很重要,因为浪费的数据复制可能会排除F#作为实现语言。 在这种情况下,您如何实现零拷贝效率?

11月29日编辑 我的问题是基于一种隐含的信念,即C / C ++ / C#风格的不安全静态强制转换是一种有用的结构,就好像这是不言而喻的。然而,在第二个想法中,这种演员在F#中并不是惯用的,因为它本质上是一种充满危险的必要语言技术。出于这个原因,我接受了V.B.的回答。其中SBE / FlatBuffers数据访问是最佳实践。

2 个答案:

答案 0 :(得分:2)

转换的纯F#方法

let convertByteArrayToStruct<'a when 'a : struct> (byteArr : byte[]) = 
    let handle = GCHandle.Alloc(byteArr, GCHandleType.Pinned)
    let structure = Marshal.PtrToStructure (handle.AddrOfPinnedObject(), typeof<'a>)
    handle.Free()
    structure :?> 'a

这是一个最小的例子,但是我建议对字节数组的长度进行一些检查,因为,如果你在那里写的话,如果你给它一个太短的字节数组,它将产生未定义的结果。您可以查看Marshall.SizeOf(typeof<'a>)

没有纯F#解决方案来进行比这更安全的转换(这已经是一种容易导致运行时故障的方法)。替代选项可以包括与C#互操作以使用unsafefixed进行转换。

但最终,你要求一种颠覆F#类型系统的方法,而这种系统实际上并不是该语言的设计目标。 F#的一个主要优点是类型系统的强大功能,它能够帮助您生成可静态验证的代码。

答案 1 :(得分:0)

F#和非常低级别的性能优化并不是最好的朋友,但是......一些聪明的人即使使用Java也没有魔力,因为Java没有价值类型和真正的通用集合。

1)我最近很喜欢轻量级模式。如果您的体系结构允许它,您可以包装一个字节数组并通过偏移访问struct成员。 C#示例here。 SBE / FlatBuffers甚至可以使用工具从定义中自动生成包装器。

2)如果您可以在C#中处于不安全的上下文中来完成工作,那么指针转换非常简单有效。但是,这需要固定字节数组并保留其句柄以供以后释放,或保留在固定关键字内。如果你有许多没有游泳池的小游戏,你可能会遇到GC问题。

3)第三个选项是滥用.NET类型系统并使用IL this转换一个字节数组(如果你坚持的话,这可以用F#编码)):

static T UnsafeCast(object value) {
 ldarg.1 //load type object
 ret //return type T
}

我尝试过这个选项,如果你需要,甚至可以在某个地方使用一个片段,但是这种方法让我感到不舒服,因为我不理解它对GC的影响。我们有两个由相同内存支持的对象,当其中一个是GCed时会发生什么?我打算就这个细节问一个关于SO的新问题,很快就会发布。

最后一种方法可能适用于结构数组,但对于单个结构,它会将其打包或复制它。由于结构体位于堆栈上并通过值传递,因此只需在不安全的C#中使用指针byte[]或在此处使用Marshal.PtrToStructure作为另一个答案,然后按值复制,就可能获得更好的结果。复制并不是最糟糕的事情,特别是在堆栈上,但是新对象和GC的分配是敌人,所以你需要池化字节数组,这将比整体构建问题增加更多的整体性能。

但如果您的结构非常大,则选项1仍可能更好。