"Matrix multiplication" 使用协程和通道
"Matrix multiplication" using goroutines and channels
我有一个大学项目,用于测试矩阵乘法的时间差,当我使用 1 个 goroutine、2 个 goroutine、3 个等等时。我必须使用频道。我的问题是,无论我添加了多少 go 例程,编译时间几乎总是相同的。也许有人可以说出问题出在哪里。也许那个发送很长,它一直在给予。代码如下
package main
import (
"fmt"
"math/rand"
"time"
)
const length = 1000
var start time.Time
var rez [length][length]int
func main() {
const threadlength = 1
toCalcRow := make(chan []int)
toCalcColumn := make(chan []int)
dummy1 := make(chan int)
dummy2 := make(chan int)
var row [length + 1]int
var column [length + 1]int
var a [length][length]int
var b [length][length]int
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
a[i][j] = rand.Intn(10)
b[i][j] = rand.Intn(10)
}
}
for i := 0; i < threadlength; i++ {
go Calc(toCalcRow, toCalcColumn, dummy1, dummy2)
}
start = time.Now()
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
row[0] = i
column[0] = j
for k := 0; k < length; k++ {
row[k+1] = a[i][j]
column[k+1] = b[i][k]
}
rowSlices := make([]int, len(row))
columnSlices := make([]int, len(column))
copy(rowSlices, row[:])
copy(columnSlices, column[:])
toCalcRow <- rowSlices
toCalcColumn <- columnSlices
}
}
dummy1 <- -1
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
fmt.Print(rez[i][j])
fmt.Print(" ")
}
fmt.Println(" ")
}
<-dummy2
close(toCalcRow)
close(toCalcColumn)
close(dummy1)
}
func Calc(chin1 <-chan []int, chin2 <-chan []int, dummy <-chan int, dummy1 chan<- int) {
loop:
for {
select {
case row := <-chin1:
column := <-chin2
var sum [3]int
sum[0] = row[0]
sum[1] = column[0]
for i := 1; i < len(row); i++ {
sum[2] += row[i] * column[i]
}
rez[sum[0]][sum[1]] = sum[2]
case <-dummy:
elapsed := time.Since(start)
fmt.Println("Binomial took ", elapsed)
dummy1 <- 0
break loop
}
}
close(dummy1)
}
您的代码很难理解(调用变量 dummy1/dummy2 会造成混淆,尤其是当它们在 Calc
中有不同的名称时),添加一些注释会使它更容易理解。
首先是一个错误。发送要计算的数据后,您 dummy1 <- -1
并且我相信您希望这会等待所有计算完成。但是,当您有多个 goroutine 时,情况就不一定如此了。该通道将被其中一个 goroutine 排空并打印出时间信息;其他 goroutines 仍将是 运行(并且可能尚未完成计算)。
就时间而言,我怀疑您向 go 例程发送数据的方式会减慢速度;您发送行,然后发送列;因为通道没有缓冲,goroutine 将在等待列时阻塞(切换回主 goroutine 以发送列)。这种来回会减慢你的 goroutines 获取数据的速度,并且可以很好地解释为什么添加额外的 goroutines 影响有限(如果你使用缓冲通道,它也会变得危险)。
我已经重构了你的代码(注意可能存在错误,而且它远非完美!)到确实显示出差异的东西(在我的电脑上 1 goroutine = 10s; 5 = 7s):
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
const length = 1000
var start time.Time
var rez [length][length]int
// toMultiply will hold details of what the goroutine will be multiplying (one row and one column)
type toMultiply struct {
rowNo int
columnNo int
row []int
column []int
}
func main() {
const noOfGoRoutines = 5
// Build up a matrix of dimensions (length) x (length)
var a [length][length]int
var b [length][length]int
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
a[i][j] = rand.Intn(10)
b[i][j] = rand.Intn(10)
}
}
// Setup completed so start the clock...
start = time.Now()
// Start off threadlength go routines to multiply each row/column
toCalc := make(chan toMultiply)
var wg sync.WaitGroup
wg.Add(noOfGoRoutines)
for i := 0; i < noOfGoRoutines; i++ {
go func() {
Calc(toCalc)
wg.Done()
}()
}
// Begin the multiplication.
start = time.Now()
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
tm := toMultiply{
rowNo: i,
columnNo: j,
row: make([]int, length),
column: make([]int, length),
}
for k := 0; k < length; k++ {
tm.row[k] = a[i][j]
tm.column[k] = b[i][k]
}
toCalc <- tm
}
}
// All of the data has been sent to the chanel; now we need to wait for all of the
// goroutines to complete
close(toCalc)
wg.Wait()
fmt.Println("Binomial took ", time.Since(start))
// The full result should be in tz
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
//fmt.Print(rez[i][j])
//fmt.Print(" ")
}
//fmt.Println(" ")
}
}
// Calc - Multiply a row from one matrix with a column from another
func Calc(toCalc <-chan toMultiply) {
for tc := range toCalc {
var result int
for i := 0; i < len(tc.row); i++ {
result += tc.row[i] * tc.column[i]
}
// warning - the below should work in this case but be careful writing to global variables from goroutines
rez[tc.rowNo][tc.columnNo] = result
}
}
您看不出有什么不同,因为准备数据以传递给 go 例程是您的瓶颈。它与执行计算一样慢或一样快。
传递行和列的副本不是一个好的策略。这正在扼杀性能。
go 例程可以直接从只读的输入矩阵读取数据。这里没有可能的竞争条件。
输出相同。如果 go 例程计算一行和一列的乘积,它会将结果写入不同的单元格。这里也没有可能的竞争条件。
接下来要做的就是。定义一个包含两个字段的结构,一个用于行,一个用于要相乘的列。
用行和列的所有可能组合填充缓冲通道,以从 (0,0) 乘以 (n-1,m-1)。
go 例程,使用来自通道的结构,执行计算并将结果直接写入输出矩阵。
然后您还有一个完成通道,用于向主 go 例程发出计算已完成的信号。当 go routine 完成处理结构 (n-1,m-1) 时,它关闭 done 通道。
go 主例程在写入所有结构后在 done 通道上等待。 done 通道关闭后,它会打印经过的时间。
我们可以使用一个等待组来等待所有的 go routine 终止他们的计算。
然后您可以从一个 go routine 开始并增加 go routines 的数量以查看处理时间的影响。
见代码:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
type pair struct {
row, col int
}
const length = 1000
var start time.Time
var rez [length][length]int
func main() {
const threadlength = 1
pairs := make(chan pair, 1000)
var wg sync.WaitGroup
var a [length][length]int
var b [length][length]int
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
a[i][j] = rand.Intn(10)
b[i][j] = rand.Intn(10)
}
}
wg.Add(threadlength)
for i := 0; i < threadlength; i++ {
go Calc(pairs, &a, &b, &rez, &wg)
}
start = time.Now()
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
pairs <- pair{row: i, col: j}
}
}
close(pairs)
wg.Wait()
elapsed := time.Since(start)
fmt.Println("Binomial took ", elapsed)
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
fmt.Print(rez[i][j])
fmt.Print(" ")
}
fmt.Println(" ")
}
}
func Calc(pairs chan pair, a, b, rez *[length][length]int, wg *sync.WaitGroup) {
for {
pair, ok := <-pairs
if !ok {
break
}
rez[pair.row][pair.col] = 0
for i := 0; i < length; i++ {
rez[pair.row][pair.col] += a[pair.row][i] * b[i][pair.col]
}
}
wg.Done()
}
我有一个大学项目,用于测试矩阵乘法的时间差,当我使用 1 个 goroutine、2 个 goroutine、3 个等等时。我必须使用频道。我的问题是,无论我添加了多少 go 例程,编译时间几乎总是相同的。也许有人可以说出问题出在哪里。也许那个发送很长,它一直在给予。代码如下
package main
import (
"fmt"
"math/rand"
"time"
)
const length = 1000
var start time.Time
var rez [length][length]int
func main() {
const threadlength = 1
toCalcRow := make(chan []int)
toCalcColumn := make(chan []int)
dummy1 := make(chan int)
dummy2 := make(chan int)
var row [length + 1]int
var column [length + 1]int
var a [length][length]int
var b [length][length]int
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
a[i][j] = rand.Intn(10)
b[i][j] = rand.Intn(10)
}
}
for i := 0; i < threadlength; i++ {
go Calc(toCalcRow, toCalcColumn, dummy1, dummy2)
}
start = time.Now()
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
row[0] = i
column[0] = j
for k := 0; k < length; k++ {
row[k+1] = a[i][j]
column[k+1] = b[i][k]
}
rowSlices := make([]int, len(row))
columnSlices := make([]int, len(column))
copy(rowSlices, row[:])
copy(columnSlices, column[:])
toCalcRow <- rowSlices
toCalcColumn <- columnSlices
}
}
dummy1 <- -1
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
fmt.Print(rez[i][j])
fmt.Print(" ")
}
fmt.Println(" ")
}
<-dummy2
close(toCalcRow)
close(toCalcColumn)
close(dummy1)
}
func Calc(chin1 <-chan []int, chin2 <-chan []int, dummy <-chan int, dummy1 chan<- int) {
loop:
for {
select {
case row := <-chin1:
column := <-chin2
var sum [3]int
sum[0] = row[0]
sum[1] = column[0]
for i := 1; i < len(row); i++ {
sum[2] += row[i] * column[i]
}
rez[sum[0]][sum[1]] = sum[2]
case <-dummy:
elapsed := time.Since(start)
fmt.Println("Binomial took ", elapsed)
dummy1 <- 0
break loop
}
}
close(dummy1)
}
您的代码很难理解(调用变量 dummy1/dummy2 会造成混淆,尤其是当它们在 Calc
中有不同的名称时),添加一些注释会使它更容易理解。
首先是一个错误。发送要计算的数据后,您 dummy1 <- -1
并且我相信您希望这会等待所有计算完成。但是,当您有多个 goroutine 时,情况就不一定如此了。该通道将被其中一个 goroutine 排空并打印出时间信息;其他 goroutines 仍将是 运行(并且可能尚未完成计算)。
就时间而言,我怀疑您向 go 例程发送数据的方式会减慢速度;您发送行,然后发送列;因为通道没有缓冲,goroutine 将在等待列时阻塞(切换回主 goroutine 以发送列)。这种来回会减慢你的 goroutines 获取数据的速度,并且可以很好地解释为什么添加额外的 goroutines 影响有限(如果你使用缓冲通道,它也会变得危险)。
我已经重构了你的代码(注意可能存在错误,而且它远非完美!)到确实显示出差异的东西(在我的电脑上 1 goroutine = 10s; 5 = 7s):
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
const length = 1000
var start time.Time
var rez [length][length]int
// toMultiply will hold details of what the goroutine will be multiplying (one row and one column)
type toMultiply struct {
rowNo int
columnNo int
row []int
column []int
}
func main() {
const noOfGoRoutines = 5
// Build up a matrix of dimensions (length) x (length)
var a [length][length]int
var b [length][length]int
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
a[i][j] = rand.Intn(10)
b[i][j] = rand.Intn(10)
}
}
// Setup completed so start the clock...
start = time.Now()
// Start off threadlength go routines to multiply each row/column
toCalc := make(chan toMultiply)
var wg sync.WaitGroup
wg.Add(noOfGoRoutines)
for i := 0; i < noOfGoRoutines; i++ {
go func() {
Calc(toCalc)
wg.Done()
}()
}
// Begin the multiplication.
start = time.Now()
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
tm := toMultiply{
rowNo: i,
columnNo: j,
row: make([]int, length),
column: make([]int, length),
}
for k := 0; k < length; k++ {
tm.row[k] = a[i][j]
tm.column[k] = b[i][k]
}
toCalc <- tm
}
}
// All of the data has been sent to the chanel; now we need to wait for all of the
// goroutines to complete
close(toCalc)
wg.Wait()
fmt.Println("Binomial took ", time.Since(start))
// The full result should be in tz
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
//fmt.Print(rez[i][j])
//fmt.Print(" ")
}
//fmt.Println(" ")
}
}
// Calc - Multiply a row from one matrix with a column from another
func Calc(toCalc <-chan toMultiply) {
for tc := range toCalc {
var result int
for i := 0; i < len(tc.row); i++ {
result += tc.row[i] * tc.column[i]
}
// warning - the below should work in this case but be careful writing to global variables from goroutines
rez[tc.rowNo][tc.columnNo] = result
}
}
您看不出有什么不同,因为准备数据以传递给 go 例程是您的瓶颈。它与执行计算一样慢或一样快。
传递行和列的副本不是一个好的策略。这正在扼杀性能。
go 例程可以直接从只读的输入矩阵读取数据。这里没有可能的竞争条件。
输出相同。如果 go 例程计算一行和一列的乘积,它会将结果写入不同的单元格。这里也没有可能的竞争条件。
接下来要做的就是。定义一个包含两个字段的结构,一个用于行,一个用于要相乘的列。
用行和列的所有可能组合填充缓冲通道,以从 (0,0) 乘以 (n-1,m-1)。
go 例程,使用来自通道的结构,执行计算并将结果直接写入输出矩阵。
然后您还有一个完成通道,用于向主 go 例程发出计算已完成的信号。当 go routine 完成处理结构 (n-1,m-1) 时,它关闭 done 通道。
go 主例程在写入所有结构后在 done 通道上等待。 done 通道关闭后,它会打印经过的时间。 我们可以使用一个等待组来等待所有的 go routine 终止他们的计算。
然后您可以从一个 go routine 开始并增加 go routines 的数量以查看处理时间的影响。
见代码:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
type pair struct {
row, col int
}
const length = 1000
var start time.Time
var rez [length][length]int
func main() {
const threadlength = 1
pairs := make(chan pair, 1000)
var wg sync.WaitGroup
var a [length][length]int
var b [length][length]int
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
a[i][j] = rand.Intn(10)
b[i][j] = rand.Intn(10)
}
}
wg.Add(threadlength)
for i := 0; i < threadlength; i++ {
go Calc(pairs, &a, &b, &rez, &wg)
}
start = time.Now()
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
pairs <- pair{row: i, col: j}
}
}
close(pairs)
wg.Wait()
elapsed := time.Since(start)
fmt.Println("Binomial took ", elapsed)
for i := 0; i < length; i++ {
for j := 0; j < length; j++ {
fmt.Print(rez[i][j])
fmt.Print(" ")
}
fmt.Println(" ")
}
}
func Calc(pairs chan pair, a, b, rez *[length][length]int, wg *sync.WaitGroup) {
for {
pair, ok := <-pairs
if !ok {
break
}
rez[pair.row][pair.col] = 0
for i := 0; i < length; i++ {
rez[pair.row][pair.col] += a[pair.row][i] * b[i][pair.col]
}
}
wg.Done()
}