You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go含相同提前返回逻辑的数组右旋函数基准性能差异问题

Go数组右旋函数n=0时20倍性能差问题

问题背景

我实现了两个将输入数组 n 次向右旋转的函数。
两种实现均包含提前退出逻辑:当完成全部旋转后结果与原数组相等时,函数不执行任何操作直接返回,该场景出现在 n 为0或为数组长度整数倍的情况下。

第一个实现(拷贝临时数组方案):

func rotateRight1(nums []int, n int) {
    n = n % len(nums)
    if n == 0 {
        return
    }

    lastNDigits := make([]int, n)
    copy(lastNDigits, nums[len(nums)-n:])

    copy(nums[n:], nums[:len(nums)-n])
    copy(nums[:n], lastNDigits)
}

第二个实现(原地环状替换方案):

func rotateRight2(nums []int, n int) {
    n = n % len(nums)
    if n == 0 {
        return
    }

    i := 0
    current := nums[i]
    iAlreadySeen := i
    for j := 0; j < len(nums); j++ {
        nextI := (i + n) % len(nums)

        nums[nextI], current = current, nums[nextI]

        i = nextI

        // 处理数组长度为偶数时,步长可能提前回到已访问索引的问题
        if nextI == iAlreadySeen {
            i = (i + 1) % len(nums)
            iAlreadySeen = i
            current = nums[i]
        }
    }
}

基准测试代码如下:

func BenchmarkRotateRight1(b *testing.B) {
    nums := make([]int, 5_000)

    b.ResetTimer()
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        rotateRight1(nums, 0)
    }
}

func BenchmarkRotateRight2(b *testing.B) {
    nums := make([]int, 5_000)

    b.ResetTimer()
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        rotateRight2(nums, 0)
    }
}

执行 go test -bench=. 命令在 go version go1.18 linux/amd64 环境下稳定得到如下测试结果:

cpu: Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz
BenchmarkRotateRight1-4         1000000000               0.4603 ns/op          0 B/op          0 allocs/op
BenchmarkRotateRight2-4         97236492                12.11 ns/op            0 B/op          0 allocs/op
PASS

两个函数都会在 n == 0 时触发提前返回,逻辑看起来完全一致,却出现了20倍以上的性能差距。


原因说明

性能差异完全来自Go 1.18编译器的优化粒度区别,和代码逻辑正确性无关:

  • 对于rotateRight1,n==0分支之后的所有代码(切片分配、内存拷贝)都依赖n>0的前提才会执行,编译器可以明确判断出n=0时整个函数没有任何内存访问、没有任何副作用,因此直接把基准测试循环里的函数调用完全优化掉了。测出来的0.46ns/op本质是空循环的耗时,根本没有实际执行函数调用逻辑。
  • 对于rotateRight2,if n==0 {return}的代码后面紧跟着current := nums[i]这类固定访问切片下标0的操作。哪怕n=0时这些代码永远不会运行,Go 1.18的编译器做不到跨分支做这么深的无效代码消除,也没法把这个函数完全内联优化为空操作,每次调用都需要执行完整的函数栈准备、取切片长度、取模、条件判断流程,耗时自然就落到了十几纳秒的水平。

验证方式很简单:把rotateRight2里if n==0 {return}之后的所有代码全部注释掉,再跑基准测试,两个函数的性能就会完全一致。


内容的提问来源于stack exchange,提问作者Rafael FT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 15:06:20