I address this differently. (1) no two different pointers will ever point to the same thing. This lets me use the strict no-alias compiler option so that it will assume that I know what I am doing and avoid repeated loads. (2) for code written by others, this assumption is unsafe. For example, if you compile egtb.cpp with this option, crafty will crash quicker than a drug addict that hasn't had a fix in 2 weeks. Because Eugene does play some serious tricks with pointers.wgarvin wrote:Actually, you're right that the compiler will probably cache 'pos->side' into a register, but the fact that pos is a pointer to const actually has nothing to do with it. Pointer to const doesn't really give the compiler any useful information, because it only means the programmer can not modify the pointed-to value through that pointer, but the value could still be modified through other pointers or references. So the compiler has to do some sort of dataflow analysis and prove that the value cannot be modified (by this thread at least) between the two access. It can do this analysis just as easily with non-const pointers.Sven Schüle wrote:- Regarding optimization, since 'pos' is already a pointer to const, the compiler will most probably replace all 'pos->xxx' expressions by optimized code that dereferences 'pos' only once, since the value of 'pos->xxx' can't change within that function. Therefore, also 'pos->side^1' will be evaluated only once and can be subject to the same kind of optimization.
I also suspect confusion about the const modifier (not from you, but as a general comment) that is very similar to the confusion about "volatile". Volatile int * p; and int * volatile p; are not the same thing. In this case, is the pointer a const or is the thing it points to a const?
IMHO, writing the code in the simplest way possible gives the compiler the best chance for doing all of its optimization tricks. Programmers that try to "help" often just get in the way of the optimizer and end up hurting or not helping at all, while making the code harder to follow (I go for fewest lines of code).
I know its not the case in your code, but one general case where it is useful to cache the values is, if any functions called between the two accesses are not inlined. If the compiler has not seen the definition of that function, it doesn't know what it will do. (Even if it has seen the definition, there may be potential aliasing through global variables or other pointers that it has to take into account). Another common case is when you pass two pointers (e.g. to arrays of data) into a function, with the intention to read from one and write to the other, in a loop. The programmer may know that these two arrays don't point to the same place or overlap with each other, but unless you explain that to the compiler (with __restrict) you might be surprised by the pessimistic code that it generates.
Anyway caching values from a struct or array into a temporary variable is a good idea either (1) to improve readability, or (2) when you want to call other functions or write through pointers, between the various uses of the expression. If you read the value into a temporary variable and don't take the address of that variable (or at least, don't let it 'escape' from the current context) then the compiler will easily discover that the temporary variable cannot be legally changed, regardless of what the code in the unknown function does. Or in the case of writes through pointers, the compiler will easily prove that the pointer you're writing through cannot point (in any legal way at least) to the temporary variable you copied the value into, so the write can not affect its value.
Lots of C++ programmers think that const will help the compiler with optimization, but its not really true. Const is mostly a promise from the programmer to human readers of the code, but its a promise which the language allows them to break with impunity (via aliasing or things like const_cast and mutable) so the compiler can't really rely on it. Fortunately, good compilers are adept at discovering the actual usage of indirect values on their own. They can often prove that a pair of pointers/references is aliased or is not aliased, and generate better code.
Knowing how compilers work can give you a good intuition about whether the compiler needs some 'help' to optimize a particular bit of code. But for speed-critical code, examining the generated code is a good idea. Occasionally you'll be surprised by particularly bad-looking code, and it can take some effort to work out why the compiler generated what it did. Usually its not because the compiler sucks (most mature optimizing compilers are quite good), but rather because your program contains something that confuses it or forces it to make pessimistic assumptions. Sometimes you can "explain" the true situation to the compiler by caching values in temporaries and carefully applying __restrict.