为什么添加并发会降低这个golang代码?

为什么添加并发会降低这个golang代码?,第1张

概述我有一些Go代码,我一直在修补,回答一个小的好奇心,我的一个视频游戏我的兄弟玩。 基本上,下面的代码模拟了与游戏中的怪物的交互,以及他可以期望他们在失败时丢弃物品的频率。我所遇到的问题是,我希望像这样的代码片段是完美的并行化,但当我并发添加时间,它需要做所有的模拟趋向于减慢原来的4-6倍无并发。 为了让你更好地理解代码的工作原理,我有三个主要功能:交互功能,这是一个简单的交互的玩家和一个怪物。如果 我有一些Go代码,我一直在修补,回答一个小的好奇心,我的一个视频游戏我的兄弟玩。

基本上,下面的代码模拟了与游戏中的怪物的交互,以及他可以期望他们在失败时丢弃物品的频率。我所遇到的问题是,我希望像这样的代码片段是完美的并行化,但当我并发添加时间,它需要做所有的模拟趋向于减慢原来的4-6倍无并发。

为了让你更好地理解代码的工作原理,我有三个主要功能:交互功能,这是一个简单的交互的玩家和一个怪物。如果怪物掉落一个项目,它返回1,否则返回0。模拟函数运行若干交互并返回交互结果的片段(即,表示成功/不成功交互的1和0)。最后,有一个测试函数,它运行一组模拟,并返回一个模拟结果片段,这是导致丢弃项目的交互的总数。这是我试图并行运行的最后一个功能。

现在,我可以理解为什么代码会减慢,如果我为我想运行的每个测试创建一个goroutine。假设我正在运行100个测试,在每个goroutine之间的上下文切换在4个cpu我的MacBook Air已经杀了性能,但我只创建与我有处理器和许多goroutine, goroutines。我希望这实际上加快了代码的性能,因为我运行我的每个测试并行,但当然,我得到了一个大的减速而不是。

我很想知道为什么这是发生,所以任何帮助将非常感激。

下面是没有go程序的常规代码:

package mainimport (    "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 mainimport (    "fmt"    "math/rand"    "time"    "runtime")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,c chan []int) {    simulations := make([]int,n)    for i := range simulations {        for _,v := range simulation(NUMBER_OF_INteraCTIONS) {            simulations[i] += v        }    }    c <- simulations}func main() {    rand.Seed(time.Now().UnixNano())    ncpu := runtime.Numcpu()    runtime.GOMAXPROCS(ncpu)    fmt.Println("Number of cpus: ",ncpu)    tests := make([]chan []int,ncpu)    for i := range tests {        c := make(chan []int)        go test(NUMBER_OF_SIMulATIONS/ncpu,c)        tests[i] = c    }    // Concatentate the test results    results := make([]int,NUMBER_OF_SIMulATIONS)    for i,c := range tests {        start := (NUMBER_OF_SIMulATIONS/ncpu) * i        stop := (NUMBER_OF_SIMulATIONS/ncpu) * (i+1)        copy(results[start:stop],<-c)    }    fmt.Println("Successful interactions: ",results)}

更新(01/12/13 18:05)

我添加了一个新版本的并发代码下面为每个goroutine创建一个新的Rand实例,每个“系统”的建议如下。我现在看到一个非常轻微的速度比串行版本的代码(大约减少15-20%的总时间)。我很想知道为什么我没有看到更接近75%的时间减少,因为我的工作负载扩展到我的MBA的4核心。有没有任何进一步的建议,可以帮助?

package mainimport (    "fmt"    "math/rand"    "time"    "runtime")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(generator *rand.Rand) int {    if generator.float64() <= DROP_RATE {        return 1    }    return 0}/** * Runs several interactions and retuns a slice representing the results */func simulation(n int,generator *rand.Rand) []int {    interactions := make([]int,n)    for i := range interactions {        interactions[i] = interaction(generator)    }    return interactions}/** * Runs several simulations and returns the results */func test(n int,c chan []int) {    source := rand.NewSource(time.Now().UnixNano())    generator := rand.New(source)    simulations := make([]int,v := range simulation(NUMBER_OF_INteraCTIONS,generator) {            simulations[i] += v        }    }    c <- simulations}func main() {    rand.Seed(time.Now().UnixNano())    ncpu := runtime.Numcpu()    runtime.GOMAXPROCS(ncpu)    fmt.Println("Number of cpus: ",results)}

更新(01/13/13 17:58)

感谢大家帮我找出我的问题。我终于得到了我正在寻找的答案,所以我想我只是总结在这里对于有同样问题的任何人。

基本上我有两个主要的问题:第一,即使我的代码是embarrassingly parallel,它是运行较慢,当我拆分它在可用的处理器,其次,解决方案打开另一个问题,这是我的序列代码运行两次慢作为在单处理器上运行的并发代码,这将是期望是大致相同的。在这两种情况下,问题是随机数生成器函数rand.float64。基本上,这是rand包提供的一个方便的函数。在该包中,Rand结构的全局实例由每个方便函数创建和使用。这个全局Rand实例具有与其相关联的互斥锁。因为我使用这个方便的函数,我不能真正地并行化我的代码,因为每个goroutine必须排队访问全局兰德实例。解决方案(下面的“系统”建议)是为每个goroutine创建一个Rand结构的单独实例。这解决了第一个问题,但创建了第二个问题。

第二个问题是我的非并行并发代码(即我的并发代码只运行一个处理器)运行的速度是顺序代码的两倍。这样做的原因是,即使我只运行一个处理器和一个goroutine,goroutine有自己的Rand结构的实例,我创建,我已经创建它没有互斥锁。顺序代码仍然使用rand.float64方便的函数,它使用全局互斥保护的Rand实例。获取该锁的成本导致顺序代码运行两次慢。

所以,故事的道德是,每当性能重要时,确保你创建一个Rand结构的实例,并调用你需要的函数,而不是使用包提供的方便的功能。

这个问题似乎来自你使用rand.float64(),它使用了一个带有Mutex锁的共享全局对象。

相反,如果为每个cpu创建一个单独的rand.New(),传递给交互(),并使用它来创建float64(),有一个巨大的改进。

更新以显示对现在使用rand.New()的问题中的新示例代码的更改

test()函数已修改为使用给定通道,或返回结果。

func test(n int,c chan []int) []int {    source := rand.NewSource(time.Now().UnixNano())    generator := rand.New(source)    simulations := make([]int,generator) {            simulations[i] += v        }       }       if c == nil {        return simulations    }       c <- simulations    return nil }

main()函数已更新以运行两个测试,并输出定时结果。

func main() {    rand.Seed(time.Now().UnixNano())    ncpu := runtime.Numcpu()    runtime.GOMAXPROCS(ncpu)    fmt.Println("Number of cpus: ",ncpu)    start := time.Now()    fmt.Println("Successful interactions: ",len(test(NUMBER_OF_SIMulATIONS,nil)))    fmt.Println(time.Since(start))    start = time.Now()    tests := make([]chan []int,<-c)    }    fmt.Println("Successful interactions: ",len(results))    fmt.Println(time.Since(start))}

输出是我收到:

> Number of cpus:  2 >> Successful interactions:  1000 > 1m20.39959s>> Successful interactions:  1000> 41.392299s
总结

以上是内存溢出为你收集整理的为什么添加并发会降低这个golang代码?全部内容,希望文章能够帮你解决为什么添加并发会降低这个golang代码?所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://www.outofmemory.cn/langs/1274653.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-08
下一篇 2022-06-08

发表评论

登录后才能评论

评论列表(0条)

保存