简体   繁体   中英

Is calling a function (or virtual function) a costly operation in C++

I'm developing an application which is heavily based on math (sin, cos, sqrt etc). These functions take some time to run but have high precision.

Some of my clients don't need that high precision, but they need it to be as fast as possible.

So I have my Sin function which is a simple array (which is created before the program starts to run) that takes a degree between 0 and 360 and returns its sin (let's say the array has 360 values).

I want to create an interface:

interface MyMath
{
    double PreciseSin(double x);
    double PreciseCos(double x);
}

It will be inherited by

  1. "Precise math" which implementation will call the normal sin,cos function.
  2. "Fast math" which will use the array trick I explained earlier.

My code will use a variable of type "mymath" to do the calculations, and in the beginning it will be initialized with preciseMath or fastMath.

Finally my questions are:

  1. How much penalty in time I will pay for calling a virtual function that calls "Math.sin" instead of calling it directly?
  2. Will the compiler will be able to optimize it and understand that in case I initialize MyMath with PriciseMath all I want is to call the normal Sin and Cos functions?
  3. Can I change my design to help the compiler understand and optimize my code?

Odds are that your sqrt, and trig functions will have a much higher cost then the function call, even if its virtual. However this looks like the perfect place to use templates. If you use them correctly you can completely remove the run time cost of the function calls as all of them can be inlined.

class PreciseMath{
    public:
    inline double sin(double sin){
        //code goes here
    }
    inline double cos(double sin){
        //code goes here
    }
    inline double sqrt(double sin){
        //code goes here
    }
};
class FastMath{
    public:
    inline double sin(double sin){
        //code goes here
    }
    inline double cos(double sin){
        //code goes here
    }
    inline double sqrt(double sin){
        //code goes here
    }
};
template<class T>
class ExpensiveOP{
    public:
    T math;
    void do(){
        double x = math.sin(9);
        x=math.cos(x);
        //etc
    }
}
ExpensiveOP<PreciseMath> preciseOp;
ExpensiveOP<FastMath> fasterOp;

For one and two:

  1. You'll pay the same amount as calling a function through a function pointer. This isn't very much.

  2. No, the compiler won't optimise virtual function calls into static function calls because it can't know that the type won't change at runtime somehow (like getting a pointer from some outside code that it doesn't know anything about). Delnan informed me in the comments that in very simple cases such as A* a = new A; a->func() A* a = new A; a->func() the compiler can see that a will never be anything other than an A , so it can perform "devirtualisation" and optimise the virtual function call into a static function call. However, the cases where it can do this are somewhat rare, and if you got the pointer from, say, the argument to a function, it can't do this because it could actually be a derived type.

I don't know of any design that could make your code faster than that, besides "compile-time virtual functions" (aka. CRTP) but you lose polymorphism if you go this way. Try it with virtual functions and profile it; if it is too slow for you, then you can try another route, but don't waste time trying to make it faster without knowing how fast it already is.

How much penalty in time I will pay for calling a virtual function that calls "Math.sin" instead of calling it directly?

A virtual call is implemented by dereferencing the virtual table pointer, getting the function pointer from the appropriate offset in the virtual table and calling the function through that pointer.

This is slightly costlier than the static call, but is still considered very cheap for "normal" usage. If you need to squeeze every last drop of performance, consider specifying all your types at compile-time to allow usage of non-virtual functions.

Will the compiler will be able to optimize it and understand that in case I initialize MyMath with PriciseMath all I want is to call the normal Sin and Cos functions?

If compiler can prove (to itself) that an object will have a particular type at the run-time, then it will be able to emit a static function call, even when the function itself is declared as virtual.

However, the compiler is not guaranteed to be smart enough to actually do it. The only way to guarantee the static call is to use the non-virtual function.

Can I change my design to help the compiler understand and optimize my code?

  • Eliminate virtual call overhead: If there is no need to change the implementation at run-time, then specify the types at compile-time and stop using virtual functions altogether. Templates can be indispensable in doing so in a generic way.
  • Eliminate the static function call overhead: Provide function bodies in headers, allowing compiler to inline function calls.

(The RTS's answer is a nice illustration of both these techniques.)


And finally, if performance is really important to you, don't just rely on advice of other people (including me) - always perform the measurements yourself!

If you can provide different binaries, just do conditional compilation :

  namespace MyMath {
#ifdef FAST_MATH
    double sin(double x) { /* do it fast */ }
    double sos(double x) { /* do it fast */ }
#else
    double sin(double x) { /* do it precise */ }
    double sos(double x) { /* do it precise */ }
#endif
  }

And then call your compiler with -DFAST_MATH to generate the fast binary and without for the exact binary.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM