代码用于将历史财务数据打包成16个字节:
type PackedCandle =
struct
val H: single
val L: single
val C: single
val V: int
end
new(h: single, l: single, c: single, v: int) = { H = h; L = l; C = c; V = v }
member this.ToByteArray =
let a = Array.create 16 (byte 0)
let h = BitConverter.GetBytes(this.H)
let l = BitConverter.GetBytes(this.L)
let c = BitConverter.GetBytes(this.C)
let v = BitConverter.GetBytes(this.V)
a.[00] <- h.[0]; a.[01] <- h.[1]; a.[02] <- h.[2]; a.[03] <- h.[3]
a.[04] <- l.[0]; a.[05] <- l.[1]; a.[06] <- l.[2]; a.[07] <- l.[3]
a.[08] <- c.[0]; a.[09] <- c.[1]; a.[10] <- c.[2]; a.[11] <- c.[3]
a.[12] <- v.[0]; a.[13] <- v.[1]; a.[14] <- v.[2]; a.[15] <- v.[3]
printfn "!!" <- for the second part of the question
a
Run Code Online (Sandbox Code Playgroud)
这些数组通过网络发送,因此我需要数据尽可能小,但由于这同时跟踪大约 80 种可交易工具,因此性能也很重要。在客户端不获取历史数据然后更新的情况下进行了权衡,而只是一分钟一分钟地获取过去 3 天的数据块,导致重复发送相同的数据以简化客户端逻辑......我继承了这个问题使低效设计......尽可能高效。这也是通过休息轮询完成的,我现在正在将其转换为套接字以保持所有内容为二进制。
所以我的第一个问题是:我怎样才能让它更快?在 C 中,您可以将任何内容转换为任何内容,我可以只取一个浮点数并将其直接写入数组,这样就没有什么比这更快的了,但是在 F# 中,我似乎需要跳过箍,获取字节然后将它们复制一个一个而不是 4 个 4 等等。有没有更好的方法?
我的第二个问题是,由于要评估一次,因此我将 ToByteArray 设为一个属性。我正在 Jupyter Notebook 中用随机值做一些测试,但后来我看到:
该属性似乎执行了两次(由两个“!!”行表示)。这是为什么?
假设您有要写入的数组(通常在使用套接字时应该使用缓冲区进行读写),您可以使用System.Runtime.CompilerServices.Unsafe.As<TFrom, TTo>
将内存从一种类型转换为另一种类型(与 C/C++ 可以做的事情相同)
type PackedCandle =
// omitting fields & consructor
override c.ToString() = $"%f{c.H} %f{c.L} %f{c.C} %d{c.V}" // debug purpose
static member ReadFrom(array: byte[], offset) =
// get managed(!) pointer
// cast pointer to another type
// same as *(PackedCandle*)(&array[offset]) but safe from GC
Unsafe.As<byte, PackedCandle> &array.[offset]
member c.WriteTo(array: byte[], offset: int) =
Unsafe.As<byte, PackedCandle> &array.[offset] <- c
Run Code Online (Sandbox Code Playgroud)
用法
let byteArray = Array.zeroCreate<byte> 100 // assume array come from different function
// writing
let mutable offset = 0
for i = 0 to 5 do
let candle = PackedCandle(float32 i, float32 i, float32 i, i)
candle.WriteTo(byteArray, offset)
offset <- offset + Unsafe.SizeOf<PackedCandle>() // "increment pointer"
// reading
let mutable offset = 0
for i = 0 to 5 do
let candle = PackedCandle.ReadFrom(byteArray, offset)
printfn "%O" candle
offset <- offset + Unsafe.SizeOf<PackedCandle>()
Run Code Online (Sandbox Code Playgroud)
但是你真的想弄乱指针(甚至是托管的)吗?测过这个代码是不是瓶颈?
最好使用MemoryMarshal
而不是 raw,Unsafe
因为首先检查超出范围并在运行时强制使用非托管(参见此处或此处)类型
member c.WriteTo (array: byte[], offset: int) =
MemoryMarshal.Write(array.AsSpan(offset), &Unsafe.AsRef(&c))
static member ReadFrom (array: byte[], offset: int) =
MemoryMarshal.Read<PackedCandle>(ReadOnlySpan(array).Slice(offset))
Run Code Online (Sandbox Code Playgroud)