Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/go/7.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Concurrency 为什么添加并发会降低这个golang代码的速度?_Concurrency_Go - Fatal编程技术网

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

Concurrency 为什么添加并发会降低这个golang代码的速度?,concurrency,go,Concurrency,Go,我有一点围棋代码,我一直在修补,以回答我的一个小好奇有关的视频游戏,我的姐夫玩 本质上,下面的代码模拟了游戏中与怪物的交互,以及他希望怪物在失败后掉落物品的频率。我遇到的问题是,我希望这样的代码对于并行化来说是完美的,但是当我添加并发时,执行所有模拟所需的时间往往会比没有并发时的原始时间慢4-6倍 为了让您更好地理解代码的工作原理,我有三个主要功能:交互功能,它是玩家和怪物之间的简单交互。如果怪物掉落物品,则返回1,否则返回0。simulation函数运行多个交互并返回交互结果的一部分(即1和0

我有一点围棋代码,我一直在修补,以回答我的一个小好奇有关的视频游戏,我的姐夫玩

本质上,下面的代码模拟了游戏中与怪物的交互,以及他希望怪物在失败后掉落物品的频率。我遇到的问题是,我希望这样的代码对于并行化来说是完美的,但是当我添加并发时,执行所有模拟所需的时间往往会比没有并发时的原始时间慢4-6倍

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

现在,我可以理解,如果我为每个要运行的测试创建一个goroutine,为什么代码会变慢。假设我正在运行100个测试,MacBook Air的4个CPU上的每个Goroutine之间的上下文切换会降低性能,但我只创建与处理器数量相同的Goroutine,并在Goroutine之间划分测试数量。我希望这实际上会提高代码的性能,因为我正在并行运行我的每个测试,但是,当然,我的速度反而会大大降低

我很想弄清楚为什么会发生这种情况,所以任何帮助都将不胜感激

以下是不带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))
}
主程序包
进口(
“fmt”
“数学/兰德”
“时间”
)
常数(
模拟的数量=1000
交互次数=1000000
下降率=0.0003
)
/**
*模拟与怪物的单一交互
*
*如果怪物掉落物品,则返回1,否则返回0
*/
func interaction()int{

如果rand.Float64()问题似乎来自于您使用的
rand.Float64()
,它使用了一个共享全局对象,该对象上有一个互斥锁

相反,如果您为每个CPU创建一个单独的
rand.New()
,将其传递给
interactions()
,并使用它创建
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, n)
    for i := range simulations {
        for _, v := range simulation(NUMBER_OF_INTERACTIONS, generator) {
            simulations[i] += v
        }   
    }   
    if c == nil {
        return simulations
    }   
    c <- simulations
    return nil 
}
我收到的输出是:

> Number of CPUs: 2 > > Successful interactions: 1000 > 1m20.39959s > > Successful interactions: 1000 > 41.392299s >CPU数量:2 > >成功互动:1000 >1m20.39959s > >成功互动:1000 >41.392299s
我的结果显示,与1个CPU相比,4个CPU具有相当大的并发性:

英特尔酷睿2四处理器Q8300@2.50GHz x 4

源代码:更新(01/12/13 18:05)


在我的Linux四核i7笔记本电脑上测试你的代码

这是一个

这表明,在Linux下,至少每个内核的伸缩性非常接近线性

我认为你没有看到这一点可能有两个原因

首先,您的macbook air只有2个真正的内核。但它有4个,这就是为什么它报告4个为最大CPU的原因。与您可能期望的100%性能相比,超线程通常只提供额外的15%性能。因此,请坚持只在macbook air上对1或2个CPU进行基准测试


另一个原因可能是与Linux相比OS X线程的性能。它们使用不同的线程模型,这可能会影响性能。

您的代码正在采样一个二项式随机变量,B(N,p),其中N是试验次数(此处1M),p是单个试验成功的概率(此处0.0003)

一种方法是建立一个累积概率表T,其中T[i]包含试验总数小于或等于i的概率。然后,为了生成样本,您可以选择一个统一的随机变量(通过rand.Float64)并找到表中包含大于或等于它的概率的第一个索引

这里有点复杂,因为你有一个非常大的N和一个相当小的p,所以如果你试图构建这个表,你会遇到非常小的数字和算术精度的问题。但是你可以构建一个更小的表(比如1000个大的表),然后对它进行1000次采样,得到100万次试验

这里有一些代码可以完成所有这些。它不太优雅(1000是硬编码的),但在我的旧笔记本电脑上,它可以在不到一秒钟的时间内生成1000个模拟。进一步优化很容易,例如,将BinomialSampler的构造从循环中提升出来,或者使用二进制搜索而不是线性扫描来查找表索引

package main

import (
    "fmt"
    "math"
    "math/rand"
)

type BinomialSampler []float64

func (bs BinomialSampler) Sample() int {
    r := rand.Float64()
    for i := 0; i < len(bs); i++ {
        if bs[i] >= r {
            return i
        }
    }
    return len(bs)
}

func NewBinomialSampler(N int, p float64) BinomialSampler {
    r := BinomialSampler(make([]float64, N+1))
    T := 0.0
    choice := 1.0
    for i := 0; i <= N; i++ {
        T += choice * math.Pow(p, float64(i)) * math.Pow(1-p, float64(N-i))
        r[i] = T
        choice *= float64(N-i) / float64(i+1)
    }
    return r
}

func WowSample(N int, p float64) int {
    if N%1000 != 0 {
        panic("N must be a multiple of 1000")
    }
    bs := NewBinomialSampler(1000, p)
    r := 0
    for i := 0; i < N; i += 1000 {
        r += bs.Sample()
    }
    return r
}

func main() {
    for i := 0; i < 1000; i++ {
        fmt.Println(WowSample(1000000, 0.0003))
    }
}
主程序包
进口(
“fmt”
“数学”
“数学/兰德”
)
类型BinomialSampler[]float64
func(bs BinomialSampler)Sample()int{
r:=rand.Float64()
对于i:=0;i=r{
返回i
}
}
返回透镜(bs)
}
func NewBinomialSampler(N int,p float64)BinomialSampler{
r:=BinomialSampler(make([]浮点64,N+1))
T:=0.0
选择:=1.0

对于i:=0;感谢您的提示,我更新了代码,为每个goroutine创建了一个Rand实例,并将其传递给
交互
函数,它似乎确实加快了并发代码的速度。不过,我仍然没有得到大幅度的加速。我有点希望看到时间缩短近4倍(因为我的机器上有4个内核)但是,我只看到时间减少了1.2倍。我继续添加了新代码和你的建议
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, 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: ", len(results))
    fmt.Println(time.Since(start))
}
> Number of CPUs: 2 > > Successful interactions: 1000 > 1m20.39959s > > Successful interactions: 1000 > 41.392299s
$ go version
go version devel +adf4e96e9aa4 Thu Jan 10 09:57:01 2013 +1100 linux/amd64

$ time  go run temp.go
Number of CPUs:  1
real    0m30.305s
user    0m30.210s
sys     0m0.044s

$ time  go run temp.go
Number of CPUs:  4
real    0m9.980s
user    0m35.146s
sys     0m0.204s
package main

import (
    "fmt"
    "math"
    "math/rand"
)

type BinomialSampler []float64

func (bs BinomialSampler) Sample() int {
    r := rand.Float64()
    for i := 0; i < len(bs); i++ {
        if bs[i] >= r {
            return i
        }
    }
    return len(bs)
}

func NewBinomialSampler(N int, p float64) BinomialSampler {
    r := BinomialSampler(make([]float64, N+1))
    T := 0.0
    choice := 1.0
    for i := 0; i <= N; i++ {
        T += choice * math.Pow(p, float64(i)) * math.Pow(1-p, float64(N-i))
        r[i] = T
        choice *= float64(N-i) / float64(i+1)
    }
    return r
}

func WowSample(N int, p float64) int {
    if N%1000 != 0 {
        panic("N must be a multiple of 1000")
    }
    bs := NewBinomialSampler(1000, p)
    r := 0
    for i := 0; i < N; i += 1000 {
        r += bs.Sample()
    }
    return r
}

func main() {
    for i := 0; i < 1000; i++ {
        fmt.Println(WowSample(1000000, 0.0003))
    }
}