ZhengGaoxiong 's Blog QA Engineer for cloud storage now

Golang结构体值调用vs指针调用

2022-05-14

两种结构体调用方式都能大部分满足我们的需求,为什么要有两种调用方式呢? 他们间的异同点是什么呢?

本文浅谈下Golang结构体值调用和指针调用间的区别。

并引出2个问题: 1.结构体指针调用确实如网上说的那么高效么? 2.如果高效,高效的原因是什么呢?

调用定义

func (s *MyStruct) pointerMethod() { } // method on pointer
func (s MyStruct)  valueMethod()   { } // method on value

区别

1.最重要的一点:接受者是否需要进行修改?如果需要修改,必须使用结构体指针调用。(slice/map作为引用类型,它们的行为有些微妙,但是例如要在函数里修改slice的长度,必须使用结构体指针调用)

modify field of slice


type SliceTest struct {
	s []int
}

func (s SliceTest) valueAppend(p int) {
	s.s = append(s.s, p)

}
func (s *SliceTest) pointerAppend(p int) {
	s.s = append(s.s, p)
}

func (s SliceTest) valueSet(p int) {
	if len(s.s) == 0 {
		return
	}
	s.s[0] = p
}

func (s *SliceTest) pointerSet(p int) {
	if len(s.s) == 0 {
		return
	}
	s.s[0] = p
}

func printSlice(s SliceTest) {
	fmt.Println("slice:", s.s, "len:", len(s.s), "cap:", cap(s.s))
}
unc main() {
	s := SliceTest{s: make([]int, 0)}
	s.valueAppend(10)
	printSlice(s)
	s.pointerAppend(10)
	printSlice(s)
	s.pointerSet(11)
	printSlice(s)
	s.valueSet(12)
	printSlice(s)
}

output

slice: [] len: 0 cap: 0
slice: [10] len: 1 cap: 1
slice: [11] len: 1 cap: 1
slice: [12] len: 1 cap: 1

modify field of other type


type OtherTest struct {
	num int
}

func (p OtherTest) valueMethod(num int) {
	p.num = num
}

func (p *OtherTest) pointerMethod(num int) {
	p.num = num
}
func main(){
        s := OtherTest{num: 0}
	fmt.Println(s)
	s.valueMethod(10)
	fmt.Println(s)
	s.pointerMethod(10)
	fmt.Println(s)
}

output

{0}
{0}
{10}

从这些例子看,如果使用结构体指针调用修改这些字段,修改对于调用者是可见的。 但是使用结构体值调用,是通过参数拷贝实现的,修改对于调用者是不可见的。

运用场景: 使用结构体值调用来替代结构体指针调用,可以遵循参数不可变行性质,更加安全。这种用法叫做“防御性拷贝”。

2.对效率的考虑,如果接受者是一个很大的结构体,使用指针调用更高效 这块后面会做个简单验证

总之除了修改字段外,这两种调用方式几乎没别的区别了;可能因为结构体值调用有参数拷贝操作,他们之前性能差异比较大

性能

写了个简单代码,粗略验证下结构体指针调用是否如网上说的那么高效

benchmark demo

package main

import (
	"testing"
)

type StructTest struct {
	num  int
	num1 int
	num2 int
	num3 int
	str  string
	b    bool
}

func (p StructTest) valueMethod(num int) {
	p.num = num
	p.num1 = num
	p.num2 = num
	p.num3 = num
	p.str = "XXXX"
	p.b = true
}

func (p *StructTest) pointerMethod(num int) {
	p.num = num
	p.num1 = num
	p.num2 = num
	p.num3 = num
	p.str = "XXXX"
	p.b = true
}

func BenchmarkValStruct(b *testing.B) {
	p := StructTest{num: 10}
	for j := 0; j < b.N; j++ {
		for i := 0; i < 1e8; i++ {
			p.valueMethod(i)
		}
	}
}

func BenchmarkPointerStruct(b *testing.B) {
	p := StructTest{num: 10}
	for j := 0; j < b.N; j++ {
		for i := 0; i < 1e8; i++ {
			p.pointerMethod(i)
		}
	}
}

output of call by value

go test -benchmem -run=^$ -bench ^BenchmarkValStruct$ demo/gobench  -benchtime=
60s
goos: darwin
goarch: amd64
pkg: demo/gobench
cpu: Intel(R) Core(TM) i5-8257U CPU @ 1.40GHz
BenchmarkValStruct-8        1356          55742350 ns/op               0 B/op          0 allocs/op
PASS
ok      demo/gobench    81.456s

output of call by pointer

go test -benchmem -run=^$ -bench ^BenchmarkPointerStruct$ demo/gobench  -benchtime=60s
goos: darwin
goarch: amd64
pkg: demo/gobench
cpu: Intel(R) Core(TM) i5-8257U CPU @ 1.40GHz
BenchmarkPointerStruct-8            1260          56138387 ns/op               0 B/op          0 allocs/op
PASS

从性能测试结果可以比较明显看出结构体指针调用确实效率会高一些。但是并不是很明显。 可能跟结构体中字段大小和数量有一定关系。

参考

defining-golang-struct-function-using-pointer-or-not
why-are-receivers-pass-by-value-in-go


Similar Posts

上一篇 My Vim Note

Comments