Go语言 了解指针操作和CPU/内存使用情况

qacovj5a  于 2023-02-27  发布在  Go
关注(0)|答案(1)|浏览(163)

我在工作中和一个同事讨论传递一个指针到一个函数和/或返回一个指针是否更有效。
我把一些基准函数放在一起来测试不同的方法。这些函数基本上接受一个变量,转换它并把它传递回来。我们有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

在运行这个测试之前,我认为最有效的方法应该是第四个测试,因为在被调用的函数的作用域中没有创建新的变量,只有内存地址被传递,但是,第四个测试似乎是最低效的,花费了最多的时间,也使用了最多的内存。
有人可能解释给我听吗,或者提供一些好的阅读链接来解释?

qyyhg6bp

qyyhg6bp1#

你所做的基准测试并不能回答你所提出的问题。微基准测试被证明是极其困难的--不仅在围棋界,而且在一般情况下。
回到效率问题上来。通常,传递一个指针给一个函数不会逃逸到堆。通常,从一个函数返回一个指针会逃逸到堆。通常是这里的关键词。你不能说什么时候编译器在堆栈上分配东西,什么时候在堆上分配东西。这不是一个小问题。这里可以找到很好很简短的解释。
但是如果你想知道,你可以问,你可以从打印编译器的优化决定开始,你可以通过把m标志传递给go tool compile来实现。

go build -gcflags -m=1

如果你传递大于1的整数,你会得到更冗长的输出。如果它不能给予你优化程序所需的答案,那么试试profiling。它远远超出了内存分析。
一般来说,在你的日常工作中,不要为天真的优化决策而烦恼。不要太执着于“通常......”的陈述,因为在真实的世界中,你永远不知道。总是首先以正确性优化为目标。然后,只有当你真的需要它,并且你证明了你需要它时,才进行性能优化。不要猜测,不要相信。另外,记住,围棋在变化,所以我们在一个版本中证明的,在另一个版本中不一定是真的.

相关问题