Is it possible to have a variable reference at compile time?

Consider the following code:

Matrix4x4 perspective(const ViewFrustum &frustum) { float l = frustum.l; float r = frustum.r; float b = frustum.b; float t = frustum.t; float n = frustum.n; float f = frustum.f; return { { 2 * n / (r - l), 0, (r + l) / (r - l), 0 }, { 0, 2 * n / (t - b), (t + b) / (t - b), 0 }, { 0, 0, -((f + n) / (f - n)), -(2 * n * f / (f - n)) }, { 0, 0, -1, 0 } }; } 

To improve the readability of the matrix design, I must either make a copy of the values ​​from the frustum structure, or reference them. However, I also do not need copies or indirect links.

Is it possible to have some kind of "link" that will be resolved at compile time, like a symbolic link. It will have the same effect as:

 Matrix4x4 perspective(const ViewFrustum &frustum) { #define l frustum.l; #define r frustum.r; #define b frustum.b; #define t frustum.t; #define n frustum.n; #define f frustum.f; return { { 2 * n / (r - l), 0, (r + l) / (r - l), 0 }, { 0, 2 * n / (t - b), (t + b) / (t - b), 0 }, { 0, 0, -((f + n) / (f - n)), -(2 * n * f / (f - n)) }, { 0, 0, -1, 0 } }; #undef l #undef r #undef b #undef t #undef n #undef f } 

Without a preprocessor (or is that acceptable?). I believe that this is really not necessary, or can be avoided in this particular case by making these 6 values ​​arguments to the function directly (although it would be a little annoying to call such a function), but even then I could make a built-in proxy function).

But I was just wondering if this is possible at all? I could not find anything like it. I think this will come in handy for locally shortening descriptive names that will be used a lot, without losing the original names.

+5
source share
3 answers

Mandatory disclaimer : do not optimize prematurely.

Let me compare your naive perspective function containing

 float l = frustum.l; float r = frustum.r; float b = frustum.b; float t = frustum.t; float n = frustum.n; float f = frustum.f; 

With define and @Sam Varshavchik with links.

We assume that our compiler optimizes and optimizes at least decent.

Build for all three versions: https://godbolt.org/g/G06Bx8 .

You may notice that the links and version definitions are exactly the same as expected. But naivety is very different. It first loads all the values ​​from memory:

  movss (%rdi), %xmm2 # xmm2 = mem[0],zero,zero,zero movss 4(%rdi), %xmm1 # xmm1 = mem[0],zero,zero,zero movss 8(%rdi), %xmm0 # xmm0 = mem[0],zero,zero,zero movss %xmm0, 12(%rsp) # 4-byte Spill movss 12(%rdi), %xmm0 # xmm0 = mem[0],zero,zero,zero movss %xmm0, 8(%rsp) # 4-byte Spill movss 16(%rdi), %xmm3 # xmm3 = mem[0],zero,zero,zero movaps %xmm3, 16(%rsp) # 16-byte Spill movss 20(%rdi), %xmm0 

And never again refers to the memory %rdi ( frustrum ). References and version definitions, on the other hand, load values ​​as needed.

This is because the implementation of the Vector4 constructor is hidden from the optimizer and cannot assume that the constructor does not change frustrum , so it must insert loads, even if such loads are excessive.

Thus, a naive version can be even faster than an “optimized” one, under certain circumstances.

+11
source

It’s good that C ++ links are for:

 const float &l = frustum.l; const float &r = frustum.r; const float &b = frustum.b; const float &t = frustum.t; const float &n = frustum.n; const float &f = frustum.f; 

Most modern compilers optimize links and use values ​​from the frustum verbatim object, in the following expression, by resolving links at compile time.

+11
source

In general, you can use simple links if you are in a local area. Modern compilers “look at them” and simply treat them as aliases (note that this even applies to pointers).

However, when working with materials on the small side, copying to a local variable is, in any case, generally useful. frustnum.r is one layer of indirection ( frustnum is actually a pointer under the hood), so accessing it is more expensive than it might seem, and if you have function calls in the middle of your function, the compiler may not be able to prove that its meaning does not change, so access may need to be repeated.

Instead, local variables are usually located directly on the stack (cheap) or directly in the registers (cheapest), and most importantly, given that they usually do not interact with the "external", the compiler has an easier argument about them, so it can be more aggressive with optimization; also, when the calculations are actually performed, these values ​​will be copied in the registers and on the stack in any case.

So, keep using copies, in the worst case the compiler is likely to do the same, in the best case you can help him optimize the material.

+4
source

All Articles