在Go中同时生成随机数

时间:2017-01-13 10:22:42

标签: go random concurrency channel goroutine

我是Go和并发/并行编程的新手。为了尝试(并希望看到goroutines的性能优势),我已经整理了一个小型测试程序,它只需要生成1亿个随机int s - 首先在一个goroutine中,然后在尽可能多的goroutine中正如runtime.NumCPU()报道的那样。

然而,使用更多goroutine比使用单个goroutine时,我的表现总是更差。我假设我在程序设计或使用goroutines / channels /其他Go功能的方式中缺少一些重要的东西。任何反馈都非常感谢。

我附上以下代码。

package main

import "fmt"
import "time"
import "math/rand"
import "runtime"

func main() {
  // Figure out how many CPUs are available and tell Go to use all of them
  numThreads := runtime.NumCPU()
  runtime.GOMAXPROCS(numThreads)

  // Number of random ints to generate
  var numIntsToGenerate = 100000000
  // Number of ints to be generated by each spawned goroutine thread
  var numIntsPerThread = numIntsToGenerate / numThreads
  // Channel for communicating from goroutines back to main function
  ch := make(chan int, numIntsToGenerate)

  // Slices to keep resulting ints
  singleThreadIntSlice := make([]int, numIntsToGenerate, numIntsToGenerate)
  multiThreadIntSlice := make([]int, numIntsToGenerate, numIntsToGenerate)

  fmt.Printf("Initiating single-threaded random number generation.\n")
  startSingleRun := time.Now()
  // Generate all of the ints from a single goroutine, retrieve the expected
  // number of ints from the channel and put in target slice
  go makeRandomNumbers(numIntsToGenerate, ch)
  for i := 0; i < numIntsToGenerate; i++ {
    singleThreadIntSlice = append(singleThreadIntSlice,(<-ch))
  }
  elapsedSingleRun := time.Since(startSingleRun)
  fmt.Printf("Single-threaded run took %s\n", elapsedSingleRun)


  fmt.Printf("Initiating multi-threaded random number generation.\n")
  startMultiRun := time.Now()
  // Run the designated number of goroutines, each of which generates its
  // expected share of the total random ints, retrieve the expected number
  // of ints from the channel and put in target slice
  for i := 0; i < numThreads; i++ {
    go makeRandomNumbers(numIntsPerThread, ch)
  }
  for i := 0; i < numIntsToGenerate; i++ {
    multiThreadIntSlice = append(multiThreadIntSlice,(<-ch))
  }
  elapsedMultiRun := time.Since(startMultiRun)
  fmt.Printf("Multi-threaded run took %s\n", elapsedMultiRun)
}


func makeRandomNumbers(numInts int, ch chan int) {
  source := rand.NewSource(time.Now().UnixNano())
  generator := rand.New(source)
  for i := 0; i < numInts; i++ {
        ch <- generator.Intn(numInts*100)
    }
}

2 个答案:

答案 0 :(得分:4)

首先让我们更正并优化代码中的一些内容:

由于Go 1.5,GOMAXPROCS默认为可用的CPU核心数,因此无需设置(尽管它没有任何危害)。

要生成的数字:

var numIntsToGenerate = 100000000
var numIntsPerThread = numIntsToGenerate / numThreads

如果numThreads类似于3,则在多个goroutines的情况下,您生成的数字较少(由于整数除法),所以让我们更正它:

numIntsToGenerate = numIntsPerThread * numThreads

不需要1亿个值的缓冲区,将其减少到合理的值(例如1000):

ch := make(chan int, 1000)

如果你想使用append(),你创建的切片应该有0长度(和适当的容量):

singleThreadIntSlice := make([]int, 0, numIntsToGenerate)
multiThreadIntSlice := make([]int, 0, numIntsToGenerate)

但是在你的情况下这是不必要的,因为只有1个goroutine正在收集结果,你可以简单地使用索引,并创建像这样的切片:

singleThreadIntSlice := make([]int, numIntsToGenerate)
multiThreadIntSlice := make([]int, numIntsToGenerate)

收集结果时:

for i := 0; i < numIntsToGenerate; i++ {
    singleThreadIntSlice[i] = <-ch
}

// ...

for i := 0; i < numIntsToGenerate; i++ {
    multiThreadIntSlice[i] = <-ch
}

确定。代码现在更好了。试图运行它,你仍然会遇到多goroutine版本运行较慢。那是为什么?

这是因为控制,同步和收集来自多个goroutine的结果确实有开销。如果他们执行的任务很少,则通信开销会更大,总体而言会失去性能。

你的情况就是这样。设置rand.Rand()后生成一个随机数非常快。

让我们修改您的&#34;任务&#34;要足够大,以便我们可以看到多个goroutines的好处:

// 1 million is enough now:
var numIntsToGenerate = 1000 * 1000


func makeRandomNumbers(numInts int, ch chan int) {
    source := rand.NewSource(time.Now().UnixNano())
    generator := rand.New(source)
    for i := 0; i < numInts; i++ {
        // Kill time, do some processing:
        for j := 0; j < 1000; j++ {
            generator.Intn(numInts * 100)
        }
        // and now return a single random number
        ch <- generator.Intn(numInts * 100)
    }
}

在这种情况下,为了获得一个随机数,我们生成1000个随机数,然后将它们扔掉(以便计算/消除时间),然后再生成我们返回的数。我们这样做是为了让工作者goroutines 的计算时间超过多个goroutines的通信开销。

现在运行应用程序,我在4核计算机上的结果:

Initiating single-threaded random number generation.
Single-threaded run took 2.440604504s
Initiating multi-threaded random number generation.
Multi-threaded run took 987.946758ms

multi-goroutine版本运行 2.5次更快。这意味着如果你的goroutine会以1000个块的形式提供随机数,你会发现执行速度提高了2.5倍(与单个goroutine代相比)。

最后一点:

您的单goroutine版本也使用多个goroutine:1表示生成数字,1表示收集结果。很可能收集器没有充分利用CPU内核,而且大多只是等待结果,但仍然使用:2个CPU内核。我们估计&#34; 1.5&#34;使用CPU核心。而multi-goroutine版本使用4个CPU内核。正如粗略估计:4 / 1.5 = 2.66,非常接近我们的性能增益。

答案 1 :(得分:1)

如果你真的想要并行生成随机数,那么每个任务应该是生成数字,然后一次性返回它们而不是一次生成一个数字的任务并将它们作为读取信息提供给通道并且写入频道将减慢多往事例程。下面是修改后的代码,然后任务在一个过程中生成所需的数字,这在多行程例中表现更好,我也使用切片来从多个例程中收集结果。

package main

import "fmt"
import "time"
import "math/rand"
import "runtime"

func main() {
    // Figure out how many CPUs are available and tell Go to use all of them
    numThreads := runtime.NumCPU()
    runtime.GOMAXPROCS(numThreads)

    // Number of random ints to generate
    var numIntsToGenerate = 100000000
    // Number of ints to be generated by each spawned goroutine thread
    var numIntsPerThread = numIntsToGenerate / numThreads

    // Channel for communicating from goroutines back to main function
    ch := make(chan []int)

    fmt.Printf("Initiating single-threaded random number generation.\n")
    startSingleRun := time.Now()
    // Generate all of the ints from a single goroutine, retrieve the expected
    // number of ints from the channel and put in target slice
    go makeRandomNumbers(numIntsToGenerate, ch)
    singleThreadIntSlice := <-ch
    elapsedSingleRun := time.Since(startSingleRun)
    fmt.Printf("Single-threaded run took %s\n", elapsedSingleRun)

    fmt.Printf("Initiating multi-threaded random number generation.\n")

    multiThreadIntSlice := make([][]int, numThreads)
    startMultiRun := time.Now()
    // Run the designated number of goroutines, each of which generates its
    // expected share of the total random ints, retrieve the expected number
    // of ints from the channel and put in target slice
    for i := 0; i < numThreads; i++ {
        go makeRandomNumbers(numIntsPerThread, ch)
    }
    for i := 0; i < numThreads; i++ {
        multiThreadIntSlice[i] = <-ch
    }
    elapsedMultiRun := time.Since(startMultiRun)
    fmt.Printf("Multi-threaded run took %s\n", elapsedMultiRun)
    //To avoid not used warning
    fmt.Print(len(singleThreadIntSlice))
}

func makeRandomNumbers(numInts int, ch chan []int) {
    source := rand.NewSource(time.Now().UnixNano())
    generator := rand.New(source)
    result := make([]int, numInts)
    for i := 0; i < numInts; i++ {
        result[i] = generator.Intn(numInts * 100)
    }
    ch <- result
}