利用并发性对函数进行矢量化



对于一个简单的神经网络,我想将一个函数应用于 gonumVecDense的所有值。

Gonum 有一个用于密集矩阵的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()结尾,因为 return 语句(完成所有工作(在它之后。

我的问题是:如何使用并发性将函数应用于 gonum 向量的每个元素?

首先请注意,这种并发计算的尝试假定SetVec()AtVec()方法对于不同索引的并发使用是安全的。如果不是这种情况,则尝试的解决方案本质上是不安全的,并可能导致数据争用和未定义的行为。


应该调用wg.Done()来表示"工人"goroutine完成了它的工作。但只有当goroutine完成它的工作时。

在您的情况下,它不是(仅(在worker goroutine中运行的sigmoid()函数,而是zs.SetVec()。所以你应该在zs.SetVec()回来的时候打电话给wg.Done(),而不是更早。

一种方法是在SetVec()方法的末尾添加一个wg.Done()(它也可以在开始时是一个defer wg.Done()(,但是引入这种依赖是不可行的(SetVec()不应该知道任何等待组和goroutines,这将严重限制其可用性(。

在这种情况下,最简单和最干净的方法是启动一个匿名函数(函数文字(作为worker 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 来完成最少的工作,一旦 goroutines 完成"微小"工作,您就可以通过不"丢弃"它们来提高性能,但您可以"重用"它们。请参阅相关问题:这是 Go 中的惯用工作线程池吗?

相关内容

  • 没有找到相关文章

最新更新