为什么添加并发会使该golang代码变慢?

我已经修改了一些Go代码,以解决与我姐夫玩的视频游戏有关的我的好奇心。


本质上,下面的代码模拟了游戏中与怪物的互动,以及他期望他们在失败后掉落物品的频率。我遇到的问题是,我希望像这样的一段代码对于并行化来说是完美的,但是当我并发添加时,完成所有模拟所花费的时间往往会使原始代码的速度降低4-6倍。没有并发。


为了使您更好地理解代码的工作方式,我有三个主要功能:交互功能,它是玩家和怪物之间的简单交互。如果怪物掉落物品,则返回1,否则返回0。仿真功能运行多个交互,并返回一部分交互结果(即1和0表示成功/不成功的交互)。最后,有一个测试函数,它运行一组模拟并返回一部分模拟结果,这些结果是导致掉落物品的相互作用的总数。这是我尝试并行运行的最后一个函数。


现在,我可以理解,如果我为要运行的每个测试创建一个goroutine,为什么代码会变慢。假设我正在运行100个测试,则在MacBook Air的4个CPU上的每个goroutine之间进行上下文切换会降低性能,但是我只创建与我拥有的处理器一样多的goroutine,并将测试次数除以goroutines。我希望这实际上可以提高代码的性能,因为我可以并行运行每个测试,但是,当然,我会遇到严重的问题。


我很想弄清楚为什么会这样,所以任何帮助将不胜感激。


以下是不带go例程的常规代码:


package main


import (

    "fmt"

    "math/rand"

    "time"

)


const (

    NUMBER_OF_SIMULATIONS = 1000

    NUMBER_OF_INTERACTIONS = 1000000

    DROP_RATE = 0.0003

)


/**

 * Simulates a single interaction with a monster

 *

 * Returns 1 if the monster dropped an item and 0 otherwise

 */

func interaction() int {

    if rand.Float64() <= DROP_RATE {

        return 1

    }

    return 0

}


/**

 * Runs several interactions and retuns a slice representing the results

 */

func simulation(n int) []int {

    interactions := make([]int, n)

    for i := range interactions {

        interactions[i] = interaction()

    }

    return interactions

}


/**

 * Runs several simulations and returns the results

 */

func test(n int) []int {

    simulations := make([]int, n)

    for i := range simulations {

        successes := 0

        for _, v := range simulation(NUMBER_OF_INTERACTIONS) {

            successes += v

        }

        simulations[i] = successes

    }

    return simulations

}


func main() {

    rand.Seed(time.Now().UnixNano())

    fmt.Println("Successful interactions: ", test(NUMBER_OF_SIMULATIONS))

}

并且,这是带有goroutines的并发代码:


package main


import (

    "fmt"

    "math/rand"

    "time"

    "runtime"

)


const (

    NUMBER_OF_SIMULATIONS = 1000

    NUMBER_OF_INTERACTIONS = 1000000

    DROP_RATE = 0.0003

)

鸿蒙传说
浏览 227回答 3
3回答

守着一只汪

在我的Linux四核i7笔记本电脑上测试您的代码我明白了这是一个Google Spreadsheet这表明,在Linux下,至少每个内核的缩放比例几乎是线性的。我认为您可能没有看到这有两个原因。首先是您的macbook air仅具有2个真正的核心。它有4个超线程,这就是为什么它报告4个最大cpus的原因。通常,超线程仅在单个内核上提供15%的额外性能,而不是您期望的100%。因此,仅在macbook air上基准测试1个或2个CPU!另一个原因可能是与Linux相比OS X线程的性能。他们使用不同的线程模型,这可能会影响性能。

MMTTMM

您的代码正在抽样一个二项式随机变量B(N,p),其中N是试验次数(此处为1M),p是成功进行单个试验的概率(此处为0.0003)。一种方法是建立一个累积概率表T,其中T [i]包含试验总数小于或等于i的概率。要生成样本,您可以选择一个统一的随机变量(通过rand.Float64),然后在表中找到包含大于或等于该概率的第一个索引。这里有点复杂,因为您有一个非常大的N和一个相当小的p,因此,如果您尝试构建表,那么数字和算术精度就会非常麻烦。但是您可以构建一个较小的表(假设有1000个大表)并对其进行1000次采样,以进行一百万次试用。这是完成所有这些工作的一些代码。它不太优雅(1000 是硬编码的),但它在我的旧笔记本电脑上不到一秒钟就生成了 1000 次模拟。通过例如将BinomialSampler的构造从循环中移出,或者通过使用二进制搜索而不是线性扫描来查找表索引,可以很容易地进一步优化。package mainimport (&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "math"&nbsp; &nbsp; "math/rand")type BinomialSampler []float64func (bs BinomialSampler) Sample() int {&nbsp; &nbsp; r := rand.Float64()&nbsp; &nbsp; for i := 0; i < len(bs); i++ {&nbsp; &nbsp; &nbsp; &nbsp; if bs[i] >= r {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return i&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; return len(bs)}func NewBinomialSampler(N int, p float64) BinomialSampler {&nbsp; &nbsp; r := BinomialSampler(make([]float64, N+1))&nbsp; &nbsp; T := 0.0&nbsp; &nbsp; choice := 1.0&nbsp; &nbsp; for i := 0; i <= N; i++ {&nbsp; &nbsp; &nbsp; &nbsp; T += choice * math.Pow(p, float64(i)) * math.Pow(1-p, float64(N-i))&nbsp; &nbsp; &nbsp; &nbsp; r[i] = T&nbsp; &nbsp; &nbsp; &nbsp; choice *= float64(N-i) / float64(i+1)&nbsp; &nbsp; }&nbsp; &nbsp; return r}func WowSample(N int, p float64) int {&nbsp; &nbsp; if N%1000 != 0 {&nbsp; &nbsp; &nbsp; &nbsp; panic("N must be a multiple of 1000")&nbsp; &nbsp; }&nbsp; &nbsp; bs := NewBinomialSampler(1000, p)&nbsp; &nbsp; r := 0&nbsp; &nbsp; for i := 0; i < N; i += 1000 {&nbsp; &nbsp; &nbsp; &nbsp; r += bs.Sample()&nbsp; &nbsp; }&nbsp; &nbsp; return r}func main() {&nbsp; &nbsp; for i := 0; i < 1000; i++ {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(WowSample(1000000, 0.0003))&nbsp; &nbsp; }}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go