16

Say I had this snippet of code:

#include <cmath> // ... float f = rand(); std::cout << sin(f) << " " << sin(f); 

As sin(f) is a well defined function there is an easy optimisation:

float f = rand(); float sin_f = sin(f); std::cout << sin_f << " " << sin_f; 

Is this an optimisation that it's reasonable to expect a modern C++ compiler to do by itself? Or is there no way for the compiler to determine that sin(f) should always return the same value for an equal value of f?

9
  • 2
    Unless sin is defined in the same compilation unit, the compiler does not know how sin is implemented, so at best this would happen at link time. Commented Jan 24, 2013 at 19:58
  • 2
    @Thomas Not necessarily. Several compilers treat functions of certain names specially because they know they're defined in the standard library, and the standard guarantees something about them. There's also compiler-specific function attributes that user-defined headers can use to declare themselves pure, though I don't know if this knowledge is used for optimization. Commented Jan 24, 2013 at 20:03
  • The optimization is indeed done with gcc. Commented Jan 24, 2013 at 20:04
  • If I was a compiler writer I certainly would. Commented Jan 24, 2013 at 20:04
  • 2
    Part of the problem is knowing that std::ostream::operator<< didn't change the value of f. Commented Jan 24, 2013 at 20:06

2 Answers 2

18

Using g++ built with default optimization flags:

float f = rand(); 40117e: e8 75 01 00 00 call 4012f8 <_rand> 401183: 89 44 24 1c mov %eax,0x1c(%esp) 401187: db 44 24 1c fildl 0x1c(%esp) 40118b: d9 5c 24 2c fstps 0x2c(%esp) std::cout << sin(f) << " " << sin(f); 40118f: d9 44 24 2c flds 0x2c(%esp) 401193: dd 1c 24 fstpl (%esp) 401196: e8 65 01 00 00 call 401300 <_sin> <----- 1st call 40119b: dd 5c 24 10 fstpl 0x10(%esp) 40119f: d9 44 24 2c flds 0x2c(%esp) 4011a3: dd 1c 24 fstpl (%esp) 4011a6: e8 55 01 00 00 call 401300 <_sin> <----- 2nd call 4011ab: dd 5c 24 04 fstpl 0x4(%esp) 4011af: c7 04 24 e8 60 40 00 movl $0x4060e8,(%esp) 

Built with -O2:

float f = rand(); 4011af: e8 24 01 00 00 call 4012d8 <_rand> 4011b4: 89 44 24 1c mov %eax,0x1c(%esp) 4011b8: db 44 24 1c fildl 0x1c(%esp) std::cout << sin(f) << " " << sin(f); 4011bc: dd 1c 24 fstpl (%esp) 4011bf: e8 1c 01 00 00 call 4012e0 <_sin> <----- 1 call 

From this we can see that without optimizations the compiler uses 2 calls and just 1 with optimizations, empirically I guess, we can say the compiler does optimize the call.

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

Comments

17

I'm fairly certain GCC marks sin with the non-standard pure attribute, ie __attribute__ ((pure));

This has the following effect:

Many functions have no effects except the return value and their return value depends only on the parameters and/or global variables. Such a function can be subject to common subexpression elimination and loop optimization just as an arithmetic operator would be.

http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

And so there is a very good chance that such pure calls will be optimized with common subexpression elimination.

(update: actually cmath is using constexpr, which implies the same optimizations)

4 Comments

Just to add to this, MSC knows about a few such functions and can generate the according code inline.
constexpr does not imply pure. It's possible to implement a conforming constexpr that randomly throws exceptions for certain inputs.
gcc also optimizes if you only include <math.h> which doesn't have an attribute, it knows about them internally, and marks sin as "const" unless you pass -frounding-math where it is only "pure".
It's worth noting that __attribute__((pure)) is a gcc extension, not a part of the C or C++ standard. Other compilers probably have similar extensions, but don't count on it without checking.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.