Go并发:Chudnovky';s算法,比同步慢

Go并发:Chudnovky';s算法,比同步慢,go,concurrency,parallel-processing,Go,Concurrency,Parallel Processing,在朋友的推荐下,我最近才开始学习围棋。到目前为止,我很喜欢它,但我写了(我认为会是)轻量级并发功能的完美例子,并得到了一个令人惊讶的结果。。。所以我怀疑我做错了什么,或者我误解了goroutines有多贵。我希望这里的一些地鼠能提供一些见解 我用goroutines和简单的同步执行在Go中编写了Chudnovsky的算法。我假设,由于每个计算都独立于其他计算,所以并发运行至少会快一点 注意:我在第5代i7上运行这个程序,所以如果goroutine像我所说的那样被多路复用到线程上,这应该是并发的和

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

我用goroutines和简单的同步执行在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
}

您正在进行的计算太简单,无法在单独的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
}

您正在进行的计算太简单,无法在单独的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
}

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


我注意到的一件有趣的事情是,通过大量的重复,同步版本实际上需要的时间是并行版本的四倍多。我猜这与Intel的超线程体系结构有关,该体系结构允许每个内核具有一定程度的并行性,但我不确定这一点。

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


我注意到的一件有趣的事情是,通过大量的重复,同步版本实际上需要的时间是并行版本的四倍多。我猜这与Intel的超线程体系结构有关,该体系结构允许每个内核具有一定程度的并行性,但我不确定这一点。

goroutines很便宜,不是免费的。
go version
命令的输出是什么?go version go1.6 linux/AMD64错误警报:您的示例,20次迭代,溢出了阶乘函数。goroutines很便宜,它们不是免费的。
go version
命令的输出是什么?go version go1.6 linux/amd64BUG警报:您的示例,20次迭代,溢出了阶乘函数。
$ 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