为了账号安全,请及时绑定邮箱和手机立即绑定

Golang:如何捕获大规模并行基准测试的返回值(>100万个任务)?

Golang:如何捕获大规模并行基准测试的返回值(>100万个任务)?

Go
蓝山帝景 2022-09-19 17:30:02
我正在构建一个参数优化器,它基本上生成配置,对所有配置进行基准测试,收集所有结果,对它们进行排序,然后选择相对于基准测试结果的最佳性能配置。基准测试本身工作正常,但每次运行需要 50 毫秒和 2 秒,具体取决于配置。关键是,优化程序生成了非常大量的配置,这意味着,在最低端的 100k 和高端的大约 4000 万之间,大约 100 万到 500 万是一个很好的正常范围。显然,单线程版本需要永远,CPU负载实际上非常低,因为任务相对较轻。我已经设计了基准测试,以使其与并发性很好地配合使用,也就是说,运行器被封装在一个单独的结构(称为代理)中,基准测试本质上是一个将所有状态作为参数的纯函数。从本质上讲,每次运行都会创建自己的状态,然后独立于所有其他运行,但所有函数都使用相同的(引用的)共享数据集。该函数如下所示。但是,我很难处理每个基准测试的返回值。回到过去,在“缩放”中,我们使用 Async / Await 来实现任务并行性,然后让结果继续。Go 例程,afaik 只能很好地与没有返回值的函数一起使用。在实践中,通道是从戈鲁廷获取值的最自然方式。这就是我正在考虑的关键:考虑到我通常有>100万个任务,如何正确有效地捕获返回值?与此相关的是,Golang实际上有一个非常快速的参数优化器吗?对于蟒蛇,我记得optum提供了出色的结果。谢谢func (a *Agent) runOptimization(strategyConfigs []cmdb.Config) (result *bmx.OptimizeResult) {scores := make([]bmx.BackTestResult, len(strategyConfigs))println("Run all benchmarks")for i, config := range strategyConfigs {    state := newState(&config)    score := a.runBenchmark(state)    scores[i] = *score // sort only works on actual values}println("Sort results")sort.Sort(bmx.ByTotalPnL(scores))println("Select best config")best := scores[len(scores)-1]println("Generate best strategy config")stratConf := a.getStrategyConfig(best.PatternConfig)println("Return optimization results ")result = &bmx.OptimizeResult{    Symbol:          best.Symbol,    StrategyType:    best.StrategyType,    OptimizedConfig: &stratConf,    ...}    return result } 
查看完整描述

1 回答

?
缥缈止盈

TA贡献2041条经验 获得超4个赞

有多种方法可以做到这一点。


一本“教科书”是这样的:


results := make(chan *score)


for i, config := range strategyConfigs {

    state := newState(&config)

    go a.runBenchmark(state, results)

}


for i := 0; i < len(strategyConfigs); i++ {

  scores[i] = <-results

}

...,然后修改方法以不返回任何值,并接受类型的第二个参数。runBenchmarkchan *score

代码段是这样滚动的:

  1. 创建通道以交换类型为 的值。*score

  2. 开始运行-我想-“一个代理人”的metod的戈鲁丁。
    该方法通过提交给它的通道发送(指向)它计算的对象并退出。runBenchmarkscore

  3. 另一个循环执行与生成的 goroutines 一样多的通道接收,并将每个接收的值放入生成的切片中。

警告:

  • 这预先假定在多个戈鲁丁同时运行的情况下执行它是可以的。arunBenchmark

    如果不能,你将需要创建一个单独的来运行每个单独的戈鲁廷。
    鉴于你的例子不是太小,我很难对它有多难/多简单做出有根据的猜测。
    如果您需要这方面的帮助,请单独提出一个狭隘的问题。a

  • 如果你有,比如,数亿个“策略配置”,这种方法会过于简单,因为所有的goroutines都会一次生成,a)这是浪费资源;b)如果数字太大,甚至可能失败。
    教科书上的解决方法是使用所谓的“扇出” - 当你有一个戈鲁廷通过一个通道接收“任务”并将它们分配到有限数量的工人大流子上时,这些工人戈鲁廷始终保持在一定限制以下。你可以从这里开始。

另一种方法是利用这样一个事实,即在Go中,数组的每个元素(和切片 - 通过扩展)都被视为一个单独的变量。这意味着从工作线程中同时更新生成的切片的各个元素是可以的 - 只要切片是预先分配的并且永远不会重新分配(使用,重新切片等操作),而此过程正在进行中。append

为了演示,让我们使用“等待组”

import "sync"


var wg sync.WaitGroup


scores := make([]*score, len(strategyConfigs))


wg.Add(len(strategyConfigs))

for i, config := range strategyConfigs {

    state := newState(&config)

    go a.runBenchmark(state, &scores[i], &wg)

}


wg.Wait()

应将方法修改为具有runBenchmark

defer wg.Done()

作为其第一个语句,并接受两个附加参数 — 类型和 。*score*sync.WaitGroup

在这里,开始在单独的 goroutine 中运行,并传递要更新其结果的元素的地址和等待组的地址以发出“任务完成”信号。runBenchmark

请注意,基本上与第一种情况下相同的警告适用。

如您所见,戈鲁廷确实不返回任何值。这主要是因为当它可能的时候,产生它的戈鲁廷可能早已消失,并且没有地方可以返回该值。

因此,基本上有两种方法可以“获取数据”:

  • 在通道上发送该值(并从该通道接收其他一些戈鲁丁)。

    这是Go的面包和黄油。我建议从这种方法开始,并使用它,直到你对它感到完全舒服。

  • 更新内存中的某个位置,前提是没有其他 goroutine 执行相同的操作(否则您将遇到数据竞赛)。

    在某些情况下,这种方法可能更快(甚至更简单,对某些人来说),但这种代码背后的原因可能更难看出。

您可以从这个开始,了解基本概念。


总而言之,有几个指针。


查看完整回答
反对 回复 2022-09-19
  • 1 回答
  • 0 关注
  • 93 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号