繁体   English   中英

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

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

我正在开发一个很大程度上基于数学(正弦,余弦,平方根等)的应用程序。 这些功能需要一些时间才能运行,但精度很高。

我的一些客户不需要那么高的精度,但是他们需要尽可能快。

因此,我有Sin函数,它是一个简单的数组(在程序开始运行之前创建),其取值范围为0到360,并返回其sin (假设该数组具有360个值)。

我想创建一个接口:

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

它将被继承

  1. “精确数学”的实现将调用正常的sin,cos函数。
  2. “快速数学”将使用我之前解释的数组技巧。

我的代码将使用“ mymath”类型的变量进行计算,并且一开始将使用precisionMath或fastMath对其进行初始化。

最后,我的问题是:

  1. 调用一个调用“ Math.sin”的虚拟函数而不是直接调用它会花费多少时间?
  2. 如果我使用PriciseMath初始化MyMath,编译器将能够对其进行优化并理解,我只想调用普通的Sin和Cos函数?
  3. 我可以更改设计以帮助编译器理解和优化代码吗?

奇怪的是,即使函数是虚拟的,您的sqrt和trig函数的成本也会比函数调用高得多。 但是,这看起来是使用模板的理想场所。 如果正确使用它们,则可以完全内嵌它们,因此可以完全消除函数调用的运行时成本。

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;

对于一和两个:

  1. 您将支付与通过函数指针调用函数相同的费用。 这不是很多。

  2. 不,编译器不会将虚拟函数调用优化为静态函数调用,因为它不知道类型在某种程度上不会在运行时更改(例如从某个完全不了解的外部代码获取指针)。 Delnan在评论中告诉我,在非常简单的情况下,例如A* a = new A; a->func() A* a = new A; a->func()编译器可以看到a永远不会是A ,因此它可以执行“去虚拟化”并将虚拟函数调用优化为静态函数调用。 但是,可以执行此操作的情况很少见,如果您从某个函数的参数中获得了指针,则它无法执行此操作,因为它实际上可能是派生类型。

除了“编译时虚拟函数”(又称CRTP)之外,我不知道有任何设计可以使您的代码比这更快,但是如果您这样做,您将失去多态性。 尝试使用虚拟功能并对其进行配置; 如果这对您来说太慢了,那么您可以尝试其他路线,但是不要浪费时间尝试使其更快而不知道它已经快了多少。

调用一个调用“ Math.sin”的虚拟函数而不是直接调用它会花费多少时间?

通过取消引用虚拟表指针,从虚拟表中的适当偏移量获取函数指针并通过该指针调用函数来实现虚拟调用。

这比静态调用稍微贵一点,但是对于“正常”使用来说仍然很便宜。 如果您需要压缩每一个性能下降,请考虑在编译时指定所有类型,以允许使用非虚拟函数。

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

如果编译器可以(本身)证明对象在运行时将具有特定类型,则即使该函数本身被声明为虚拟的,它也能够发出静态函数调用。

但是,不能保证编译器足够聪明地实际执行该操作。 保证静态调用的唯一方法是使用非虚函数。

我可以更改设计以帮助编译器理解和优化代码吗?

  • 消除虚拟调用开销:如果不需要在运行时更改实现,则在编译时指定类型,然后完全停止使用虚拟函数。 模板可能以通用方式不可或缺。
  • 消除静态函数调用的开销:在标头中提供函数主体,允许编译器内联函数调用。

RTS的答案很好地说明了这两种技术。)


最后,如果性能对您来说真的很重要,则不要仅仅依靠其他人(包括我)的建议-始终自己进行测量

如果可以提供不同的二进制文件,则只需执行条件编译即可

  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
  }

然后使用-DFAST_MATH调用编译器以生成快速二进制文件,而无需生成确切的二进制文件。

暂无
暂无

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

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