淘寶優(yōu)惠劵網(wǎng)站怎么做app制作一個需要多少錢
一、虛函數(shù)性能
一般來說,面向?qū)ο蟮脑O計中,繼承和多態(tài)是其中兩個非常重要的特征。從使用的過程來看,一般應用到繼承的,使用多態(tài)的可能性就非常大。而多態(tài)的實現(xiàn)有很多種,
但開發(fā)者通常認為的多態(tài)(動多態(tài))一般是指通過虛函數(shù)來實現(xiàn)的多態(tài)。
虛函數(shù)的不同于普通函數(shù),它會通過一個虛表來控制函數(shù)的二次跳轉(zhuǎn)或者叫做重定向。在普通的認知里,虛函數(shù)這個特征一般是無法進行諸如內(nèi)聯(lián)等進行優(yōu)化的。所以一談到虛函數(shù)都會認為其性能堪憂。
但在前面的分析中也知道了,什么東西都有特殊情況。但無論如何說明,在常識里,虛函數(shù)就是要比普通函數(shù)的性能要低。那么到底虛函數(shù)性能為什么會低?是不是所有情況下都低?下面進行一下分析。
二、用例子看問題
先看對比的例子:
#include <iostream>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <chrono>
class Parent
{
public:virtual double exeReadDataV(double a, int b){return std::sqrt(a) * std::sin(b);}double exeReadData(double a, int b){return std::sqrt(a) * std::sin(b);}
};int main()
{Parent* p = new Parent();double a = 3.14f;int b = 3;double sum = 0.f;auto t1 = std::chrono::steady_clock::now();for (int num = 0; num < 100000000; num++){//使用一個定值,一個動態(tài)值sum += p->exeReadDataV(a,num);//全是定值//sum += p->exeReadDataV(a, b);}auto t2 = std::chrono::steady_clock::now();auto escape1 = t2 - t1;std::cerr << "escape1 is:" << escape1.count() << std::endl;auto t3 = std::chrono::steady_clock::now();for (int num = 0; num < 100000000; num++){sum += p->exeReadData(a,num);//sum += p->exeReadData(a, b);}auto t4 = std::chrono::steady_clock::now();auto escape2 = t4 - t3;std::cerr << "escape2 is:" << escape2.count() << std::endl;return 0;
}
執(zhí)行結(jié)果:
escape1 is:1982749665
escape2 is:1850111712
從上面的執(zhí)行可以看到,兩者的執(zhí)行基本沒區(qū)別。這可能打破了不少人的感官認知。無論哪種函數(shù),決定性能的有兩個重要環(huán)節(jié):一個是調(diào)用的開銷;另外一個是確定性調(diào)用。前者比較好理解,后者則不容易弄明白。其實可以這樣理解,一個寫代碼要盡量降低調(diào)用的開銷,一個是寫出的代碼編譯器能更準確的知曉上下文,然后進行優(yōu)化。在前者確定的情況下,后者就非常重要了。而虛函數(shù)被大多數(shù)人認為性能低的主要原因就在于后者。
虛函數(shù)需要一個虛表進行跳轉(zhuǎn),在內(nèi)存中這種開銷與函數(shù)的功能開銷相對來說可以忽略。但這種跳轉(zhuǎn)本身意味著大量的未知,而未知就意味著編譯無法掌控更多的確定性,而對某些很簡單的優(yōu)化可能都無法進行。正如早期的編譯器,在for循環(huán)中,直接給一個變量和一個表達式,效率差不少就是這個原因。而后的編譯器則對此進行了優(yōu)化,將其直接轉(zhuǎn)成一個常量值。把上面的例子中注釋部分打開并注釋當前的執(zhí)行(即兩個參數(shù)都為定值的情況):
執(zhí)行結(jié)果:
escape1 is:867604532
escape2 is:100431290
在執(zhí)行的函數(shù)調(diào)用中,兩個參數(shù)中一個為定值,一個為變量時,是否調(diào)用虛函數(shù)或者普通函數(shù),基本運行是差不多的。但是一旦調(diào)用的都為定值時,此時普通函數(shù)可以直接將兩個計算函數(shù)std::sqrt(a) 和 std::sin(b)均優(yōu)化為固定值并只計算一次。此時再看,計算結(jié)果可就差了將一個量級了。
而在前面的文章中(“內(nèi)聯(lián)補遺”)分析過的虛函數(shù)可以內(nèi)聯(lián),恰恰是那種可以明確確定的虛擬函數(shù),可以內(nèi)聯(lián)。即編譯器知道虛函數(shù)不具有多態(tài)性的情況下,它可普通函數(shù)沒有什么區(qū)別。把原來的例子拿上來:
class A{
public:inline virtual void Test(){...}
};
class B:public A
{
public:inline virtual void Test(){...}
};
inline void Get(A& a){a.Test();
}
int main(){A a;B b;b.Test();//可以內(nèi)聯(lián)//下面不確定Get(b);Get(a);
return 0;
}
那么可以從內(nèi)聯(lián)的角度來分析,虛函數(shù)為什么會給大家一個性能低的印象?首先為什么內(nèi)聯(lián)函數(shù)快,主要就是固定地址,編譯器優(yōu)化兩大方面。而上面的虛擬函數(shù)可以內(nèi)聯(lián),仿佛是與此結(jié)論相反,但恰恰提到了能夠內(nèi)聯(lián)的虛擬函數(shù)的情形。互相印證,應該就明白為什么虛函數(shù)在多態(tài)的情況下性能低的原因了。
總之,虛函數(shù)本身不是性能低的代表,但是虛函數(shù)多態(tài)的調(diào)用會影響優(yōu)化才是性能降低的根源。這些優(yōu)化包括計算優(yōu)化、分支跳轉(zhuǎn)優(yōu)化以及調(diào)用優(yōu)化等等。而這些優(yōu)化無法被編譯器使用,自然也就會使得編譯出來的代碼有著很多的多余的運行指令。
三、設計上對虛函數(shù)數(shù)的替代
如果對性能的敏感性不強,那么如何使用虛函數(shù)不是一個多大的問題??墒侨绻麑嶋H情況對此要求比較嚴格時,可以考慮用如下的方式來解決虛函數(shù)的使用問題:
1、模板的方法如CRTP(奇異遞歸)
2、使用宏(不推薦)
3、通過設計模式等設計方法實現(xiàn)(如訪問者模式等 )
4、使用一些技術(shù)或方法繞開多態(tài),比如就直接寫多個類然后直接控制
到底如何使用或不使用虛函數(shù),是根據(jù)實際情況來決定的。還是那句話,沒有一個技術(shù)是包打天下的。
四、總結(jié)
學習知識不是簡單的為了會用,而是能夠靈活的運用。要想靈活的運用,則必須掌握技術(shù)本質(zhì)的內(nèi)涵。只有把其內(nèi)在體系掌握,才能在具體的場景上發(fā)揮其優(yōu)勢。這也是總說的從必然世界到自由世界的一個哲學問題。