I'm building a parameter optimizer that essentially generates configurations, benchmarks all of them, collects all results, sorts them, and then picks the best performing configuration relative to the benchmark result.
The benchmark by itself works fine, but takes between 50 ms an 2 sec per run depending on the configuration. The crux is, the optimizer generates a very large number of configuration, that means, between 100k on the lowest end and about 40 million on the higher end with about 1 - 5 million as a good normal range. Obviously, the single threaded version takes forever and CPU load is actually very low as the task is relatively light.
I already designed the benchmark in a way to make it play nicely with concurrency, that is, the runner is encapsulated in a separate struct (called agent) and the benchmark is essentially a pure function that takes all state as a parameter. Essentially, each run creates its own state and then runs independently of all others, but all functions using the same (referenced) shared dataset. The function is shown below.
However, I struggle with dealing with the return value per Benchmark. Back in the days, in Scale we used Async / Await for task parallelism and just let the results roll on. Go Routines, afaik only work well with functions that have no return value. In practice, channels are the most natural way to fetch a value from a goroutine. And that's the crux I'm mulling over:
Considering that I usually have > 1 million tasks, how do I catch the return values correctly and efficiently?
Related to that, is there actually a very fast parameter optimizer for Golang? For python, I remember optuna delivering excellent results.
Thank you
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
}