猿问

当我将切片参数作为值或指针传递时,为什么会有性能差异?

我有以下代码:


func AddToSliceByValue(mySlice []int) {

    for idx := range mySlice {

        mySlice[idx]++

    }

}


func AddToSliceByPointer(mySlice *[]int) {

    for idx := range *mySlice {

        (*mySlice)[idx]++

    }

}

我的第一个想法是性能应该几乎相同,因为按值传递复制切片标头并按指针传递会迫使我取消引用指针,但我的基准测试显示了其他内容:


func BenchmarkAddByValue(b *testing.B) {

    mySlice := rand.Perm(1000)

    for n := 0; n < b.N; n++ {

        AddToSliceByValue(mySlice)

    }

}


func BenchmarkAddByPointer(b *testing.B) {

    mySlice := rand.Perm(1000)

    for n := 0; n < b.N; n++ {

        AddToSliceByPointer(&mySlice)

    }

}

基准AddByValue-12 1151256 1035 ns/op


BenchmarkAddByPointer-12 2145110 525 ns/op


谁能向我解释为什么性能差异如此之大?


我还为这两个函数添加了汇编代码。


交互式爱情
浏览 120回答 3
3回答

森林海

我无法重现您的基准...package main_testimport (&nbsp; &nbsp; "math/rand"&nbsp; &nbsp; "testing")func AddToSliceByValue(mySlice []int) {&nbsp; &nbsp; for idx := range mySlice {&nbsp; &nbsp; &nbsp; &nbsp; mySlice[idx]++&nbsp; &nbsp; }}func AddToSliceByPointer(mySlice *[]int) {&nbsp; &nbsp; for idx := range *mySlice {&nbsp; &nbsp; &nbsp; &nbsp; (*mySlice)[idx]++&nbsp; &nbsp; }}func BenchmarkAddByValue(b *testing.B) {&nbsp; &nbsp; mySlice := rand.Perm(1000)&nbsp; &nbsp; for n := 0; n < b.N; n++ {&nbsp; &nbsp; &nbsp; &nbsp; AddToSliceByValue(mySlice)&nbsp; &nbsp; }}func BenchmarkAddByPointer(b *testing.B) {&nbsp; &nbsp; mySlice := rand.Perm(1000)&nbsp; &nbsp; for n := 0; n < b.N; n++ {&nbsp; &nbsp; &nbsp; &nbsp; AddToSliceByPointer(&mySlice)&nbsp; &nbsp; }}$ go test -bench=. -benchmem -count=4goos: linuxgoarch: amd64pkg: test/bencsliceBenchmarkAddByValue-4&nbsp; &nbsp; &nbsp; &nbsp; 3010280&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;385 ns/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 B/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 allocs/opBenchmarkAddByValue-4&nbsp; &nbsp; &nbsp; &nbsp; 3118990&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;385 ns/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 B/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 allocs/opBenchmarkAddByValue-4&nbsp; &nbsp; &nbsp; &nbsp; 3117450&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;384 ns/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 B/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 allocs/opBenchmarkAddByValue-4&nbsp; &nbsp; &nbsp; &nbsp; 3109251&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;386 ns/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 B/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 allocs/opBenchmarkAddByPointer-4&nbsp; &nbsp; &nbsp; 2012487&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;610 ns/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 B/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 allocs/opBenchmarkAddByPointer-4&nbsp; &nbsp; &nbsp; 2009690&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;594 ns/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 B/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 allocs/opBenchmarkAddByPointer-4&nbsp; &nbsp; &nbsp; 2009222&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;594 ns/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 B/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 allocs/opBenchmarkAddByPointer-4&nbsp; &nbsp; &nbsp; 1850820&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;596 ns/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 B/op&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 allocs/opPASSok&nbsp; &nbsp; &nbsp; test/bencslice&nbsp; 13.476s$ go versiongo version go1.15.2 linux/amd64无论如何,行为可能取决于许多因素,首先是运行时的版本。只要您可以测试、重现和监控,了解内塞就没什么意义。

qq_花开花谢_0

我发现我的方差太高了:AddByValue-12&nbsp; &nbsp; 5.41µs ±15%AddByPointer-12&nbsp; 5.30µs ± 4%我能够减少测试结果的go test -benchmem -count 5&nbsp; -benchtime=1000000x -bench=. ./...方差,并且可以确认我的第一个假设,即结果应该大致相等:AddByValue-12&nbsp; &nbsp; 5.04µs ± 1%AddByPointer-12&nbsp; 5.17µs ± 1%根据评论,高差异的主要原因是我没有在基准设置后重置计时器。使用以下代码和较短的工作时间,我还减少了方差:func BenchmarkAddByValue(b *testing.B) {&nbsp; &nbsp; mySlice := rand.Perm(10000)&nbsp; &nbsp; b.ResetTimer()&nbsp; &nbsp; for n := 0; n < b.N; n++ {&nbsp; &nbsp; &nbsp; &nbsp; AddToSliceByValue(mySlice)&nbsp; &nbsp; }}func BenchmarkAddByPointer(b *testing.B) {&nbsp; &nbsp; mySlice := rand.Perm(10000)&nbsp; &nbsp; b.ResetTimer()&nbsp; &nbsp; for n := 0; n < b.N; n++ {&nbsp; &nbsp; &nbsp; &nbsp; AddToSliceByPointer(&mySlice)&nbsp; &nbsp; }}结果:AddByValue-12&nbsp; &nbsp; 5.03µs ± 0%AddByPointer-12&nbsp; 5.17µs ± 1%非常感谢你的帮助!

jeck猫

这是“低级”语言的普遍问题。当您按值传递时,这意味着您实际上是在复制数据。这是它的工作原理。当您通过引用传递时:引用的副本被创建并传递给方法(引用很可能是 8 个字节,所以这很快)你读出了引用后面的数据(这也很快,因为引用很可能在 CPU 缓存中)在按值传递的情况下:分配内存块来存储您传入的数据(慢)数据被复制到新分配的内存块中(可能快,也可能慢)然后通过引用访问您的数据(可能很慢,也可能很快,取决于数据是否在缓存中)
随时随地看视频慕课网APP

相关分类

Go
我要回答