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

Asked

Viewed 111 times

5

I worry about the final performance of an executable, at the same time I don’t 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 method getPosition 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, I should 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 programmer concern.

Or is there some smart cache where I shouldn’t worry about it?

2 answers

4

Instead of trying to guess, why don’t you 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...

Realize that the value 42 was passed to the register r15d and, whenever the loop prints the value of the first member of the pair, it is passed as argument pro std::cout.operator<<(int) through the recorder esi, copying straight 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 member of the pair is stored in the register ebp, which is passed as parameter to std::cout.operator<<(int) for esi also.

See the two results on Godbolt.

As Maniero said, there are several factors at play when it comes to the compiler optimizing your code, so one should avoid guessing what will happen to your code. Isolate the problem and Compile locally, to see what the result is. Then, compile the entire project and see what the result is by comparing it to the isolated code. Finally, and most importantly, run a Profiling (Profiling), or a benchmark, of the isolated and 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 good too). Just like this, you will be sure that your manual optimizations have been successful.

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

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

  • Fantastic! I’m discovering a lot that I never imagined. Thank you so much for the tips and support!

4


You may have some code optimization that is guaranteed to not change. That’s not the case. So it might be better to do it outside. You know it returns the same value, the compiler does not. I’m assuming that it doesn’t return a constant and does practically nothing else, because then it may be that the method can be optimized to keep the expression in place of the call and how the expression is known as constant already in the compilation and there does not even need to segregate. It is actually possible that until the loop 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 picking up the changes. On the other hand, you might not want to get modifications, so if you put it in, it might not produce the expected result. So where to put has to do with the desired semantics more than the optimization.

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

Programmer exists to have these concerns. At least the most professional. For the most amateur can matter little. The tendency is for artificial intelligence to solve simple cases in the very long term, and these programmers should be out of work.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.