C 與 C++ 誰的效率高,為什麼?


C和C++的性能不同是一種誤解。

如果你使用同樣的抽象和同樣的邏輯去實現同樣的代碼,C和C++的性能幾乎是嚴格相等的。

如果你需要抽象,例如跳轉表,那和同樣能力的Virtual Function是沒有差別的,甚至編譯器能夠更好的優化Virtual Table的代碼。

C++可能會鼓勵你使用運行效率更低,但是開發效率更高的抽象(例如使用相對複雜的Function Object),但是你可以選擇不用。更多的,C++的還會有諸如inline之類的機制來改善效率。

所以對於這類懷疑,一句話概括就是:匹夫無罪,懷璧其罪。


C++的對C的兼容沒有對C語言帶來任何時間上的開銷,空間上是有少量開銷的,包括:

1. 函數名mangle比C大,導致對二進位體積有些增大(咳咳 俺不是做面向用戶的客戶端軟體的而是做線上服務的,所以都沒有把函數符號strip,方便線上調試),如果是動態庫導出符號(GCC默認導出所有符號而VC必須手工指定導出),這些符號還必須帶到內存里損失一些DCACHE,所以高性能服務現在都有啟動後cache預熱一說,就是淘汰掉這些永遠不會用第二次的CACHE(elf二進位需要禁用plt之流的延遲綁定)。;

2. 異常處理unwind代碼(不過GCC默認也給C語言生成了dwarf異常處理代碼,使用 -fno-asynchronous-unwind-tables可以去掉.eh_frame 段(c或者c++都可以去掉),那樣會導致相關代碼無法在遇到異常時unwind堆棧, 可以閱讀回答in Why GCC compiled C program needs .eh_frame section?),不過只要你的C語言不去鏈接會拋異常的代碼,這些異常處理代碼是永遠不會被執行也不會佔你的ICACHE的。


C++相對於C的抽象,相較於直接用C的實現,效率如何呢?

1. 異常: 對於GCC c++用的dwarf異常比C裡面模擬實現用的longjmp時間效率要高,畢竟他記錄了哪些callee-saved 寄存器是需要恢復的,用空間換了些時間。mingw默認也用dwarf而不是setjmp/longjmp異常規範。

2.虛函數: 這種抽象,C實際有2種實現,一種就是照搬C++的實現,顯然效率不會有太多差別(也許可以手工管理虛表內容,讓比較熱的虛函數指針放一塊兒?那也是虛函數很多的情況才有用);第二種是file handle那一套:讓每個對象持有每個虛函數的函數指針,初始化慢些且對象或者虛函數多了也特別費內存,但是調用虛函數時少了一次間接定址,而且還可以運行期修改這個函數指針的值(C++虛表是防寫的...而且改動會對所有對象生效)。總之看需求了,對象實例較少虛函數不多或者類型可能無限擴展需要像腳本語言那樣運行時更新的時候,第二種還是挺有吸引力的,C++包裝個函數指針對象來實現第二種也不難,,而且虛函數怎麼說呢,,讓你少掉不少」心智負擔「和選擇困難症(我這個類,以後是否會有很多對象同時存在???無法預料啊。。)。

3.返回結構體對象:C++支持對於非POD(搞個空基類帽子就夠了)執行返回拷貝優化,C99結構體由於都是POD,據我的GCC版本測試都依然不能支持返回拷貝優化(應該是必須要嚴格遵循ABI的關於is_pod的定義和返回值的規範,如system v ABI for x64),注意這裡說的POD 不是C++11/14那種可以升級的POD的定義,而是ABI中的POD定義。

4. C++函數模版(operator &<) vs C函數指針定義concept(qsort的比較函數指針):本質是生成多份實例用空間換時間 VS 用函數調用(函數指針離開上下文去使用是無法inline的)解耦可復用代碼和不可復用代碼從而避免可復用代碼產生多份實例,前者佔用內存多點但少了些許跳轉屬於對ICACHETLB不友好但對流水線友好,後者佔用內存少點但多了些函數跳轉屬於對ICACHETLB友好但對流水線不友好。

Intel 奔騰4之後不再走加長流水線的路線,AMD大致也相仿,ARM我不太了解就不獻醜了。 而CACHE受制於虛實地址轉換中頁表的大小,增大L123緩存本質只敢增加多路緩存的路數(類似於走多核的路線)來適配操作系統,所以緩存目前來說也漸漸成了停滯增長的資源(路數如果無限增加對硬體實現查找緩存命中來說很麻煩),增長速度遠遠小於摩爾定律或者內存容量增速(有最多cache的Xeon系列反正看著最新的skylake架構也沒什麼漲)。 所以在這一點上,C和C++的路線到底誰對效率更友好,很難說。有些優秀案例在鼓勵C語言,如redis;但更多的大型系統在鼓勵使用C++去支持更優良的設計。我本人在這一點上持中立態度,可能精雕細琢的大型開源項目用C更合適些,但若是工業項目則更推薦用C++保證可維護性。

5. 虛繼承:堆棧上的PIMPL模式,性能比C好(相對引用 VS 間接引用),不過顯然失去了PIMPL封裝後的二進位前向兼容性。

6. RAII:這限制了一部分編譯器和程序員的優化空間。強制限制了資源回收必須按照初始化的相反順序去執行(很多時候,一個函數里定義的局部變數其實都是沒有什麼析構依賴關係的),析構函數還不能inline(為函數體在任何地方都可能出現的異常做準備。inline的話析構代碼就會上下文相關,依賴異常發生在哪個地方了)。

--------------------------------------------------------------------------------------------------------------------------------

大致就這些吧,有什麼細節想讓我進一步比較的可以回復我。

------------------------------------------------------------------------------------------------------------------------

補充:

對於VC++的異常,c和c++異常都使用windows的SEH異常,而由於C++里需要閹割掉」再試一次「這個功能,C++異常在使用上會有一些低效(廢棄掉了function resume的能力)。

---------------------------------------------------------------------------------------------------------------------

吃完個飯怎麼這麼多人來點贊,,親們別吝嗇您的滑鼠,快快來給我的個人開源項目call_in_stack http://github.com/yuanzhubi/call_in_stack 點個贊或者宣傳一下吧,小子敬謝不敏。


因為C++基本上是C的超集(只有少量C特性不支持),純粹用語言來對比,C可以實現的性能,C++同樣可以(簡單把一般C的源碼在C++編譯器下編譯),但反過來不是。通常說的例子是C++的inline和template,C99也吸納了inline,template的話C暫時只能使用類型不安全的宏來模擬。

通常談語言也會比較它們的標準庫,例如C++的std::sort肯定比C的qsort快(因為template function的優點,甚至可以做specialization),但C++的iostream系列又比C的printf系列慢(見iostream 是 C++ 的缺陷嗎,為什麼? - Milo Yip 的回答)。

由於C++的標準庫功能比C大得多,許多時候可以用較少的代碼及時間實現相同的演算法。


actually, there is a similar Quora answer to this question, answers very well written:
Is C++ slower than C?
Would encourage those who are interested to take a look.
----------------------------

雖然已經很多人都回答過這個問題了但是我還想再簡短地補充一些。首先這個題目太籠統,到底是什麼效率呢?是開發效率,維護效率,還是執行效率?姑且認為是執行效率吧,那又在哪個平台上的執行效率呢?姑且認為是X86吧。

有兩個方面影響最終執行效率,第一是語言特性,第二是編譯器優化效率。

從語言特性角度上來看,C++是C的超集。在(C++) - C的這部分語言特性中有很多會降低執行效率。一個例子是dynamic_cast,執行一個dynamic_cast要消耗100-300個CPU cycles,因為機器要跳到一段特別的snippet(一小段程序)去檢查type inheritance,在內層循環中使用它無非是大大浪費時間。另一個例子是大家都很熟悉的vtable,這裡不多說了。

但是一些增加了的語言特性會極大地提高編譯器識別並對代碼進行優化的能力。最簡單的就是inline關鍵詞。在C中程序員是不能顯示地告訴編譯器要不要inline某個函數,C++有了這個能力,也就是說把控制權更多地交給了寫代碼的人(雖然最終不一定會inline)。inline和const這兩個關鍵詞使得在global constant propagation這個編譯器優化過程里,一些在底層函數中,在C里不能被全局識別的常數都能被順利地展開,這樣生成的代碼必然比C要快得多。(這一段需要一些編譯器優化的知識才能理解)

題外說一句,編譯器很重要。說一個之前優化代碼的例子:在做循環內的運算優化時,如果運算數據在內存里相鄰並對齊,GCC可以檢測出來並生成並行代碼(在X86上是Intel SSE指令)。最終我通過數據對齊讓GCC生成SIMD代碼將循環速度提升了20倍。這一點上C和C++收到的效果是一樣的。

總而言之,你不能簡單地說C和C++哪個效率更高。它們各有各的特性,如何利用它們各自的特性生成運行效率優秀的程序,是一個程序員應該思考的事情。希望我的回答能對問題有所補充。

-------------
@馮東 提出了一些問題,我也繼續說說我的看法。

我不明白為什麼使用Template的話CPU cache會失效增加?假如template生成兩個實例,那在執行template的兩個實例時兩者的execution path應該是類似的,於是CPU cache失效率也應該是類似的。代碼量的增加並不是Cache miss的條件,很多情況下在優化代碼時我們會刻意增加代碼長度,來減少cache miss,增加編譯器發現並輸出cache preload指令的可能。

然而實際上,template的使用會提高編譯器發現優化的可能性,因為在編譯時template會提供更多的信息,使得靜態編譯器有更大的機會優化代碼,並且更有可能輸出cache miss較少的binary。

至於,C++ binary interface,通常工業界的做法是做個C的API wrapper,然後內部用C++實現,這樣C++代碼就有了C這樣乾淨的binary interface了。


一份C代碼,用C++原樣改寫後快了5%,誰有興趣分析一下?

原始 C 語言版:klib/khash.h at master · attractivechaos/klib · GitHub
用 C++ 照抄一遍:https://gist.github.com/chenshuo/a0e7a13622578445168d#file-khash-hpp
測試:https://gist.github.com/chenshuo/a0e7a13622578445168d#file-test-cc
編譯:g++ -std=c++11 -Wall -O2 http://test.cc
環境:g++ 4.8.4 on Ubuntu 14.04 64-bit
運行結果:

$ ./a.out
--- generating data... done!
[ht_khash_int] size: 624444
[ht_timing] 0.3411 sec
[ht_khash_int2] size: 624444
[ht_timing] 0.3150 sec
[ht_khash_int] size: 624444
[ht_timing] 0.3268 sec
[ht_khash_int2] size: 624444
[ht_timing] 0.3112 sec

ht_khash_int2[C++] 用時比 ht_khash_int[C] 少大約 5%.


效率的高低不在於語言提供了什麼特性或者你用了語言的什麼特性,而在於你多做了多少不必要的事情,不必要的事情做得越多,效率就越低。當一門語言提供了一些特性讓你可以非常容易和無痛地做不必要的事情的時候,程序員讓語言做不必要事情的幾率就會大大增加,因此語言的效率就會降低。C++ 給程序員提供了很多便利,自然也會導致程序員能夠更容易地引入不必要的操作。

但是,如果這些特性同時也能讓你非常容易和無痛地做有用和必要的事情,那部分的效率損失顯然也是值得的。仔細看一下 std::sort 的實現就知道,通過這樣極其複雜而精緻的實現來盡一切可能提高執行效率,如果沒有在標準庫里提供,程序員想要自己在 C 語言裏手寫寫到比它高效,那幾乎是不可能做到的事情。由於語言本身不提供高級的支持而造成程序員迴避使用一些高效演算法,最終導致整體效率下降這也是不能不考慮的一個因素。

總體而言,C++ 運行效率比 C 略低,低的程度視具體問題和程序員自身水平的不同而不同。現實生活中不可能有足夠巨大的工程讓水平相當的程序員用 C 和 C++ 各寫一遍然後比較效率,因此所有效率比較都是針對一些刻意構造的片段進行的,意義不大,低 5% 到 10% 是一個比較普遍的說法。

把虛函數作為 C++ 低效的證據是非常沒有道理的。如果不通過動態綁定機制,直接調用一個棧對象的虛函數的話,編譯器會直接處理成普通成員函數調用,調用效率和成員函數完全沒有區別。通過動態綁定機制調用是有效率損失的,但那也說明你在這個地方就是需要動態綁定這個特性,如果不使用虛函數的話,你就必須自己實現一個函數指針表,自己填表,自己查表,最終效率和虛函數調用還是一樣的。如果由於不了解一個特性而畏懼該特性可能造成的效率損失,說明對這門語言還沒有入門。

最後補充一句,提問者在評論中的態度非常差,我很生氣。


微軟的草藥·薩特(Herb Sutter)大大曾經在不止一個地方說過,C++設計出來的使命之一就是要不犧牲可以同C相比擬的性能,所以一直到C++ 14它都沒有偏離這個目標。

唯一的問題是,C++代碼用肉眼很難看出它後面做了什麼事情,只能記住正確的用法並且催眠自己」這種寫法既優美又高效「,初學者容易記錯正確的用法而掉進坑裡……


我來說一下我看到的所有靠譜答案的共同缺點:我覺得,C這種抽象成都巨低的語言要跟C++來比顯然需要考慮兩個問題:

1:假設這裡是一個不會濫用特徵的程序員,用C++建模還是選用了這些「表面上降低性能」的feature,那他用C語言,就可以不人肉把C++抽象展開成C來達到同等的解耦效果嗎?
2:如果我們限制這個程序員,僅使用「使用C++來完成這個任務」的這麼長的時間來讓他寫C語言,結果又如何呢?

所以說拿一個語言和他的子集來比較,這種idea真是無法忍受。你說你拿C++和C#來比,F#和haskell比,ironpython和pypy比,那也就算了,因為你完成一個目標所用的feature的組合方法是不同的,這才可以比。你拿C++跟C比,要怎麼比?


難道沒有人發現嗎,在PL流派中,Micro-Manage Everything流派(含C和C++)的特點就是可以無限抬杠下去,因為可以Micro-Manage嘛



某些C++抽象手段(最典型的例子是虛函數)會帶來一些性能上的「損耗」(注意,不是損失)。但這相比起得到的好處來說可以忽略不計。C要想到達同樣的效果,不僅在性能上一樣的得有損耗,而且還沒有語法上的共通性(因為不是語言特性)。維護性就會差一些,做起來也麻煩。
現代C++(主要指C++11以後),因為提供抽象手段而帶來效率上損耗的地方已經極少了(我不太想得出來),但在提高抽象度的方面又有了更大的提高(右值,range-base for ,統一的初始化等等等等),減少了很多那些同時追求語法抽象度和性能兩個方面而產生的變態庫設施(比如像bind1st神馬之類的,我都已經stop being able to understand them了~~)。
我看好Modern C++,因為在抽象能力+低階的性能模型+平台廣泛性的平衡性上,無乎沒有對手。


姑且認為題主問的是執行效率吧
表面上看,是C的執行效率要高,C簡單直接,沒有C++那些複雜的特性,編譯生成的彙編代碼都直接對應每行C代碼,沒有C++編譯器插入的奇怪的東西,比如 隱式類型轉換,虛函數調用,算符重載,拷貝構造等。

但是看問題不能這麼表面,新手們容易認為性能問題是選擇造成,只要我選擇了正確的語言、框架函數、演算法自然就有好的性能,實際上好的性能只能是靠Profiling工具壓榨出來的。
性能瓶頸之所以叫瓶頸,就是因為只是極少數的地方有性能問題,不可能是整個代碼到處都是瓶頸,上Profiling工具通常可以把性能問題定位到少數幾個函數和數據結構。

解決性能問題靠猜測是很沒效率的,即使你碰巧猜對了也很難證明。

如果是Windows,上VTune,哪裡是瓶頸一目了然,甚至給你定位到具體是哪行彙編碼。
如果是iOS,上instruments,是哪個函數的問題一目了然。

再回到C和C++的性能問題吧,我用C++可以提高開發效率,節省下來的時間可以用來做Profiling,那麼最後誰的執行效率高還真不好說呢。

拋開開發實踐,空談語言的效率是毫無意義的。


其實對於簡單的並不複雜的代碼,兩者之間的執行效率的差異是很微小的。但是要牽扯到開發效率方面,這個是不言自明的。
有句話說得好,大部分人的努力程度之低,根本輪不到拼天賦。
所以恕我直言,大部分人寫的代碼,程序的組織,用到的演算法、數據結構之爛,根本用不到拼語言效率的階段。
碰到討論這種問題的時候,只要告訴別人PHP是最好的語言就行了。


這個話題。。。無語,效率幾乎一樣。因為C++同樣是把性能放在數一數二的地位來考慮的。當然,C++可能會犧牲編譯期的成本。


每次提到語言效率都被提到這個網站, 雖然不是很看懂...

http://shootout.alioth.debian.org/


C++的親爸爸寫過一篇博客,指出了人們的一個誤解:
為了效率,要往底層去寫代碼。
鏈接在這裡
Five Popular Myths about C++, Part 3 :

Standard C++
他使用了qsort的例子來說明 C++ 在抽象程度更高的同時,效率也可以達到更高。(通過保留類型信息給編譯器。)

當然,qsort已經是一個大家都用爛的例子,他還舉過另外一個例子,當時震撼到我了:
假設我們需要寫一個函數生成一個電子郵件的地址,C++版本的這樣寫:

string compose(const string name, const string domain)
{
return name+"@"+domain;
}

這樣用:

string addr = compose("gre","research.att.com");

C版本需要手工操作內存:

char* compose(const char* name, const char* domain)
{
char* res = malloc(strlen(name)+strlen(domain)+2); // space for strings, "@", and 0
char* p = strcpy(res,name);
p += strlen(name);
*p = "@";
strcpy(p+1,domain);
return res;
}

這樣用:

char* addr = compose("gre","research.att.com");
// …
free(addr); // release memory when done

首先,親爸爸很高冷的問大家 C 版本里有沒有bug(吐槽c版本太複雜)。其次,你猜哪個版本的效率高?是C++的效率高,因為不需要strlen,而且也不需要free動態分配的內存。

原文在此
Five Popular Myths about C++, Part 1 :

Standard C++

我的翻譯在此
譯.C++的5個迷思


問題本身就有問題,性能不完全在於語言和工具,用vb寫的好代碼可以快過用彙編寫的垃圾代碼。
j


有一個格鬥家他叫C,格鬥時擅長掏襠挖眼,殺人效率很高;
另一個格鬥家和C師出同門,叫C++,格鬥時除了可以掏襠挖眼之外,還可以附帶一些花哨的招式;

問誰的殺人效率高?

C可以的C++都可以,C++也可以掏襠挖眼。


C++在運行效率上比C低多少,取決於你在代碼裡面用了什麼。有些特性,可能適合開發大程序,但是會犧牲效率。如果你像C一樣寫C++,基本是看不到效率差距的。


不能離開場景談效率


推薦閱讀:

對使用 C++ 異常處理應具有怎樣的態度?

TAG:編程語言 | C(編程語言) | 性能 | C++ |