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

我正在构建一个参数优化器,它基本上生成配置,对所有配置进行基准测试,收集所有结果,对它们进行排序,然后选择相对于基准测试结果的最佳性能配置。


基准测试本身工作正常,但每次运行需要 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

 }

 


蓝山帝景
浏览 103回答 1
1回答

缥缈止盈

有多种方法可以做到这一点。一本“教科书”是这样的:results := make(chan *score)for i, config := range strategyConfigs {&nbsp; &nbsp; state := newState(&config)&nbsp; &nbsp; go a.runBenchmark(state, results)}for i := 0; i < len(strategyConfigs); i++ {&nbsp; scores[i] = <-results}...,然后修改方法以不返回任何值,并接受类型的第二个参数。runBenchmarkchan *score代码段是这样滚动的:创建通道以交换类型为 的值。*score开始运行-我想-“一个代理人”的metod的戈鲁丁。该方法通过提交给它的通道发送(指向)它计算的对象并退出。runBenchmarkscore另一个循环执行与生成的 goroutines 一样多的通道接收,并将每个接收的值放入生成的切片中。警告:这预先假定在多个戈鲁丁同时运行的情况下执行它是可以的。arunBenchmark如果不能,你将需要创建一个单独的来运行每个单独的戈鲁廷。鉴于你的例子不是太小,我很难对它有多难/多简单做出有根据的猜测。如果您需要这方面的帮助,请单独提出一个狭隘的问题。a如果你有,比如,数亿个“策略配置”,这种方法会过于简单,因为所有的goroutines都会一次生成,a)这是浪费资源;b)如果数字太大,甚至可能失败。教科书上的解决方法是使用所谓的“扇出” - 当你有一个戈鲁廷通过一个通道接收“任务”并将它们分配到有限数量的工人大流子上时,这些工人戈鲁廷始终保持在一定限制以下。你可以从这里开始。另一种方法是利用这样一个事实,即在Go中,数组的每个元素(和切片 - 通过扩展)都被视为一个单独的变量。这意味着从工作线程中同时更新生成的切片的各个元素是可以的 - 只要切片是预先分配的并且永远不会重新分配(使用,重新切片等操作),而此过程正在进行中。append为了演示,让我们使用“等待组”:import "sync"var wg sync.WaitGroupscores := make([]*score, len(strategyConfigs))wg.Add(len(strategyConfigs))for i, config := range strategyConfigs {&nbsp; &nbsp; state := newState(&config)&nbsp; &nbsp; go a.runBenchmark(state, &scores[i], &wg)}wg.Wait()应将方法修改为具有runBenchmarkdefer&nbsp;wg.Done()作为其第一个语句,并接受两个附加参数 — 类型和 。*score*sync.WaitGroup在这里,开始在单独的 goroutine 中运行,并传递要更新其结果的元素的地址和等待组的地址以发出“任务完成”信号。runBenchmark请注意,基本上与第一种情况下相同的警告适用。如您所见,戈鲁廷确实不返回任何值。这主要是因为当它可能的时候,产生它的戈鲁廷可能早已消失,并且没有地方可以返回该值。因此,基本上有两种方法可以“获取数据”:在通道上发送该值(并从该通道接收其他一些戈鲁丁)。这是Go的面包和黄油。我建议从这种方法开始,并使用它,直到你对它感到完全舒服。更新内存中的某个位置,前提是没有其他 goroutine 执行相同的操作(否则您将遇到数据竞赛)。在某些情况下,这种方法可能更快(甚至更简单,对某些人来说),但这种代码背后的原因可能更难看出。您可以从这个开始,了解基本概念。总而言之,有几个指针。我建议在掌握其基础知识之前,不要开始编写涉及并发的几乎不严重的代码。请从围棋之旅的相关部分开始,然后转到围棋博客:https://blog.golang.org/pipelineshttps://blog.golang.org/io2013-talk-concurrencyhttps://blog.golang.org/concurrency-timeouts首先尝试使用更简单的示例。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go