如何在 F# 中优化这段代码的速度以及为什么一个部分执行两次?
How to optimize this code for speed, in F# and also why is a part executed twice?
该代码用于打包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
这些数组通过网络发送,因此我需要数据尽可能小,但由于这是同时跟踪大约 80 种可交易工具,因此性能也很重要。
在客户端不获取历史数据然后更新的情况下进行权衡,但只是逐分钟获取最近 3 天的数据块,导致一遍又一遍地发送相同的数据以简化客户端逻辑..我继承了这个问题使低效的设计..尽可能高效。这也是通过休息轮询完成的,我现在正在将其转换为套接字以保持所有二进制文件。
所以我的第一个问题是:
我怎样才能让它更快?在 C 中,你可以将任何东西转换成任何东西,我可以只取一个浮点数并将它直接写入数组,所以没有什么比这更快的了,但在 F# 中,看起来我需要跳过箍,获取字节,然后将它们复制一个一个而不是 4 乘 4,等等。有更好的方法吗?
我的第二个问题是,由于要计算一次,所以我将 ToByteArray 设置为 属性。我正在 Jupyter Notebook 中使用随机值进行一些测试,但随后我看到:
属性 似乎被执行了两次(由两个“!!”行表示)。这是为什么?
我的第一个问题是,为什么需要 ToByteArray
操作?在评论中,您说您正在通过网络发送这些值的数组,所以我假设您计划将数据转换为字节数组,以便您可以将其写入网络流。
我认为使用一种采用 StreamWriter
并将数据直接写入流的方法会更有效(也更容易):
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.WriteTo(sw:StreamWriter) =
sw.Write(this.H)
sw.Write(this.L)
sw.Write(this.C)
sw.Write(this.V)
如果您现在有一些用于网络通信的代码,这将公开一个流,您将需要写入该流。假设这是 stream
,你可以这样做:
use writer = new StreamWriter(stream)
for a in packedCandles do a.WriteTo(writer)
关于你的第二个问题,我认为没有更完整的代码示例无法回答。
假设您有要写入的数组(通常在使用套接字时您应该使用缓冲区进行读写),您可以使用 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
用法
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>()
但是你真的想弄乱指针(甚至管理)吗?测出这段代码是瓶颈?
更新
最好在运行时使用 MemoryMarshal
instead of raw Unsafe
because first checks out-of-range and enforces usage of unmanaged (see here or here) 类型
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))
该代码用于打包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
这些数组通过网络发送,因此我需要数据尽可能小,但由于这是同时跟踪大约 80 种可交易工具,因此性能也很重要。 在客户端不获取历史数据然后更新的情况下进行权衡,但只是逐分钟获取最近 3 天的数据块,导致一遍又一遍地发送相同的数据以简化客户端逻辑..我继承了这个问题使低效的设计..尽可能高效。这也是通过休息轮询完成的,我现在正在将其转换为套接字以保持所有二进制文件。
所以我的第一个问题是: 我怎样才能让它更快?在 C 中,你可以将任何东西转换成任何东西,我可以只取一个浮点数并将它直接写入数组,所以没有什么比这更快的了,但在 F# 中,看起来我需要跳过箍,获取字节,然后将它们复制一个一个而不是 4 乘 4,等等。有更好的方法吗?
我的第二个问题是,由于要计算一次,所以我将 ToByteArray 设置为 属性。我正在 Jupyter Notebook 中使用随机值进行一些测试,但随后我看到:
属性 似乎被执行了两次(由两个“!!”行表示)。这是为什么?
我的第一个问题是,为什么需要 ToByteArray
操作?在评论中,您说您正在通过网络发送这些值的数组,所以我假设您计划将数据转换为字节数组,以便您可以将其写入网络流。
我认为使用一种采用 StreamWriter
并将数据直接写入流的方法会更有效(也更容易):
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.WriteTo(sw:StreamWriter) =
sw.Write(this.H)
sw.Write(this.L)
sw.Write(this.C)
sw.Write(this.V)
如果您现在有一些用于网络通信的代码,这将公开一个流,您将需要写入该流。假设这是 stream
,你可以这样做:
use writer = new StreamWriter(stream)
for a in packedCandles do a.WriteTo(writer)
关于你的第二个问题,我认为没有更完整的代码示例无法回答。
假设您有要写入的数组(通常在使用套接字时您应该使用缓冲区进行读写),您可以使用 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
用法
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>()
但是你真的想弄乱指针(甚至管理)吗?测出这段代码是瓶颈?
更新
最好在运行时使用 MemoryMarshal
instead of raw Unsafe
because first checks out-of-range and enforces usage of unmanaged (see here or here) 类型
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))