如何优化此代码以提高速度,在 F# 中以及为什么部分执行两次?

Tho*_*mas 3 optimization f#

代码用于将历史财务数据打包成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 中用随机值做一些测试,但后来我看到:

在此处输入图片说明

该属性似乎执行了两次(由两个“!!”行表示)。这是为什么?

JL0*_*0PD 5

假设您有要写入的数组(通常在使用套接字时应该使用缓冲区进行读写),您可以使用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)