当前位置:  开发笔记 > 编程语言 > 正文

在Go中同时生成随机数

如何解决《在Go中同时生成随机数》经验,为你挑选了1个好方法。

我是Go和并发/并行编程的新手。为了试验goroutine(并希望看到它们的性能优势),我整理了一个小型测试程序,该程序简单地生成1亿个随机ints -首先在单个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)
    }
}

icza.. 5

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

从Go 1.5开始,GOMAXPROCS默认设置为可用的CPU内核数,因此无需进行设置(尽管它没有害处)。

要生成的数字:

var numIntsToGenerate = 100000000
var numIntsPerThread = numIntsToGenerate / numThreads

如果numThreads为3,则在使用多重goroutine的情况下,生成的数字会更少(由于整数除法),因此请更正它:

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()速度非常快。

让我们将“任务”修改为足够大,以便我们可以看到多个goroutine的好处:

// 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个随机数,并在生成返回值之前将其丢弃(以进行一些计算/消除时间)。我们这样做是为了使辅助goroutine的计算时间超过多个goroutine的通信开销。

现在运行该应用程序,我的结果在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

多goroutine版本的运行速度快了2.5倍。这意味着,如果您的goroutine以1000个块的形式传递随机数,则执行速度将提高2.5倍(与单个goroutine生成相比)。

最后一点:

您的单goroutine版本还使用了多个goroutine:1用于生成数字,而1用于收集结果。收集器很可能没有充分利用CPU内核,而只是等待结果,但仍然:使用2个CPU内核。让我们估计使用了“ 1.5”个CPU内核。而多goroutine版本使用4个CPU内核。粗略估算:4 / 1.5 = 2.66,非常接近我们的性能提升。



1> icza..:

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

从Go 1.5开始,GOMAXPROCS默认设置为可用的CPU内核数,因此无需进行设置(尽管它没有害处)。

要生成的数字:

var numIntsToGenerate = 100000000
var numIntsPerThread = numIntsToGenerate / numThreads

如果numThreads为3,则在使用多重goroutine的情况下,生成的数字会更少(由于整数除法),因此请更正它:

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()速度非常快。

让我们将“任务”修改为足够大,以便我们可以看到多个goroutine的好处:

// 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个随机数,并在生成返回值之前将其丢弃(以进行一些计算/消除时间)。我们这样做是为了使辅助goroutine的计算时间超过多个goroutine的通信开销。

现在运行该应用程序,我的结果在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

多goroutine版本的运行速度快了2.5倍。这意味着,如果您的goroutine以1000个块的形式传递随机数,则执行速度将提高2.5倍(与单个goroutine生成相比)。

最后一点:

您的单goroutine版本还使用了多个goroutine:1用于生成数字,而1用于收集结果。收集器很可能没有充分利用CPU内核,而只是等待结果,但仍然:使用2个CPU内核。让我们估计使用了“ 1.5”个CPU内核。而多goroutine版本使用4个CPU内核。粗略估算:4 / 1.5 = 2.66,非常接近我们的性能提升。

推荐阅读
周扒pi
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有