对于一个简单的神经网络,我想将一个函数应用于gonum VecDense
的所有值。
Gonum对Dense矩阵有一个Apply
方法,但对于向量没有,所以我这样做是为了这个:
func sigmoid(z float64) float64 {
return 1.0 / (1.0 + math.Exp(-z))
}
func vSigmoid(zs *mat.VecDense) {
for i := 0; i < zs.Len(); i++ {
zs.SetVec(i, sigmoid(zs.AtVec(i)))
}
}
这似乎是并发执行的明显目标,所以我尝试了
var wg sync.WaitGroup
func sigmoid(z float64) float64 {
wg.Done()
return 1.0 / (1.0 + math.Exp(-z))
}
func vSigmoid(zs *mat.VecDense) {
for i := 0; i < zs.Len(); i++ {
wg.Add(1)
go zs.SetVec(i, sigmoid(zs.AtVec(i)))
}
wg.Wait()
}
由于Sigmoid()
没有以wg.Done()
结束,因此返回语句(完成所有工作)都在它之后,这不会起作用,也许不会出乎意料。<\ n / p>
我的问题是:如何使用并发将函数应用于gonum向量的每个元素?
答案 0 :(得分:4)
首先请注意,这种同时进行计算的尝试假定SetVec()
和AtVec()
方法对于使用不同索引的并发安全是安全的。如果不是这种情况,则尝试的解决方案本质上是不安全的,可能会导致数据争用和未定义的行为。
wg.Done()
来表示&#34;工人&#34; goroutine完成了它的工作。但是当goroutine完成它的工作时只有。
在您的情况下,它不是(仅)在worker goroutine中运行的sigmoid()
函数,而是zs.SetVec()
。因此,您应该在wg.Done()
返回时致电zs.SetVec()
,而不是更早。
一种方法是在wg.Done()
方法的末尾添加SetVec()
(它的开头也可能是defer wg.Done()
),但它不会是引入这种依赖是可行的(SetVec()
不应该知道任何等待组和goroutines,这会严重限制它的可用性。)
在这种情况下,最简单,最干净的方法是启动匿名函数(函数文字)作为工作者goroutine,您可以在其中调用zs.SetVec()
,并在其中调用wg.Defer()
一旦上述功能返回。
这样的事情:
for i := 0; i < zs.Len(); i++ {
wg.Add(1)
go func() {
zs.SetVec(i, sigmoid(zs.AtVec(i)))
wg.Done()
}()
}
wg.Wait()
但仅此不会工作,因为函数文字(闭包)引用了同时修改的循环变量,因此函数文字应该与其自己的副本一起使用,例如:
for i := 0; i < zs.Len(); i++ {
wg.Add(1)
go func(i int) {
zs.SetVec(i, sigmoid(zs.AtVec(i)))
wg.Done()
}(i)
}
wg.Wait()
另请注意,goroutines(尽管可能是轻量级的)确实有开销。如果他们所做的工作很小,那么开销可能超过利用多个核心/线程的性能增益,总体而言,你可能无法通过同时执行这些小任务来获得性能(地狱,你甚至可能会更糟糕)比不使用goroutines)。测量
此外,您正在使用goroutines来完成最少的工作,您可以通过不投掷&#34;来提高性能。一旦他们完成了他们的&#34;微小的&#34;工作,但你可能会重复使用&#34;他们。请参阅相关问题:Is this an idiomatic worker thread pool in Go?