利用并发对函数进行向量化 [英] Vectorise a function taking advantage of concurrency

查看:107
本文介绍了利用并发对函数进行向量化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

For a simple neural network I want to apply a function to all the values of a gonum VecDense.

Gonum对于密集矩阵有一个 Apply 方法,但对于矢量却没有,因此我是手工完成的:

Gonum has an Apply method for Dense matrices, but not for vectors, so I am doing this by hand:

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)))
    }
}

这似乎是并发执行的明显目标,所以我尝试了

This seems to be an obvious target for concurrent execution, so I tried

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语句(完成所有工作)紧随其后。

This doesn't work, perhaps not unexpectedly, as Sigmoid() doesn't end with wg.Done(), as the return statement (which does all the work) comes after it.

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

My question is: How can I use concurrency to apply a function to each element of a gonum vector?

推荐答案

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

First note that this attempt to do computation concurrenty assumes that the SetVec() and AtVec() methods are safe for concurrent use with distinct indices. If this is not the case, the attempted solution is inherently unsafe and may result in data races and undefined behavior.

<应该调用code> wg.Done()来表明工人 goroutine已完成工作。但是,当goroutine完成工作时,只能

wg.Done() should be called to signal that the "worker" goroutine finished its work. But only when the goroutine finished its work.

在您的情况下,它不是(唯一) sigmoid()在工作程序goroutine中运行的函数,而在 zs.SetVec()中运行。因此,当 zs.SetVec()返回时,应该立即调用 wg.Done()

In your case it is not (only) the sigmoid() function that is run in the worker goroutine, but rather zs.SetVec(). So you should call wg.Done() when zs.SetVec() has returned, not sooner.

一种方法是将 wg.Done()添加到 SetVec( )方法(在开始时也可以是 def wg.Done()),但引入这种依赖关系并不可行( SetVec()不应该知道任何等待组和goroutine,这会严重限制其可用性。)

One way would be to add a wg.Done() to the end of the SetVec() method (it could also be a defer wg.Done() at its beginning), but it wouldn't be feasible to introduce this dependency (SetVec() should not know about any wait groups and goroutines, this would seriously limit its usability).

在这种情况下,最简单,最干净的方法是启动一个匿名函数(函数文字)作为工作程序goroutine,在其中您可以调用 zs.SetVec(),然后上面提到的函数返回后,您可以在其中调用 wg.Defer()

The easiest and cleanest way in this case would be to launch an anonymous function (a function literal) as the worker goroutine, in which you may call zs.SetVec(), and in which you may call wg.Defer() once the above mentioned function has returned.

类似这样的事情:

for i := 0; i < zs.Len(); i++ {
    wg.Add(1)
    go func() {
        zs.SetVec(i, sigmoid(zs.AtVec(i)))
        wg.Done()
    }()
}
wg.Wait()

但这仅无效,因为函数文字(闭包)是指同时修改的循环变量,因此函数文字应使用其自己的副本,例如:

But this alone won't work, as the function literal (closure) refers to the loop variable which is modified concurrently, so the function literal should work with its own copy, e.g.:

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做得更糟) 。

Also note that goroutines (although may be lightweight) do have overhead. If the work they do is "small", the overhead may outweight the performance gain of utilizing multiple cores / threads, and overall you might not gain performance by executing such small tasks concurrently (hell, you may even do worse than without using goroutines). Measure.

另外,您正在使用goroutine来完成最少的工作,可以通过在完成微小的工作后不扔掉 goroutine来提高性能,但是您可以重用它们。请参阅相关问题:这是一个惯用的工作线程Go中有游泳池吗?

Also you are using goroutines to do minimal work, you may improve performance by not "throwing" away goroutines once they're done with their "tiny" work, but you may "reuse" them. See related question: Is this an idiomatic worker thread pool in Go?

这篇关于利用并发对函数进行向量化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆