我实现了两个函数,用于将输入数组向右旋转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

        // h和le even length arrays where i+k might equal an already seen index
        if nextI == iAlreadySeen {
            i = (i + 1) % len(nums)
            iAlreadySeen = i
            current = nums[i]
        }
    }
}

在进行基准测试时,我惊讶地发现,当两个函数的n等于0时,速度相差+20倍.

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=.始终会产生这样的结果:

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

I don't underst和 this performance difference as both functions are basically doing the same thing 和 exiting early in the if k == 0 condition.

Could someone help me underst和 this?

go version go1.18 linux/amd64

推荐答案

您看到的是编译器优化的结果.编译器足够聪明,可以通过内联函数调用使应用程序更快.有时,编译器会优化以删除函数调用,并人为地降低基准测试的运行时间(这可能很复杂).

在分析之后,我们可以注意到在基准测试执行期间甚至没有调用函数rotateRight1:

(pprof) list BenchmarkRotateRight1
Total: 260ms
ROUTINE ======================== main_test.BenchmarkRotateRight1 in (edited)
     260ms      260ms (flat, cum)   100% of Total
         .          .     43:func BenchmarkRotateRight1(b *testing.B) {
         .          .     44:   nums := make([]int, 5_000)
         .          .     45:
         .          .     46:   b.ResetTimer()
         .          .     47:   b.ReportAllocs()
     260ms      260ms     48:   for i := 0; i < b.N; i++ {
         .          .     49:       rotateRight1(nums, 0)
         .          .     50:   }
         .          .     51:}

另一方面,正在调用rotateRight2,这就是为什么您会看到基准运行时的差异:

(pprof) list BenchmarkRotateRight2
Total: 1.89s
ROUTINE ======================== main_test.BenchmarkRotateRight2 in (edited)
     180ms      1.89s (flat, cum)   100% of Total
         .          .     53:func BenchmarkRotateRight2(b *testing.B) {
         .          .     54:   nums := make([]int, 5_000)
         .          .     55:
         .          .     56:   b.ResetTimer()
         .          .     57:   b.ReportAllocs()
     130ms      130ms     58:   for i := 0; i < b.N; i++ {
      50ms      1.76s     59:       rotateRight2(nums, 0)
         .          .     60:   }
         .          .     61:}

如果用-gcflags '-m -m'(双-m)运行基准测试,您将看到一些编译器优化代码的决定:

$ go test -gcflags '-m -m' -benchmem -bench . main_test.go

# command-line-arguments_test [command-line-arguments.test]
./main_test.go:7:6: can inline rotateRight1 with cost 40 as: func([]int, 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) }
./main_test.go:20:6: cannot inline rotateRight2: function too complex: cost 83 exceeds budget 80
...

因此,编译器根据复杂度阈值决定是否进行优化.

您可以在要禁用内联优化的函数之前使用//go:noinline指令.这将覆盖编译器的常规优化规则:

//go:noinline
func rotateRight1(nums []int, n int) {
...
}

现在你会注意到基准非常相似:

cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkRotateRight1
BenchmarkRotateRight1-16        135554571            8.886 ns/op           0 B/op          0 allocs/op
BenchmarkRotateRight2
BenchmarkRotateRight2-16        143716638            8.775 ns/op           0 B/op          0 allocs/op
PASS

Go相关问答推荐

Makefile:现有文件上没有这样的文件或目录,不加载环境变量

AWS S3 SelectObjectContent在AWS SDK v2 for Go中不返回结果

map 中的多个函数类型,Golang

如何确定泛型类型在运行时是否可比较?

使用 Golang 获取目录磁盘使用情况

xml.Unmarshal 不支持的类型 struct

Go 切片容量增长率

使用图像解码 JPEG 时 colored颜色 不正确.解码并写入 PDF?

GOLANG:为什么 SetDeadline/SetReadDeadline/SetWriteDeadline 在使用 os.File.Fd() 时对文件不起作用?

emersion/go-imap - imap.FetchRFC822:无效内存地址或零指针取消引用

golang pic.ShowImage 为什么它不生成图像而是向我发送base64值

如何将具有嵌入式 struct 的 struct 展平为 json

如何在 Golang 中使用具有相同名称或特定关键字的行或列重新排列/排序 CSV

GoLang 遍历 yaml 文件

在 Golang 中使用 OR 条件验证 struct 的两个字段

在 Go 中将十六进制转换为带符号的 Int

什么是无效字符实体 &ccb

如何在眼镜蛇(golang)中将标志作为参数传递?

如何动态解析 Go Fiber 中的请求正文?

并发 map map导致的 Golang 数据竞争