Do we use Over-Pointer in Go?

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 // ...and later on: c := CrossOf(a, b) 

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) { // Cannot assign directly since we are using pointers. It possible that a or b == res x := aY*bZ - aZ*bY y := aZ*bX - aX*bZ res.Z = aX*bY - aY*bX res.Y = y res.X = x } // usage c.CrossOf(&a, &b) 

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?
+5
source share
1 answer

Short answer: Yes, you overdo it here according to the sign.

A little quick math here ... your structure consists of three float32 objects for a total of 96 bits. Assuming you are on a 64-bit machine, your pointer has a length of 64 bits, so at best you will save yourself insignificant 32-bit copies.

As the price of saving these 32 bits, you force an additional search (it must follow the pointer and then read the original values). It should allocate these objects on the heap instead of the stack, which means a whole bunch of extra overhead, extra work for the garbage collector, and reduced memory locality.

When writing highly efficient code, you should be aware that the potential overhead associated with poor memory can be extremely expensive. The latency of the main memory can be 100 times less than that of L1.

Also, since you are taking a pointer to a structure that you are preventing the compiler from making a number of optimizations that it might otherwise have done. For example, Go might record call agreements in the future, which would be prevented here.

In a nutshell, saving 4 bytes of copy may cost you quite a bit, so yes, in this case you are abusing the step-by-step pointer. I would not use pointers only for efficiency, if the structure was not 10 times larger than this, and even then it is not always clear if this is the right approach, given the likelihood of errors caused by accidental modification.

+1
source

Source: https://habr.com/ru/post/1260959/


All Articles