This question is specific to function calls and is aimed at the reliability of the Go optimizer when passing structures by value vs pointer.
If you are wondering when to use vs vs pointers in structure fields, see Go - Performance - What is the difference between a pointer and a value in a structure?
Please note:. I tried to say it so that everyone could understand, some of the terms as a result are inaccurate.
Invalid jump code
Suppose we have a structure:
type Vec3 struct{ X, Y, X float32 }
And we want to create a function that calculates the transverse product of two vectors. (Math does not matter for this question.) There are several ways to do this. A naive implementation would be:
func CrossOf(a, b Vec3) Vec3{ return Vec3{ aY*bZ - aZ*bY, aZ*bX - aX*bZ, aX*bY - aY*bX, } }
which is called through:
a:=Vec3{1,2,3} b:=Vec3{2,3,4} var c Vec3
This works fine, but in Go, it seems to be not very efficient. a and b are passed by value (copied) to the function, and the results are copied again. Although this is a small example, the problems are more obvious when considering large structures.
A more efficient implementation would be:
func (res *Vec3) CrossOf(a, b *Vec3) {
It is harder to read and takes up more space, but more efficiently. If the transferred structure was very large, this would be a reasonable compromise.
For most people with a C-like software background, it is intuitively simple to link as far as possible, solely for efficiency.
In Go, it is intuitive to think that this is the best approach, but Go itself indicates a flaw in this reasoning.
Go smarter than
Something here works in Go, but cannot work in most low-level C-like languages:
func GetXAsPointer(vec Vec3) *float32{ return &vec.X }
We allocated Vec3 , grabbed the pointer in field X and returned it to the caller. See the problem? In C, when the function returns, the stack will expand and the return pointer will become invalid.
However, Go is garbage collection. It will find that this float32 must continue to exist and will allocate it (either float32 , or the entire Vec3 ) to the heap instead of the stack.
Go requires a trigger detection for this to work. It blurs the line between passing by value and skipping.
It is well known that Go is designed for aggressive optimization. If it is more efficient to pass by reference, and the transferred structure is not changed by the function, why not take a more efficient approach?
Thus, our effective example can be rewritten as follows:
func (res *Vec3) CrossOf(a, b Vec3) { res.X = aY*bZ - aZ*bY rex.Y = aZ*bX - aX*bZ res.Z = aX*bY - aY*bX } // usage c.CrossOf(a, b)
Please note that this is more readable, and if we allow an aggressive pass-by-value for the compiler by pointer, as efficient as before.
In accordance with the documents, it is recommended to transfer sufficiently large receivers using pointers and treat receivers in the same way as arguments: https://golang.org/doc/faq#methods_on_values_or_pointers
Go does an escape detection on each variable already to determine if it is pushed onto the heap or stack. Thus, within the framework of the Go paradigm, it seems that only the pointer will pass if the structure is changed by the function. This will result in more readable and less error prone code.
Will the Go compiler automatically compile a step-by-step value into a pass-by-pointer? Looks like he should.
So here is the question
For structs, when should we use pass-by-pointer vs pass-by-value?
Things to consider:
- For structures, is it actually more efficient than the other, or will they be optimized equally with the Go compiler?
- Is it wrong to use this compiler for optimization?
- Is it worse than scrolling around the world, creating error-prone code?