我在工作中和一个同事讨论传递一个指针到一个函数和/或返回一个指针是否更有效。
我把一些基准函数放在一起来测试不同的方法。这些函数基本上接受一个变量,转换它并把它传递回来。我们有4种不同的方法来做这件事:
1.正常地传入变量,为转换结果创建一个新变量,并传回它的副本
1.正常地传入变量,为转换结果创建一个新变量,然后传回内存地址
1.传入一个指向变量的指针,为转换的结果创建一个新变量,并传回该变量的副本
1.传入一个指向变量的指针,对指针的值执行转换,不传回任何内容。
package main
import (
"fmt"
"testing"
)
type MyStruct struct {
myString string
}
func acceptParamReturnVariable(s MyStruct) MyStruct {
ns := MyStruct{
fmt.Sprintf("I'm quoting this: \"%s\"", s.myString),
}
return ns
}
func acceptParamReturnPointer(s MyStruct) *MyStruct {
ns := MyStruct{
fmt.Sprintf("I'm quoting this: \"%s\"", s.myString),
}
return &ns
}
func acceptPointerParamReturnVariable(s *MyStruct) MyStruct {
ns := MyStruct{
fmt.Sprintf("I'm quoting this: \"%s\"", s.myString),
}
return ns
}
func acceptPointerParamNoReturn(s *MyStruct) {
s.myString = fmt.Sprintf("I'm quoting this: \"%s\"", s.myString)
}
func BenchmarkNormalParamReturnVariable(b *testing.B) {
s := MyStruct{
myString: "Hello World",
}
var ns MyStruct
for i := 0; i < b.N; i++ {
ns = acceptParamReturnVariable(s)
}
_ = ns
}
func BenchmarkNormalParamReturnPointer(b *testing.B) {
s := MyStruct{
myString: "Hello World",
}
var ns *MyStruct
for i := 0; i < b.N; i++ {
ns = acceptParamReturnPointer(s)
}
_ = ns
}
func BenchmarkPointerParamReturnVariable(b *testing.B) {
s := MyStruct{
myString: "Hello World",
}
var ns MyStruct
for i := 0; i < b.N; i++ {
ns = acceptPointerParamReturnVariable(&s)
}
_ = ns
}
func BenchmarkPointerParamNoReturn(b *testing.B) {
s := MyStruct{
myString: "Hello World",
}
for i := 0; i < b.N; i++ {
acceptPointerParamNoReturn(&s)
}
_ = s
}
我发现结果相当令人吃惊。
$ go test -run=XXXX -bench=. -benchmem
goos: darwin
goarch: amd64
pkg: XXXX
cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
BenchmarkNormalParamReturnVariable-16 10538138 103.3 ns/op 48 B/op 2 allocs/op
BenchmarkNormalParamReturnPointer-16 9526380 201.2 ns/op 64 B/op 3 allocs/op
BenchmarkPointerParamReturnVariable-16 7542066 147.0 ns/op 48 B/op 2 allocs/op
BenchmarkPointerParamNoReturn-16 45897 119265 ns/op 924351 B/op 5 allocs/op
在运行这个测试之前,我认为最有效的方法应该是第四个测试,因为在被调用的函数的作用域中没有创建新的变量,只有内存地址被传递,但是,第四个测试似乎是最低效的,花费了最多的时间,也使用了最多的内存。
有人可能解释给我听吗,或者提供一些好的阅读链接来解释?
1条答案
按热度按时间qyyhg6bp1#
你所做的基准测试并不能回答你所提出的问题。微基准测试被证明是极其困难的--不仅在围棋界,而且在一般情况下。
回到效率问题上来。通常,传递一个指针给一个函数不会逃逸到堆。通常,从一个函数返回一个指针会逃逸到堆。通常是这里的关键词。你不能说什么时候编译器在堆栈上分配东西,什么时候在堆上分配东西。这不是一个小问题。这里可以找到很好很简短的解释。
但是如果你想知道,你可以问,你可以从打印编译器的优化决定开始,你可以通过把
m
标志传递给go tool compile
来实现。如果你传递大于1的整数,你会得到更冗长的输出。如果它不能给予你优化程序所需的答案,那么试试profiling。它远远超出了内存分析。
一般来说,在你的日常工作中,不要为天真的优化决策而烦恼。不要太执着于“通常......”的陈述,因为在真实的世界中,你永远不知道。总是首先以正确性优化为目标。然后,只有当你真的需要它,并且你证明了你需要它时,才进行性能优化。不要猜测,不要相信。另外,记住,围棋在变化,所以我们在一个版本中证明的,在另一个版本中不一定是真的.