基于Go Routines优化无内置函数的矩阵乘法性能问询
Go协程实现矩阵乘法的优化方案
以下是你提供的矩阵乘法代码:
func main() { var wg sync.WaitGroup var col = 100 var row = 150 var randMatrixA [][]int var randMatrixB [][]int chA := make(chan [][]int) chB := make(chan [][]int) wg.Add(1) go genMat(row, col, chA, &wg) wg.Add(1) go genMat(col, row, chB, &wg) wg.Wait() randMatrixA = <-chA randMatrixB = <-chB fmt.Println(randMatrixA) fmt.Println(randMatrixB) fmt.Println("The Go Result of Matrix Multiplication = ") start := time.Now() c := doCalc(randMatrixA, randMatrixB) elapsed := time.Since(start) fmt.Println(c) fmt.Printf("Time taken to calculate %s \n", elapsed) } func genMat(row int, col int, ch chan<- [][]int, wg *sync.WaitGroup) { nM := make([][]int, col) for i := 0; i < col; i++ { nM[i] = make([]int, row) } generateNums(nM) wg.Done() ch <- nM } func generateNums(randMatrix [][]int) { for i, innerArray := range randMatrix { for j := range innerArray { randMatrix[i][j] = rand.Intn(100) } } } func rowCount(inM [][]int) int { return (len(inM)) } func colCount(inM [][]int) int { return (len(inM[0])) } func doCalc(inA [][]int, inB [][]int) [][]int { var i, j int var wg sync.WaitGroup chC := make(chan [][]int) m := rowCount(inA) // number of rows the first matrix p := rowCount(inB) // number of rows the second matrix q := colCount(inB) // number of columns the second matrix k := 0 total := 0 var nM [][]int wg.Add(1) go genMat(m, q, chC, &wg) nM = <-chC wg.Wait() for i = 0; i < m; i++ { for j = 0; j < q; j++ { for k = 0; k < p; k++ { total = total + inA[i][k]*inB[k][j] } nM[i][j] = total total = 0 } fmt.Println() } return nM }
当前你通过两个协程并行生成矩阵A、B,单个协程计算结果矩阵C,平均耗时约2.4ms。以下是基于Go协程且不使用内置函数的优化建议,可将耗时降至1-2ms甚至更低:
优化建议
1. 并行化矩阵乘法核心计算
当前doCalc函数是单线程遍历计算结果矩阵,完全没利用多核CPU。可以将结果矩阵的每一行计算分配给独立协程,用sync.WaitGroup等待所有行计算完成,直接拉满计算效率。
修改后的doCalc示例:
func doCalc(inA [][]int, inB [][]int) [][]int { m := rowCount(inA) p := rowCount(inB) q := colCount(inB) // 直接初始化结果矩阵,无需额外协程 nM := make([][]int, m) for i := range nM { nM[i] = make([]int, q) } var wg sync.WaitGroup wg.Add(m) for i := 0; i < m; i++ { // 启动协程计算第i行 go func(i int) { defer wg.Done() rowA := inA[i] // 提前取出当前行,减少索引访问开销 for j := 0; j < q; j++ { total := 0 for k := 0; k < p; k++ { total += rowA[k] * inB[k][j] } nM[i][j] = total } }(i) // 传递i的副本,避免循环变量引用问题 } wg.Wait() return nM }
2. 解决随机生成的并发锁竞争
rand.Intn不是并发安全的,多个协程调用时会触发内部互斥锁,拖慢矩阵生成速度。每个生成矩阵的协程应创建独立的随机源,完全避免锁开销:
修改generateNums函数:
func generateNums(randMatrix [][]int) { // 每个协程创建专属随机源 r := rand.New(rand.NewSource(time.Now().UnixNano())) for i, innerArray := range randMatrix { for j := range innerArray { randMatrix[i][j] = r.Intn(100) } } }
3. 移除不必要的IO与协程开销
原代码中的fmt.Println(randMatrixA)、fmt.Println(randMatrixB)以及doCalc里的fmt.Println()都是极耗时的IO操作,直接删除即可;同时doCalc中用协程初始化结果矩阵完全没必要,直接在函数内初始化,减少协程创建和通道通信的额外开销。
4. 优化内存布局提升缓存命中率
Go的二维切片是"切片的切片",内存地址不连续,访问时CPU缓存命中率低。可以改用一维数组模拟二维矩阵,通过索引计算定位元素,大幅提升缓存利用率:
修改后的doCalc(一维数组版本):
func doCalc(inA [][]int, inB [][]int) []int { m := rowCount(inA) p := rowCount(inB) q := colCount(inB) // 一维数组存储结果矩阵 nM := make([]int, m*q) var wg sync.WaitGroup wg.Add(m) for i := 0; i < m; i++ { go func(i int) { defer wg.Done() rowA := inA[i] baseIdx := i * q // 当前行的起始索引 for j := 0; j < q; j++ { total := 0 for k := 0; k < p; k++ { total += rowA[k] * inB[k][j] } nM[baseIdx+j] = total } }(i) } wg.Wait() return nM }
内容的提问来源于stack exchange,提问作者Subin Suthan
相关产品推荐
相关产品推荐

