我有以下代码:
// volume queue
let volumeQueue = Queue<float>()
let queueSize = 10 * 500 // 10 events per second, 500 seconds max
// add a signed volume to the queue
let addToVolumeQueue x =
volumeQueue.Enqueue(x)
while volumeQueue.Count > queueSize do volumeQueue.TryDequeue() |> ignore
// calculate the direction of the queue, normalized between +1 (buy) and -1 (sell)
let queueDirection length =
let subQueue =
volumeQueue
|> Seq.skip (queueSize - length)
let boughtVolume =
subQueue
|> Seq.filter (fun l -> l > 0.)
|> Seq.sum
let totalVolume =
subQueue
|> Seq.sumBy (fun l -> abs l)
2. * boughtVolume / totalVolume - 1.
它的作用是运行一个固定长度的队列,向其中添加事务量,一些是正的,一些是负的。
然后它计算正项与负项的累积比率,并将其在 +1 和 -1 之间归一化,0 表示总和为一半。
目前没有优化,但这段代码的性能很重要。所以我想让它变快,同时又不影响可读性(大约每 100 毫秒调用一次)。
首先想到的是在一个循环中同时计算两个和(正数和所有数字)。用for循环很容易搞定,但是用集合函数可以搞定吗?
我考虑的下一个选项是摆脱队列并使用循环缓冲区,但由于代码在缓冲区的一部分(最后的“长度”项)上运行,我必须处理环绕部分;我想我可以将缓冲区扩展到 2 的幂的大小并以这种方式自动环绕。
欢迎提出任何想法,但我的第一个原始问题是:我可以使用集合函数在一次传递中完成两个总和吗?我不能用索引器在队列中迭代,所以我不能使用 for 循环(或者我想我必须实例化一个迭代器)
最佳答案
首先,在 F# 中使用可变变量和循环本身并没有错。尤其是在小范围内(例如在函数内部),这通常非常可读 - 或者至少,如果有合适的注释,则很容易理解。
要使用单次迭代执行此操作,您可以使用 fold
。这基本上以牺牲一些可读性为代价在一次迭代中计算两个总和:
let queueDirectionFold length =
let boughtVolume, totalVolume =
volumeQueue
|> Seq.skip (queueSize - length)
|> Seq.fold (fun (bv, tv) v ->
(if v > 0.0 then bv else bv + v), tv + abs v) (0.0, 0.0)
2. * boughtVolume / totalVolume - 1.
正如我之前提到的,我也会考虑使用循环。循环本身非常简单,但是由于您需要跳过一些元素而增加了一些复杂性。不过,我认为这很清楚:
let queueDirectionLoop length =
let mutable i = 0
let mutable boughtVolume = 0.
let mutable totalVolume = 0.
for v in volumeQueue do
if i >= queueSize - length then
totalVolume <- totalVolume + abs v
if v > 0. then boughtVolume <- boughtVolume + v
i <- i + 1
2. * boughtVolume / totalVolume - 1.
我使用 4000 个元素测试了性能,这是我得到的结果:
#time
let rnd = System.Random()
for i in 0 .. 4000 do volumeQueue.Enqueue(rnd.NextDouble())
for i in 0 .. 10000 do ignore(queueDirection 1000) // ~900 ms
for i in 0 .. 10000 do ignore(queueDirectionFold 1000) // ~460 ms
for i in 0 .. 10000 do ignore(queueDirectionLoop 1000) // ~370 ms
只对队列进行一次迭代绝对有助于提高性能。在命令式循环中执行此操作有助于提高性能——如果您关心性能,这可能是值得的。代码的可读性可能比原始代码差一点,但我认为它并不比 fold
差多少。
关于performance - 我可以在 F# 中与收集函数并行执行两个求和,或者通常优化整个事情,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63746816/