【发布时间】:2020-03-01 23:52:02
【问题描述】:
我有两个程序。他们解决了一个线性方程组。它们都可以正常工作(它们产生相同的结果)。
第一个程序在没有并发的情况下工作。
第二个程序与第一个程序非常相似,不同之处在于我在某些地方添加了并行性。这些地方都用代码标出来了。
这里有两个程序:
第一个。没有并发。
package main
import (
"fmt"
"math"
"os"
"time"
)
func main() {
start := time.Now()
N := 1000
a := CreateRandomMatrix(N)
b := CreateRandomVector(N)
index := make([]int, len(a))
for i := range index {
index[i] = i
}
for i := 0; i < len(a); i++ {
r := a[i][index[i]]
var kk int
var maxElemInRow float64
for k := i; k < len(a); k++ {
if math.Abs(a[i][index[k]]) > maxElemInRow {
kk = k
maxElemInRow = math.Abs(a[i][index[k]])
}
}
index[i], index[kk] = index[kk], index[i]
r = a[i][index[i]]
if r == 0 {
if b[i] == 0 {
fmt.Println("a lot of solutions")
} else {
fmt.Println("no solutions")
}
os.Exit(1)
}
for j := 0; j < len(a[i]); j++ {
a[i][index[j]] /= r
}
b[i] /= r
for k := i + 1; k < len(a); k++ {
r = a[k][index[i]]
for j := 0; j < len(a[i]); j++ {
a[k][index[j]] = a[k][index[j]] - a[i][index[j]]*r
}
b[k] = b[k] - b[i]*r
}
}
var x vector = make(vector, len(b))
for i := len(a) - 1; i >= 0; i-- {
x[i] = b[i]
for j := i + 1; j < len(a); j++ {
x[i] = x[i] - (x[j] * a[i][index[j]])
}
}
result := make([]string, len(x))
for i, val := range index {
result[val] = fmt.Sprintf("%.2f", x[i])
}
fmt.Println("tested part took:", time.Now().Sub(start))
}
第二个:
package main
import (
"fmt"
"math"
"os"
"sync"
"time"
)
const (
workers = 8
)
var wg sync.WaitGroup
func main() {
start := time.Now()
N := 1000
a := CreateRandomMatrix(N)
b := CreateRandomVector(N)
index := make([]int, len(a))
for i := range index {
index[i] = i
}
for i := 0; i < len(a); i++ {
r := a[i][index[i]]
var kk int
var max float64
for k := i; k < len(a); k++ {
if math.Abs(a[i][index[k]]) > max {
kk = k
max = math.Abs(a[i][index[k]])
}
}
index[i], index[kk] = index[kk], index[i]
r = a[i][index[i]]
if r == 0 {
if b[i] == 0 {
fmt.Println("a lot of solutions")
} else {
fmt.Println("no solutions")
}
os.Exit(1)
}
// concurrency here
for w := 0; w < workers; w++ {
wg.Add(1)
go func(w int) {
start := len(a[i]) / workers * w
end := len(a[i]) / workers * (w + 1)
if end > len(a[i]) {
end = len(a[i])
}
for j := start; j < end; j++ {
a[i][index[j]] /= r
}
wg.Done()
}(w)
}
b[i] /= r
wg.Wait()
for k := i + 1; k < len(a); k++ {
r = a[k][index[i]]
for j := 0; j < len(a[i]); j++ {
a[k][index[j]] = a[k][index[j]] - a[i][index[j]]*r
}
b[k] = b[k] - b[i]*r
}
}
var x vector = make(vector, len(b))
for i := len(a) - 1; i >= 0; i-- {
x[i] = b[i]
for j := i + 1; j < len(a); j++ {
x[i] = x[i] - (x[j] * a[i][index[j]])
}
}
result := make([]string, len(x))
for i, val := range index {
result[val] = fmt.Sprintf("%.2f", x[i])
}
fmt.Println("tested part took:", time.Now().Sub(start))
}
两个程序的附加代码块相同
package main
import "math/rand"
type matrix [][]float64
type vector []float64
func CreateRandomMatrix(n int) matrix {
m := make(matrix, n)
for i := 0; i < n; i++ {
m[i] = make(vector, n)
for j := 0; j < n; j++ {
m[i][j] = float64(rand.Intn(100))
}
}
return m
}
func CreateRandomVector(n int) vector {
v := make(vector, n)
for i := 0; i < n; i++ {
v[i] = float64(rand.Intn(100))
}
return v
}
所以。这是问题所在:
理论上,第二个程序应该运行得更快,因为一些计算分布在处理器内核上。但这不会发生。每次添加并行元素时,第二个程序都会开始变慢。
我测试了 N 的大值以及小值。第二版程序的运行时间明显落后于第一版。比如你设置N=3500,执行的时间差大概是10秒左右。
此外,如果您将工作人员的数量设置为 1,则第二个程序开始运行得更快。
为什么会这样?我在某个地方犯了错误吗?如何让分布式计算加速程序?
开始版本: 1.14。但我也在 1.13 版本上检查了这段代码。
添加:我发现如果程序使用大矩阵大小,那么并行版本开始赶上顺序版本。
编辑摘要:在第二个程序中,在计算kk 和max 的地方删除了一个具有并行性的部分,以消除数据竞争。
【问题讨论】:
-
您将并行性和并发性混为一谈。在当前版本的 Go 中,goroutines 不是可抢占的,因此像你这样具有紧密计算循环的 goroutines 不会屈服于其他 goroutines。您的程序可能仍会受益于多核,但这取决于这些 goroutine 的调度方式。
-
另请注意,您的并行版本存在数据竞争。使用
--race运行它以查看它们。允许编译器重写你的代码,这样最大检查就不会像你期望的那样工作,因为它可以假设每个 goroutine 在访问maxElemInRow和kk时是完全独立的,并相应地进行优化。 (比如它可以假设maxElemInRow后面不能改,所以如果它只是写它,它就不必读它。所以goroutine A可以写5,然后goroutine B写10,然后goroutine A写 7,因为它超过 5。) -
@BurakSerdar:“在当前版本的 Go 中,goroutines 不可抢占,” False。 Goroutines 现在是异步可抢占的。 golang.org/doc/go1.14
-
@peterSO 你是对的,这里仍然是 1.13,是时候升级了。虽然 OP 没有指定版本...
-
您假设您受 CPU 限制。你可能记忆力有限。
标签: go concurrency