簡體   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