Golang性能优化:这两个简单函数还能更快吗?

Golang性能优化:这两个简单函数还能更快吗? 我对自己的代码进行了基准测试,结果出乎意料,两个简单的函数竟然占用了相当长的计算时间。好吧,它们在一个while循环中被调用了很多很多次,但我没想到会这样。 这两个函数相当简单:

  1. 给定一个二维切片(NxN矩阵,其中N的范围是2-50)和一个矩阵列索引,返回该列的值作为一个切片。
func view(mm [][]int, columnIndex int) []int {
	column := make([]int, len(mm))
	for i, row := range mm {
		column[i] = row[columnIndex]
	}
	return column
}
  1. 给定一个二维切片(NxN矩阵,其中N的范围是2-30),返回矩阵的列和作为一个切片。
func colSums(mm [][]int) []int {
	sum := make([]int, len(mm[0]))
	for i := range mm[0] {
		psum := 0
		for j := range mm {
			psum = psum + mm[j][i]
		}
		sum[i] = psum
	}
	return sum
}

有没有办法让它们变得更好/更快?


更多关于Golang性能优化:这两个简单函数还能更快吗?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

如果你对矩阵进行转置,即让外层的切片代表列而非行,那么你将获得更好的引用局部性,并且能更稳定地命中缓存。

另一种选择是,在一个连续的切片中分配整个矩阵,然后为行或列构建子切片。

更多关于Golang性能优化:这两个简单函数还能更快吗?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你对代码进行过基准测试吗? 你能将其与以下代码进行比较吗?

  1. 给定一个二维切片(NxN 矩阵,其中 N 在 2-50 范围内)和一个矩阵列索引,将该列的值作为切片返回
func view(mm [][]int, columnIndex int) []int {
	column := make([]int, len(mm))
	for i := range mm {
		column[i] = mm[i][columnIndex]
	}
	return column
}
  1. 给定一个二维切片(NxN 矩阵,其中 N 在 2-30 范围内),将矩阵的列和作为切片返回
func colSums(mm [][]int) []int {
	sum := make([]int, len(mm[0]))
	for i := range mm[0] {
		for j := range mm {
			sum[i] += mm[j][i]
		}
	}
	return sum
}

这两个函数确实有优化空间,主要问题在于内存分配和访问模式。以下是优化方案:

1. 列视图函数优化

原函数每次调用都分配新切片,可以通过预分配复用内存:

func viewFast(mm [][]int, columnIndex int, buf []int) []int {
    for i := 0; i < len(mm); i++ {
        buf[i] = mm[i][columnIndex]
    }
    return buf[:len(mm)]
}

// 使用示例
func benchmark() {
    matrix := make([][]int, 50)
    // 初始化矩阵...
    
    buf := make([]int, 50) // 预分配最大可能大小
    for i := 0; i < 1000000; i++ {
        col := viewFast(matrix, 2, buf)
        _ = col // 使用列数据
    }
}

更彻底的优化是直接操作,避免复制:

func viewDirect(mm [][]int, columnIndex int, fn func(int)) {
    for i := 0; i < len(mm); i++ {
        fn(mm[i][columnIndex])
    }
}

// 使用示例
viewDirect(matrix, 2, func(val int) {
    // 直接处理每个值,避免切片分配
    process(val)
})

2. 列求和函数优化

原函数访问模式不佳(列优先),改为行优先访问:

func colSumsFast(mm [][]int) []int {
    if len(mm) == 0 {
        return nil
    }
    
    n := len(mm[0])
    sum := make([]int, n)
    
    for _, row := range mm {
        for j := 0; j < n; j++ {
            sum[j] += row[j]
        }
    }
    return sum
}

使用循环展开进一步优化:

func colSumsUnrolled(mm [][]int) []int {
    if len(mm) == 0 {
        return nil
    }
    
    n := len(mm[0])
    sum := make([]int, n)
    
    for _, row := range mm {
        j := 0
        // 每次处理4个元素
        for ; j+3 < n; j += 4 {
            sum[j] += row[j]
            sum[j+1] += row[j+1]
            sum[j+2] += row[j+2]
            sum[j+3] += row[j+3]
        }
        // 处理剩余元素
        for ; j < n; j++ {
            sum[j] += row[j]
        }
    }
    return sum
}

3. 组合优化方案

如果两个函数在同一个热点循环中,可以合并计算:

func computeColumnStats(mm [][]int, targetCol int) ([]int, []int) {
    if len(mm) == 0 {
        return nil, nil
    }
    
    n := len(mm[0])
    sums := make([]int, n)
    target := make([]int, len(mm))
    
    for i, row := range mm {
        target[i] = row[targetCol]
        for j := 0; j < n; j++ {
            sums[j] += row[j]
        }
    }
    
    return target, sums
}

基准测试对比

func BenchmarkOriginalView(b *testing.B) {
    matrix := make([][]int, 50)
    for i := range matrix {
        matrix[i] = make([]int, 50)
        for j := range matrix[i] {
            matrix[i][j] = i + j
        }
    }
    
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _ = view(matrix, 2)
    }
}

func BenchmarkOptimizedView(b *testing.B) {
    matrix := make([][]int, 50)
    for i := range matrix {
        matrix[i] = make([]int, 50)
        for j := range matrix[i] {
            matrix[i][j] = i + j
        }
    }
    
    buf := make([]int, 50)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _ = viewFast(matrix, 2, buf)
    }
}

优化后的版本应该能获得2-5倍的性能提升,具体取决于矩阵大小和调用频率。关键优化点:

  1. 减少内存分配(预分配和复用)
  2. 改善缓存局部性(行优先访问)
  3. 循环展开减少循环开销
回到顶部