Should I avoid repeated access to the same method within a loop?

5

I worry about the final performance of an executable at the same time, I do not want to penalize the programmer with excessive or unnecessary care in the code.

In the code below, for example, it will return the same value as the getPosition method for 10000 times:

for (int i=0; i<10000 ; i++)
    std::cout << i + window.getPosition().x << endl;

In this particular case, I know that window.getPosition().x will always return the same value .

To avoid a loss of performance, should I change the code to, for example:

const int x = window.getPosition().x;
for (int i=0; i<10000 ; i++)
    std::cout << i + x << endl;

This always involves an additional concern of the programmer.

Or is there some smart cache, where should I not worry about that?

    
asked by anonymous 21.05.2018 / 03:06

2 answers

4

You may have some optimization in code that is guaranteed to not change. It's not the case. So it should be better off. You know that returns the same value, the compiler does not. I am assuming that it does not return a constant and does almost nothing else, because there it may be that the method can be optimized to hold the expression in place of the call and as the expression is known as constant already in the compilation and there neither need to do segregation. In fact it is possible that even the bond disappears. Of course it depends on the compiler and how the method was written.

Optimizations always depend on a number of factors, such as the compiler you are using, the platform that will be generated, the compiler version, the settings used, the context.

If the method has side effects that will potentially bring different results to each call, then caching a result before the loop can prevent you from picking up the changes. On the other hand it may be that you do not want to take modifications, then placing yourself inside may not produce the expected result. So where to put has to do with the desired semantics rather than optimization.

Note that it is not just a question of the returned result. If you always give the same result, but doing something collateral in the method call once or 10,000 times will give different total result, even if the result returned is different.

Scheduler exists to have these concerns. At least the most professional. For the more amateurs it may matter little. The tendency is for artificial intelligence to solve simple cases in the very long run, and these programmers should run out of work.

    
21.05.2018 / 03:21
4

Instead of guessing, why not take a look?

Compiling the following code:

#include <utility>
#include <iostream>

struct Window
{
    std::pair<int, int> pos;

    std::pair<int, int> getPosition() const
    {
        return pos;
    }
};

int main()
{
    Window window{{42, 314}};

    for (int i = 0; i < 10000; ++i)
        std::cout << (i + window.getPosition().first) << std::endl;
}

With Clang 6.0.0, with level 3 optimization:

main: # @main
  push r15
  push r14
  push rbx
  mov r15d, 42
.LBB0_1: # =>This Inner Loop Header: Depth=1
  mov edi, offset std::cout
  mov esi, r15d
  call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
  mov r14, rax
  ; Continua...

Note that the 42 value has been passed to the r15d register, and whenever the loop prints the value of the first pair member, it is passed as the pro std::cout.operator<<(int) argument through the esi register, copying direct from r15d .

Something similar is done by GCC 8.1, with the same flags:

main:
  push r12
  push rbp
  mov ebp, 42
  push rbx
  jmp .L7
  ; Continua...
.L7:
  mov esi, ebp
  mov edi, OFFSET FLAT:std::cout
  call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
  mov r12, rax
  ; Continua...

The value of the first pair member is stored in register ebp , which is passed as parameter to std::cout.operator<<(int) by esi as well.

See the two results on Godbolt .

As Maniero said, there are several factors at play when compiler optimizes your code and so you should avoid guessing what will happen to your code. Isolate the problem and compile locally to see what the result is. Then compile the whole project and see what the result is compared to the isolated code. Lastly, and more importantly, run profiling, or a benchmark, of isolated, non-isolated code, then optimize it in code (if necessary!) And compare the results with new profiling (I'm not even considering the architecture where your program will be running, but it would be nice too). Only then will you be sure that your manual optimizations have been successful.

In most cases, premature optimization (i.e. trying to guess the behavior of your program's execution and optimizing on top of it) is harmful and can make the optimizations that the compiler can do even worse.

Always optimize thinking about the data that your program will process and little in the construction of the code, comparing profiles to prove the success of the optimizations.

    
21.05.2018 / 17:49