Rick Zheng's Blog

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