5

There are tons of questions and good answers concerning compiler optimization about "redundant function calls" on SO (I won't post links), however, I could not find anything on multiple same function calls on SO.

Say I have a code snippet like this:

void fairlyComplexFunction(const double &angle)
{
    //do stuff and call "sin(angle)" very often in here
}

Calling sin(angle) is a fairly expensive operation and since angle is a const within the scope of fairlyComplexFunction every call of the sine will end up with the same result so only calling it once would be a better approach:

void fairlyComplexFunction(const double &angle)
{
    const double sineOfAngle = sin(angle);
    //do stuff and use sineOfAngle very often in here
}

Is a compiler in any way able to detect things like these and optimize this for me or is the second example a better approach?

8
  • Compile it and look at the output yourself? Commented Jun 17, 2016 at 8:37
  • 2
    @KerrekSB First, I am really not that skilled at looking at and understanding assembly language. Also, just because when I compile it and it doesn't work, wouldn't mean that maybe other compilers won't be able to do it. Commented Jun 17, 2016 at 8:49
  • 5
    In principle, if the compiler can be sure that the function in question is a pure function (free of side effects, and the only thing effecting its output is its input,) then it can trivially eliminate the multiple calls. However, proving that a function is pure is not always easy or trivial in C++. Commented Jun 17, 2016 at 8:51
  • 1
    @yzt In this case, sin(x) is indeed a pure function, so I guess that it "could work in principle"? Commented Jun 17, 2016 at 8:53
  • 1
    double S= sin(Angle) can save you a lot of typing... Commented Jun 17, 2016 at 11:54

2 Answers 2

8

As already stated in the comment, a compiler may be able to optimize such thing if it can detect that the called function is a pure function (no side effects, no i/o, ...).

A small example (https://godbolt.org/g/2b3Vgg) with g++:

#include <cmath>

extern double g (double);

template <double (*F) (double)>
double f1 (double angle) {
  double x = 3 * F(angle) + F(angle);
  double y = F(angle) + F(angle) * F(angle);
  return x + y * F(angle);
}

template double f1<sin> (double);
template double f1<g> (double);

In f1 you have multiple calls to the F function, and two instanciations:

  • One with std::sin - Which should be considered a pure function by any sane compiler.
  • One with an extern function which cannot be considered a pure function.

If you look at the generated assembly*:

double f1<&sin>(double):
        subq    $8, %rsp
        call    sin
        ...
        ret

double f1<&(g(double))>(double):
        subq    $40, %rsp
        movsd   %xmm0, (%rsp)
        call    g(double)
        movsd   %xmm0, 8(%rsp)
        movsd   (%rsp), %xmm0
        call    g(double)
        movsd   8(%rsp), %xmm1
        mulsd   .LC0(%rip), %xmm1
        ...
        call    g(double)
        movsd   %xmm0, 16(%rsp)
        movsd   (%rsp), %xmm0
        call    g(double)
        movsd   %xmm0, 24(%rsp)
        movsd   (%rsp), %xmm0
        call    g(double)
        mulsd   24(%rsp), %xmm0
        movsd   16(%rsp), %xmm2
        ...
        call    g(double)
        mulsd   16(%rsp), %xmm0
        addsd   8(%rsp), %xmm0
        ...

You see that in the instantiation with sin, g++ only do one function call (call sin) while in the instantiation with g, you have 6 calls.

So yes, the compiler may do some optimization regarding multiple calls to pure function but I would not rely on it** and use an explicit intermediate variable as in your second example.

* I removed most of the generated instructions but all the call instructions are shown.

** clang does not optimize this even with -O3 (but it does with -ffast-math).

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you very much, this is a really thorough answer. I find it interestig that this is not optimized using -O3 but you need -ffast-math for it, since that should be redundant. Also it is tricky that the user "has to know this" and might end up with not fully optimized code...
@phil13131: There's a somewhat theoretical concern that the rounding mode might change between two calls, which would make the function actually impure. With -ffast-math the compielr can disregard rounding mode changes.
2

It can optimise it if it can prove that sin has no side effects whatsoever.

But even if it can, your second version makes it clear both to the reader and yourself that all the code should use the same value and that none of the uses is a bug; there's less room for both error and uncertainty.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.