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