一個採用GC的原生程序語言有沒有可能性能上超越非GC的原生程序語言?

使用GC的語言,似乎給人一種印象就是會「慢」一些。

為了避免 JIT vs. 原生程序 這種 擴大化的討論,這裡把GC限定在:

採用GC的原生程序語言(比如D那樣) 和 C/C++/Rust 這樣沒有GC的原生程序進行比對。

原生程序語言指「直接編譯成二進位代碼的語言」,不包括解釋執行的語言以及採用JIT方式執行的語言。

當然這裡性能是用 整個吞吐量來衡量,由於大多數模型下GC的STW,實時性上可能不如非GC的C/C++/Rust(她們內存釋放均攤了)。

我個人覺得GC本質類似於批處理釋放內存,如果語言模型設計的好,性能是有可能超越C/C++的(假若編譯器優化都做的足夠好的情況下)。

因為,C/C++/Rust這種非GC的模式本質是把內存釋放做了均攤。

而使用GC的批處理釋放內存,這種批處理的方式有可能增加整個吞吐量。

當然使用GC必然涉及一些「薄記」工作的成本,不過其實C++的智能指針也同樣增加了「薄記」成本的。其實很多libc庫本身對內存的管理也背地裡有不少「薄記」工作的成本的。甚至有一些C/C++程序會把內存釋放動作單獨放到一個thread裡面去進行,這其實就是類似於自己人肉DIY一個簡化版GC嘛。

所以整體考慮的話,一個採用GC的原生程序語言有沒有可能性能上超越非GC的原生程序語言?


Garbage Collection Can Be Faster Than Stack Allocation


只要你指定寫程序的人的話,那絕對是非常有可能的。這都沒什麼好討論的了。


在性能優化領域,向用戶開放了最低層次的Micro-manage能力的系統(比如允許嵌入彙編代碼的C/C++)至少在打嘴炮的時候是可以無限抬杠下去的:你確實沒法說服他,因為人家可以Micro-manage啊,人家大不了把你模擬出來


C++不是把內存釋放做了均攤,而是把內存管理策略交給程序員實現。最簡單的方法,在析構函數里釋放,是有點類似均攤的感覺,當然也會帶來內存碎片的代價。

在允許發揮語言的極限性能的情況下,GC語言是不可能超越C++的,但是在這種場景下,C++程序員要付出的心力恐怕也是駭人聽聞的。

首先,在允許發揮語言的極限性能的情況下,C++內存管理效率至少可以做到和帶GC的語言一樣好。C++可以自己建一個內存池,高效實現任何內存的自動分配/回收策略。對手語言的GC採用什麼策略,用C++一定可以實現同樣的內存管理策略。

這說明C++的內存管理效率至少可以做到和對手的一樣好。

其次,在允許發揮語言的極限性能的情況下,C++內存管理可以做到一些GC語言做不到的特技。(我就不說C++還能內嵌ASM這種作弊工具了。)

比如在已知一個對象的引用關係里不會出現環路的情況下,C++可以直接用shared pointer指定某一個實例採用引用計數策略。自動GC的語言,恐怕就不敢簡簡單單地只用引用計數策略,哪怕程序員從業務邏輯上知道這個實例是可以使用引用計數策略的。

再比如分代回收策略,C++不僅能用內存池實現分代回收,業務上如果知道一個對象是永生的我還能直接把它放到老年代而不用一步步提升(或者其他的精細內存控制技巧)。

C++還能利用內存分配器allocator從棧上分配固定內存給動態對象使用,如果運行時對象需要更多的內存再從堆上分配。由於棧上分配內存沒有運行時開銷,在內存需求大致固定但極少情況下需要動態分配的場景中,使用這種技巧能極大提升性能。(動態GC語言,即使事先Reserve也是要運行時在堆上搜索可用空間的,reserve只能避免頻繁申請內存而不能像在棧上分配那樣完全沒有運行時消耗)

經常被人提到的C++對GC的一大缺點:析構函數的開銷,其實也可以通過Arena Allocator或者類似的技術來避免。

以上所說的一切都建立在在允許發揮語言的極限性能的情況下。C++因為可以無限精細對內存的管理,實現任何可能的內存策略,理論上是GC語言是怎麼也比不上的。

但是GC語言的長處在於解放程序員的生產力,題主要GC語言和C++比效率,實在是拿一家的短處和另一家的長處相比,結果不言而喻。


所有比較性能但又不限定具體應用場景的,都是在瞎扯淡。


GC的問題不是慢,而是你不知道它什麼時候會慢。

原生程序可能比採用GC的程序慢,但是你知道它哪裡會慢。它的運行速度基本是確定的。

GC程序就不一樣了。你不知道它啥時候就Stop the world.如果GC程序全程不觸發GC,那有可能比原生程序快。但是,你很難確定它什麼時候會觸發GC,會觸發幾次GC。萬一剛好在關鍵時刻(比如搶月餅的時候(逃)觸發GC呢?那你就悲劇了。


由於,目前的幾乎所有計算機硬體是不支持gc的,所以從軟體來說,你的帶GC語言肯定是由不帶GC語言實現的,所以,對任何帶GC語言的程序,都存在一個至少性能不會更差的不帶GC語言實現,從這個角度說,如果題主的問題要有意義,應該至少限定兩個語言的對比程序是「代碼等價」的,即實現功能和實現方式都差不多,只是一個用了GC,一個手動申請釋放

在這個前提下,題主的觀點是對的,的確存在批量釋放性能更高的情況,我自己就遇到過,可以搜一下dhrystone這個benchmark。。。。

這個bench主要是整數和數組運算,其中也有結構體運算,關鍵在於,網上能找到的C或java(大概也就是原版的bench代碼)對於結構體的測試是兩個結構體來回賦值,但是後面python引入這個bench的時候(你的py安裝目錄下的lib/test/pystone.py)對它做了修改,將結構體賦值改為申請新對象,釋放老對象,這樣一來,就從一個固定內存運算的bench,變成了一個帶大量內存申請釋放的bench,題主可以試著按照pystone.py的實現修改或重寫下它的C和java實現,你就會發現,java比c快好多,因為在大量申請釋放並且維持存活對象很少的情景下,java的節點複製gc演算法極快,比libc的malloc和free快太多了

相關理論可以去看《垃圾收集》這本書的節點複製一章,裡面提到一個文獻,Appel曾經論證了,當存活對象和垃圾對象佔用空間比小於1:7時,節點複製的自動GC演算法效率超過手工申請釋放,這本書建議精讀,寫得非常好


你把D往好里寫,把C/C++往渣里寫,當然前者性能好了。

於是KUSO完畢,正式說人話:

GC和手工操作內存哪個性能好,本質上就是這個問題:

可用於各種場合的泛用演算法,和針對單一場合優化的專用演算法,哪個性能好?

根本不用想,理論上肯定是後者性能好。

當然,理論和實踐是兩回事。否則就不會有這麼多GC語言大行其道了。這其中的原因有:

  1. 有GC可以大幅度簡化程序編寫,降低程序猿和程序媛們的負擔,消滅一些常見但不好排查的內存管理BUG。還方便構建一些高級語法。
  2. 實踐中,大多數場合下GC損失的性能並不多。Java有段時間最喜歡拉出C++來對比性能。GC的實現,一般都是業界大牛的作品,優化GC也是編程語言領域的一個重要課題。GC演算法的質量一般都是很高的。相比而言,C/C++里自己管理內存的人,水平就沒有保證了。
  3. 手工管理內存理論上可以做到比GC更優的性能,但是開發成本(時間,精力)上有可能得不償失。為優化10%的性能,多耗100%甚至1000%的開發時間,顯然是不值的。
  4. 許多程序的性能瓶頸並不在內存管理,而是IO、並發等。這時候去琢磨內存管理的性能是捨本逐末。
  5. 有時候性能無關緊要。一個就用一兩次的程序,用非GC的語言寫,速度是0.03秒。用GC,哪怕慢100倍,又有多大差別?

GC語言讓人感覺「慢」,並不一定是因為他們本身的性能劣勢,更可能是因為他們被用在了根本快不起來的場合。

所以實際動工時應關注問題本身,內存管理適合用哪種辦法,就用哪種。這需要經驗,但不需要糾結。


如果你只是想要一個數學上的證明,那麼很簡單。

目前所有常見的GC都是用C或C++實現的,而從功能角度來說C又是C++的一個真子集,所以很顯然C++至少可以達到同等的效率。

又因為沒有任何一個自動的GC能識別出所有的程序邏輯並進行最優化處理,因為識別程序邏輯是一個停機問題,所以C++至少可以比自動化GC多做一些手工優化。

綜上所述,對於任何一個使用自動化GC的程序,總存在一個對應的功能相同但性能更高的C++程序。

但上面的話純屬扯淡,因為沒有考慮到實現這樣一個程序所需的代價。


不同意某些人的說法

有些人把GC與Allocator相提並論

兩者顯然完全不同

Allocator是內存分配演算法

而GC從本質上來說並不需要關心如何分配內存(雖然大多數GC順帶實現了一個高性能的泛用內存分配演算法)

GC顧名思義垃圾回收

他的主要任務是檢查出哪些東西是垃圾

所以理論上來說GC演算法只要能鑒別出垃圾就可以了

至於如何分配以及如何釋放內存不是他的本職工作

所以GC是一種管理對象生命周期的方法

可以與之比較的應該是其他管理方法

LZ說了GC與智能指針的比較

不過只說對了一半

兩者確實都是簿記的成本

但是GC還多了跟蹤的成本

不然無法解決循環引用問題

所以GC的成本顯然比智能指針高的多

理論上來說是完全可以寫出不會出現循環引用的程序的

所以循環引用並不是一個死穴 非GC就不可破

當然智能指針的性能當然也無法和raw pointer手動管理比

當然GC確實有一個很大的優勢

這個優勢其實類似於

位元組碼在虛擬機上運行所擁有的優勢

就是可以做動態優化

而且天然支持內存重定向

GC能掌握全局的對象生命周期信息

自然可以做一些全局的動態優化

比如設法讓一堆訪問平凡的對象移動到一起

使得緩存命中率更高

當然這操作本身的代價也很高

動態優化都需要用一些概率學方法來評估是否划算

這個優勢自然是 其他管理方法沒有辦法得到的

要說性能會不會更好呢

我的結論是:

GC在實現非常優秀的時候

它任何時候都能得到一個不算差的性能

手動管理的方式

依賴於程序員水平 他的態度 和對特定平台的熟悉程度

性能上限自然可以遠超過GC 性能下限也可以低於GC


覺得有點像手動擋和自動擋汽車的區別,還是看好手動擋。


頻繁創建銷毀內存/對象的場景本來就是 GC 語言更勝一籌。。。


C++是不計開發成本和程序員素質能力。

很多人提到的事物,忘記了 C ++ 在軟體層是一張白紙,由你來描繪的特點。

所以 C ++ 在實現軟體層的緩存,小對象內存池技術方面是自由的啊!同志們!

而並不是說你必須拿 頻繁的堆內存分配,和 gc 去比較。而棧上臨時內存的分配和回收是沒有什麼成本可言的。

所以,其他語言的 gc,也不過是運行平台提供了一個對你透明的軟體層而已,它可以基本等同於你在 C ++ 層面自己實現的一個軟體層。

gc 的主要目的是降低程序員消耗在內存管理方面的成本。但它也因為需要一定的普適性和非具體情況具體分析的非定製的特點而犧牲掉一些個性性質的性能。

而 C++ 是自由的,它的實現效果是跨度很大的,這就是說,不是 C++ 的性能比其他語言是好還是壞,而是看 C++ 的實現者在這方面有多少追求而已。只要他想注重性能,他就能像海綿里擠出水一樣的在這方面進行提升。

還有是否 native 主要是啟動過程有差異。 java 是解釋型還是編譯型語言目前沒有絕對結論,因為 java 執行過程,兼具兩者。


Garbage collection can be faster than stack allocation_百度學術

我留著慢慢看……


我相信很多場景下,甚至是多數場景下,GC 的吞吐量平均性能要高於手工管理內存,這本質上是內存池 vs 平凡的內存管理,而 GC 這個內存池實現質量會比大部分手工實現的內存池性能好。


不考慮理論只說實際情況啊...

有gc的語言減少了程序員的心智負擔之後可能對性能有正面提高哦~

比如我, 我用c寫的程序就沒有我用crystal寫的快. 這你哪兒講理去?


這個問題就好比「有沒有適應所有情況的最高效的內存分配演算法」,這個問題在我看來就好比「這個世界上有沒有最好看的人」一樣。


理論上沒可能


有,一個寫的好,一個寫的爛。

同等情況下不會,因為gc需要額外代價。


手工管理內存相當於程序員寫了個針對當前應用的定製化gc么,所以當然有可能了


Java, C#,Python等語言慢並不是慢在GC上!


理論上,是的。GC可以比手動內存管理更有效率,甚至接近棧上的內存分配,也能更好的減少內存碎片,使得分配新的內存時,異常高效。但問題來了,內存回收會帶來業務停頓,對於實時系統,稍微長一點的業務停頓是不可接受的。

所以,GC的根本問題在於如何實現高效的回收演算法,儘可能減少業務停頓的頻率和時間。

當然,對實時性要求這麼高的系統,是非常少見的,通常也只是系統中的一個關鍵組件,用非GC語言來寫,也很正常。

每一種技術和方法都是為了解決某一類問題而引入的,都會有其局限性,我們只要利用其優點就好了。


c++沒操作系統這個gc不也跑不起來嗎?

哪有沒gc的語言啊


推薦閱讀:

寫工業級別代碼是怎樣一種體驗?
一段簡單文件IO的C程序,為什麼我的優化反而更慢?
如何優化QTableView的性能?
你見過哪些令你瞠目結舌的C/C++代碼技巧?
gcc為何有時會在call前push eax,默認的調用約定不是cdecl?

TAG:C | 編譯原理 | CC | GC垃圾回收計算機科學 | 性能優化 |