performance - 我可以在 F# 中与集合函数并行进行两次求和,或者通常优化整个事情
问题描述
我有以下代码:
// 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
.
推荐阅读
- c# - 在c#中处理设备插槽的最佳方法是什么
- css - 在 reactstrap 标签中设置 material-ui 图标
- r - 在输入旁边设置小部件标签并最小化其上方和下方的距离
- r - 选择删除并在同一个选择调用中保留一些变量
- unit-testing - 来自命名导出的 Jest 模拟工厂函数
- macos - google-cloud-sdk install ERROR: (gcloud.components.update) 提供的路径必须存在
- bash - 使用 env-cmd 将 bash 环境变量传递到新的 shell
- reactjs - 从 woocommerce 中的 API 向特定国家/地区销售
- apache-kafka - 如何使用 SMT 为不同主题转换重命名字段
- spring-boot - 在redis中对哈希键进行通配符搜索