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

前端之家收集整理的这篇文章主要介绍了为什么添加并发会降低这个golang代码?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有一些Go代码,我一直在修补,回答一个小的好奇心,我的一个视频游戏我的兄弟玩。

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

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

现在,我可以理解为什么代码会减慢,如果我为我想运行的每个测试创建一个goroutine。假设我正在运行100个测试,在每个goroutine之间的上下文切换在4个cpu我的MacBook Air已经杀了性能,但我只创建与我有处理器和许多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
)

/**
 * 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 main

import (
    "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

猜你在找的Go相关文章