为了账号安全,请及时绑定邮箱和手机立即绑定

使用 goroutine 和通道的“矩阵乘法”

使用 goroutine 和通道的“矩阵乘法”

Go
倚天杖 2022-04-20 19:25:01
当我使用 1 个 goroutine、2 个 goroutine、3 个等等时,我有一个大学项目来测试矩阵乘法的时间差。我必须使用渠道。我的问题是,无论我添加多少 goroutine,编译时间几乎总是相同的。也许有人能说出问题出在哪里。也许发送时间很长,并且一直在发送。代码如下package mainimport (    "fmt"    "math/rand"    "time")const length = 1000var start time.Timevar rez [length][length]intfunc 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]            }
查看完整描述

2 回答

?
慕妹3146593

TA贡献1820条经验 获得超9个赞

您看不到差异,因为准备数据以传递给 go 例程是您的瓶颈。它比执行计算更慢或一样快。


传递行和列的副本不是一个好的策略。这正在扼杀表演。


go 例程可以直接从只读输入矩阵中读取数据。这里没有可能的竞争条件。


输出也一样。如果 go 例程计算行和列的乘积,它会将结果写入不同的单元格。这里也没有可能的竞争条件。


要做的事情如下。定义一个包含两个字段的结构,一个用于行,一个用于要相乘的列。


用所有可能的行和列组合填充缓冲通道,以从 (0,0) 乘到 (n-1,m-1)。


go 例程使用通道中的结构,执行计算并将结果直接写入输出矩阵。


然后,您还有一个 done 通道向主 go 例程发出计算已完成的信号。当 goroutine 完成对结构 (n-1,m-1) 的处理时,它会关闭 done 通道。


编写完所有结构后,主 go 例程在 done 通道上等待。一旦完成通道关闭,它就会打印经过的时间。我们可以使用一个等待组来等待所有的 goroutine 终止他们的计算。


然后,您可以从一个 goroutine 开始,并增加 goroutine 的数量,以查看处理时间的影响。


见代码:


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()

}


查看完整回答
反对 回复 2022-04-20
?
慕哥6287543

TA贡献1831条经验 获得超10个赞

您的代码很难理解(调用变量 dummy1/dummy2 会让人感到困惑,特别是当它们在 中获得不同的名称时Calc)并且添加一些注释会使它更容易理解。


首先是一个错误。在发送要计算的数据之后dummy1 <- -1,我相信您希望这会等待所有计算完成。但是,当您有多个 goroutine 时,情况不一定如此。通道将被其中一个 goroutine 耗尽,并打印出计时信息;其他 goroutine 仍将运行(并且可能尚未完成计算)。


在时间方面,我怀疑您向 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

    }

}


查看完整回答
反对 回复 2022-04-20
  • 2 回答
  • 0 关注
  • 114 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信