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

5 f#

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

let pkt = byte_buffer :> PktHeader
Run Code Online (Sandbox Code Playgroud)

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 ....
Run Code Online (Sandbox Code Playgroud)

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

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

V.B*_*.B. 0

F# 和非常低级的性能优化并不是最好的朋友,但是……一些聪明的人甚至使用 Java 也能创造奇迹,因为 Java 没有值类型和真正的泛型集合。

1) 我最近非常喜欢蝇量级模式。如果您的架构允许,您可以包装字节数组并通过偏移量访问结构成员。C# 示例在这里。SBE/FlatBuffers 甚至具有根据定义自动生成包装器的工具。

2) 如果您可以在 C# 中的不安全上下文中完成工作,则指针转换非常简单且高效。但是,这需要固定字节数组并保留其句柄以供以后发布,或者保留在固定关键字内。如果您有许多没有池的小进程,则 GC 可能会出现问题。

3) 第三个选项是滥用 .NET 类型系统并使用 IL 转换字节数组,如下所示如果您坚持的话,可以用 F# 进行编码:)):

static T UnsafeCast(object value) {
 ldarg.1 //load type object
 ret //return type T
}
Run Code Online (Sandbox Code Playgroud)

我尝试过这个选项,如果你需要的话,甚至在某个地方有一个片段,但这种方法让我感到不舒服,因为我不明白它对 GC 的后果。我们有两个由相同内存支持的对象,当其中一个对象被 GC 时会发生什么?我打算就这个细节提出一个新问题,很快就会发布。


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

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