为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
交互式爱情 2022-07-11 17:22:28
我有以下代码: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/opBenchmarkAddByPointer-12 2145110 525 ns/op谁能向我解释为什么性能差异如此之大?我还为这两个函数添加了汇编代码。
查看完整描述

3 回答

?
森林海

TA贡献2011条经验 获得超2个赞

我无法重现您的基准...


package main_test


import (

    "math/rand"

    "testing"

)


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)

    }

}

$ go test -bench=. -benchmem -count=4

goos: linux

goarch: amd64

pkg: test/bencslice

BenchmarkAddByValue-4        3010280           385 ns/op           0 B/op          0 allocs/op

BenchmarkAddByValue-4        3118990           385 ns/op           0 B/op          0 allocs/op

BenchmarkAddByValue-4        3117450           384 ns/op           0 B/op          0 allocs/op

BenchmarkAddByValue-4        3109251           386 ns/op           0 B/op          0 allocs/op

BenchmarkAddByPointer-4      2012487           610 ns/op           0 B/op          0 allocs/op

BenchmarkAddByPointer-4      2009690           594 ns/op           0 B/op          0 allocs/op

BenchmarkAddByPointer-4      2009222           594 ns/op           0 B/op          0 allocs/op

BenchmarkAddByPointer-4      1850820           596 ns/op           0 B/op          0 allocs/op

PASS

ok      test/bencslice  13.476s

$ go version

go version go1.15.2 linux/amd64

无论如何,行为可能取决于许多因素,首先是运行时的版本。只要您可以测试、重现和监控,了解内塞就没什么意义。


查看完整回答
反对 回复 2022-07-11
?
qq_花开花谢_0

TA贡献1835条经验 获得超7个赞

我发现我的方差太高了:


AddByValue-12    5.41µs ±15%

AddByPointer-12  5.30µs ± 4%

我能够减少测试结果的go test -benchmem -count 5  -benchtime=1000000x -bench=. ./...方差,并且可以确认我的第一个假设,即结果应该大致相等:


AddByValue-12    5.04µs ± 1%

AddByPointer-12  5.17µs ± 1%

根据评论,高差异的主要原因是我没有在基准设置后重置计时器。


使用以下代码和较短的工作时间,我还减少了方差:


func BenchmarkAddByValue(b *testing.B) {

    mySlice := rand.Perm(10000)

    b.ResetTimer()

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

        AddToSliceByValue(mySlice)

    }

}


func BenchmarkAddByPointer(b *testing.B) {

    mySlice := rand.Perm(10000)

    b.ResetTimer()

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

        AddToSliceByPointer(&mySlice)

    }

}

结果:


AddByValue-12    5.03µs ± 0%

AddByPointer-12  5.17µs ± 1%

非常感谢你的帮助!


查看完整回答
反对 回复 2022-07-11
?
jeck猫

TA贡献1909条经验 获得超7个赞

这是“低级”语言的普遍问题。当您按值传递时,这意味着您实际上是在复制数据。这是它的工作原理。

当您通过引用传递时:

  • 引用的副本被创建并传递给方法(引用很可能是 8 个字节,所以这很快)

  • 你读出了引用后面的数据(这也很快,因为引用很可能在 CPU 缓存中)

在按值传递的情况下:

  • 分配内存块来存储您传入的数据(慢)

  • 数据被复制到新分配的内存块中(可能快,也可能慢)

  • 然后通过引用访问您的数据(可能很慢,也可能很快,取决于数据是否在缓存中)


查看完整回答
反对 回复 2022-07-11
  • 3 回答
  • 0 关注
  • 118 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号