【问题标题】:Go Concurrency: Chudnovky's algorithm, slower than syncGo Concurrency:Chudnovky 算法,比同步慢
【发布时间】:2016-03-19 16:14:09
【问题描述】:

我最近在朋友的推荐下开始学习围棋。到目前为止,我很喜欢它,但是我写了(我认为会是)轻量级并发功能的完美示例,并得到了令人惊讶的结果......所以我怀疑我做错了什么,或者我是误解了 goroutine 的昂贵程度。我希望这里的一些地鼠可以提供见解。

我使用 goroutine 和简单的同步执行在 Go 中编写了 Chudnovsky 算法。我假设,每个计算都独立于其他计算,并发运行至少会快一点。

注意:我在第 5 代 i7 上运行它,所以如果按照我所说的将 goroutine 多路复用到线程上,这应该是并发 并行的。

 package main

import (
  "fmt"
  "math"
  "strconv"
  "time"
)

func main() {
  var input string
  var sum float64
  var pi float64
  c := make(chan float64)

  fmt.Print("How many iterations? ")
  fmt.Scanln(&input)
  max,err := strconv.Atoi(input)

  if err != nil {
    panic("You did not enter a valid integer")
  }
  start := time.Now() //start timing execution of concurrent routine

  for i := 0; i < max; i++ {
    go chudnovskyConcurrent(i,c)
  }

  for i := 0; i < max; i++ {
    sum += <-c
  }
  end := time.Now() //end of concurrent routine
  fmt.Println("Duration of concurrent calculation: ",end.Sub(start))
  pi = 1/(12*sum)
  fmt.Println(pi)

  start = time.Now() //start timing execution of syncronous routine
  sum = 0
  for i := 0; i < max; i++ {
    sum += chudnovskySync(i)
  }
  end = time.Now() //end of syncronous routine
  fmt.Println("Duration of synchronous calculation: ",end.Sub(start))
  pi = 1/(12*sum)
  fmt.Println(pi)
}

func chudnovskyConcurrent(i int, c chan<- float64) {
  var numerator float64
  var denominator float64
  ifloat := float64(i)
  iun := uint64(i)
  numerator = math.Pow(-1, ifloat) * float64(factorial(6*iun)) * (545140134*ifloat+13591409)
  denominator = float64(factorial(3*iun)) * math.Pow(float64(factorial(iun)),3) * math.Pow(math.Pow(640320,3),ifloat+0.5)
  c <- numerator/denominator
}

func chudnovskySync(i int) (r float64) {
  var numerator float64
  var denominator float64
  ifloat := float64(i)
  iun := uint64(i)
  numerator = math.Pow(-1, ifloat) * float64(factorial(6*iun)) * (545140134*ifloat+13591409)
  denominator = float64(factorial(3*iun)) * math.Pow(float64(factorial(iun)),3) * math.Pow(math.Pow(640320,3),ifloat+0.5)
  r = numerator/denominator
  return
}

func factorial(n uint64) (res uint64) {
  if ( n > 0 ) {
    res = n * factorial(n-1)
    return res
  }

  return 1
}

这是我的结果:

How many iterations? 20
Duration of concurrent calculation:  573.944µs
3.1415926535897936
Duration of synchronous calculation:  63.056µs
3.1415926535897936

【问题讨论】:

  • goroutine 很便宜,它们不是免费的。 go version 命令的输出是什么?
  • go 版本 go1.6 linux/amd64
  • BUG ALERT:您的示例,20 次迭代,溢出了您的阶乘函数。

标签: go concurrency parallel-processing


【解决方案1】:

您所做的计算过于简单,无法在单独的 goroutine 中完成每一项。与实际计算相比,您在运行时(创建 goroutine、多路复用、调度等)中损失的时间更多。您正在尝试做的事情更适合 GPU,例如,您拥有大量并行执行单元,可以在瞬间完成这些简单的计算。但您需要其他语言和 API 才能做到这一点。

您可以做的是为每个硬件执行线程创建软件执行线程。您想将 max 变量拆分为大块并并行执行它们。这里有一个非常简单的看法,只是为了说明这个想法:

package main

import (
  "fmt"
  "math"
  "strconv"
  "time"
  "runtime"
)

func main() {
  var input string
  var sum float64
  var pi float64
  c := make(chan float64, runtime.GOMAXPROCS(-1))
  fmt.Print("How many iterations? ")
  fmt.Scanln(&input)
  max,err := strconv.Atoi(input)

  if err != nil {
    panic("You did not enter a valid integer")
  }
  start := time.Now() //start timing execution of concurrent routine

  for i := 0; i < runtime.GOMAXPROCS(-1); i++ {
    go func(i int){
      var sum float64
      for j := 0; j < max/runtime.GOMAXPROCS(-1); j++  {
        sum += chudnovskySync(j + i*max/runtime.GOMAXPROCS(-1))
      }
      c <- sum
    }(i)
  }

  for i := 0; i < runtime.GOMAXPROCS(-1); i++ {
    sum += <-c
  }
  end := time.Now() //end of concurrent routine
  fmt.Println("Duration of concurrent calculation: ",end.Sub(start))
  pi = 1/(12*sum)
  fmt.Println(pi)

  start = time.Now() //start timing execution of syncronous routine
  sum = 0
  for i := 0; i < max; i++ {
    sum += chudnovskySync(i)
  }
  end = time.Now() //end of syncronous routine
  fmt.Println("Duration of synchronous calculation: ",end.Sub(start))
  pi = 1/(12*sum)
  fmt.Println(pi)
}

func chudnovskySync(i int) (r float64) {
  var numerator float64
  var denominator float64
  ifloat := float64(i)
  iun := uint64(i)
  numerator = math.Pow(-1, ifloat) * float64(factorial(6*iun)) * (545140134*ifloat+13591409)
  denominator = float64(factorial(3*iun)) * math.Pow(float64(factorial(iun)),3) * math.Pow(math.Pow(640320,3),ifloat+0.5)
  r = numerator/denominator
  return
}

func factorial(n uint64) (res uint64) {
  if ( n > 0 ) {
    res = n * factorial(n-1)
    return res
  }

  return 1
}

这是结果

$ go version
go version go1.5.2 windows/amd64

$ go run main.go
GOMAXPROCS = 4
How many iterations? 10000
Duration of concurrent calculation:  932.8916ms
NaN
Duration of synchronous calculation:  2.0639744s
NaN 

【讨论】:

    【解决方案2】:

    我同意,您的计算没有做足够的处理来克服拥有多个 goroutine 的开销。只是为了好玩,我修改了你的代码,在返回结果之前多次计算(1000、10000、100000、1000000)。我在四核 Xeon 上运行的 Mac OS X Yosemite 下运行了这个(进行了 20 次迭代),正如您所料,同步版本的运行时间大约是并行版本的四倍。

    我注意到一个有趣的事情是,在大量重复的情况下,同步版本实际上花费的时间是并行版本的四倍多。我猜这与英特尔的超线程架构有关,该架构允许每个内核内具有某种程度的并行性,但我不确定。

    【讨论】:

      【解决方案3】:

      由于产生线程的开销,您可能需要对每个线程执行多项序列以更快。 例如下面的代码,每个线程使用更少的线程和更多的术语(但使用不同的算法)

      package main
      
      import (
          "fmt"
          "math/rand"
          "runtime"
          "time"
      )
      
      var precision int = 2147483647
      var inside int = 0
      var size int = 2147483647
      var threads int = runtime.NumCPU()
      
      func getPi(total int) float64 {
          return (float64(inside) / float64(total)) * 4
      }
      
      func addpoints(times int, out chan int) {
          r1 := rand.New(rand.NewSource(time.Now().UnixNano()))
          var x, y, ret int
          ret = 0
          for i := 0; i < times; i++ {
              x = r1.Intn(size)
              y = r1.Intn(size)
              if ((x * x) + (y * y)) < (size * size) {
                  ret += 1
              }
          }
          out <- ret
      }
      
      func main() {
          fmt.Println("running on " + fmt.Sprint(threads) + " threads")
          start := time.Now()
          results := make(chan int, threads)
          for i := 0; i < threads; i++ {
              go addpoints(precision/threads, results)
          }
          for i := 0; i < threads; i++ {
              inside += <-results
          }
          duration := time.Since(start)
          fmt.Println(getPi(precision))
          fmt.Println("compute time: " + fmt.Sprint(duration))
      }
      

      【讨论】:

        猜你喜欢
        • 2023-04-09
        • 2015-12-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-02-28
        • 2023-03-25
        • 2020-06-07
        相关资源
        最近更新 更多