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

时间:2012-03-03 17:02:08

标签: c++ optimization inheritance virtual

我正在开发一个基于数学(sin,cos,sqrt等)的应用程序。 这些函数需要一些时间才能运行,但精度很高。

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

所以我有我的Sin函数,它是一个简单的数组(在程序开始运行之前创建),需要0到360之间的度数并返回其sin(假设数组有360个值)

我想创建一个界面:

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

它将由

继承
  1. “精确数学”,其实现将称为正常的sin,cos函数。
  2. “快速数学”将使用我之前解释过的数组技巧。
  3. 我的代码将使用“mymath”类型的变量进行计算,并在开始时使用preciseMath或fastMath进行初始化。

    最后我的问题是:

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

4 个答案:

答案 0 :(得分:4)

可能的是,你的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)

一,二:

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

  2. 不,编译器不会将虚函数调用优化为静态函数调用,因为它不知道该类型在运行时不会以某种方式改变(比如从某些外部代码中获取指针)它不知道任何事情。 Delnan在评论中告诉我,在A* a = new A; a->func()之类的非常简单的情况下,编译器可以看到a永远不会是A。 1}},因此它可以执行“devirtualisation”并将虚函数调用优化为静态函数调用。但是,它可以做到这一点的情况有点罕见,如果你从函数的参数获得指针,它就不能这样做,因为它实际上可能是派生类型。

  3. 除了“编译时虚函数”(又称CRTP)之外,我不知道任何可以使你的代码更快的设计,但如果你这样做就会失去多态性。尝试使用虚拟功能并对其进行分析;如果它对你来说太慢了,那么你可以尝试另一条路线,但不要浪费时间试图让它更快,而不知道它已经有多快。

答案 2 :(得分:1)

  

我会为调用名为“Math.sin”而不是直接调用它的虚函数支付多少时间?

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

这比静态调用稍贵,但对于“正常”使用仍然被认为非常便宜。如果您需要挤压每一滴性能,请考虑在编译时指定所有类型以允许使用非虚函数。

  

编译器是否能够优化它并理解如果我用PriciseMath初始化MyMath,我想要的是调用正常的Sin和Cos函数?

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

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

  

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

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

RTS's answer很好地说明了这两种技术。)


最后,如果表现对您来说非常重要,请不要只依赖其他人(包括我)的建议 - 始终自己执行测量

答案 3 :(得分:0)

如果您可以提供不同的二进制文件,请执行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
  }

然后使用-DFAST_MATH调用编译器生成快速二进制文件而不使用完全二进制文件。