为什么添加并发会降低这个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的接口? 接口可以说是一种类型,可以粗略的理解为他...
1、Golang指针 在介绍Golang指针隐式间接引用前,先简单说下...
1、概述 1.1&#160;Protocol buffers定义 Protocol buffe...
判断文件是否存在,需要用到"os"包中的两个函数: os.Stat(...
1、编译环境 OS :Loongnix-Server Linux release 8.3 CPU指...
1、概述 Golang是一种强类型语言,虽然在代码中经常看到i:=1...