简体   繁体   English

在C ++中调用函数(或虚函数)是一项昂贵的操作

[英]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). 因此,我有Sin函数,它是一个简单的数组(在程序开始运行之前创建),其取值范围为0到360,并返回其sin (假设该数组具有360个值)。

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. “精确数学”的实现将调用正常的sin,cos函数。
  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. 我的代码将使用“ mymath”类型的变量进行计算,并且一开始将使用precisionMath或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? 调用一个调用“ Math.sin”的虚拟函数而不是直接调用它会花费多少时间?
  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? 如果我使用PriciseMath初始化MyMath,编译器将能够对其进行优化并理解,我只想调用普通的Sin和Cos函数?
  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. 奇怪的是,即使函数是虚拟的,您的sqrt和trig函数的成本也会比函数调用高得多。 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() Delnan在评论中告诉我,在非常简单的情况下,例如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. A* a = new A; a->func()编译器可以看到a永远不会是A ,因此它可以执行“去虚拟化”并将虚拟函数调用优化为静态函数调用。 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. 除了“编译时虚拟函数”(又称CRTP)之外,我不知道有任何设计可以使您的代码比这更快,但是如果您这样做,您将失去多态性。 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? 调用一个调用“ Math.sin”的虚拟函数而不是直接调用它会花费多少时间?

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? 如果我使用PriciseMath初始化MyMath,编译器将能够对其进行优化并理解,我只想调用普通的Sin和Cos函数?

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.) RTS的答案很好地说明了这两种技术。)


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. 然后使用-DFAST_MATH调用编译器以生成快速二进制文件,而无需生成确切的二进制文件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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