從C++的Return Value Optimization (RVO)到C#的value type (轉)
從C++的Return Value Optimization (RVO)到的value type:namespace prefix = o ns = "urn:schemas--com::office" />
先看一段簡單的C++程式碼:
Type get(int I){
return Type(i);
}
Type t = get(1);
這裡, 我們從C++的基本語義看上去, 應該是Type(i) 一次複製構造, 在堆疊中生成一個臨時;然後,用該物件構造返回物件;然後對這個臨時物件呼叫解構函式;在呼叫者方, 用返回的臨時物件呼叫複製建構函式以初始化物件t, 返回物件的解構函式在這之後, 函式返回之前呼叫。
所以, Type t = get(i); 應該有三個複製建構函式和兩個解構函式的呼叫.
可是, 還有一種說法是, 可能會對這兩個臨時物件進行,最終的最佳化結果會是隻有一次的建構函式。因為很明顯地可以看到, 這裡我們其實只是要用一個整數構造一個Type物件。
嗯. 似乎很有道理!
那麼, 哪一種說法對呢? 沒有調查就沒有發言權,於是本人用VC++6.0做了實驗。 放了些cout<
“你個弱智編譯器!腦袋進水了吧?”(忘了編譯器沒腦袋了)“很明顯在這個例子裡我的兩個臨時物件都沒有用的啊!”
於是,上網, 查資料, 一下吧!
下面是我查到的一些結果:
其實, 這種對值傳遞的最佳化的研究, 並不只侷限於返回值。對下面這個例子:
void f(T t){}
void main(){
T t1;
f(t1);
}
也有這種考慮。
f(T)是按值傳遞的。語義上應該做一個複製, 使得函式內部對T的改變不會影響到原來的t1.
但是,因為在呼叫f(t1)之後, 我們沒有再使用t1(除了一個隱含的destructor呼叫),是否可能把複製最佳化掉, 直接使用t1呢?這樣可以節省掉一個複製建構函式和一個解構函式。
可是, 不論是對返回值的最佳化, 還是對上面這種區域性物件的最佳化,在1995年的C++新標準草案出臺前都是為標準所嚴格限制的 (雖然有些編譯器並沒有遵行這個標準, 還是支援了這種“最佳化”)
那麼, 這又是為什麼呢?
這裡面涉及到一個普遍的對s-effect的擔憂。
什麼又是side-effect呢?
所謂side-effect就是一個函式的呼叫與否能夠對的狀態造成區別。
int add(int I, int j){return I+j;}就是沒有side-effect的,而
void set(int* p, int I, int v){p[I]=v;}就是有side-effect的。因為它改變了一個陣列元素的值, 而這個陣列元素在函式外是可見的。
通常意義上來說, 所有的最佳化應該在不影響的可觀察行為的基礎上進行的。否則,快則快了, 結果卻和所想要的完全不同!
而C++的複製建構函式和解構函式又很多都是有side-effect的。如果我們的“最佳化”去掉了一個有side-effect的複製建構函式和一個解構函式, 這個“最佳化”就有可能改變程式的可觀察行為。(注意, 我這裡說的是“可能”,因為“負負得正”, 兩個有side-effect的函式的呼叫, 在不考慮並行執行的情況下, 也許反而不會影響程式的可觀察行為。不過, 這種塞翁失馬的事兒, 編譯器就很難判斷了)
基於這種憂慮, 1995年以前的標準, 明確禁止對含有side-effect的複製建構函式和解構函式的最佳化。同時, 還有一些對C++擴充的提議, 考慮讓程式設計師自己對類進行允許最佳化的宣告。 程式設計師可以明確地告訴編譯器:不錯, 我這個複製建構函式, 解構函式是有side-effect, 但你別管, 儘管最佳化, 出了事有我呢!
哎, side-effect真是一個讓人又恨又愛的東西!它使編譯器的最佳化變得困難;加大了程式維護和的難度。因此functional language 把side-effect當作洪水猛獸一樣,乾脆禁止。但同時,我們又很難離開side-effect. 不說程式設計師們更習慣於imperative 的方法, 象操作,IO操作都天然就是side-effect.
不過,個人還是認為C++標準對“最佳化”的保守態度是有道理的。無論如何,讓“最佳化”可以潛在地偷偷地改變程式的行為總是讓人想起來就不舒服的。
但是, 矛盾是對立統一的。(想當年俺馬列可得了八十多分呢)。 對這種aggressive的“最佳化”的呼聲是一浪高過一浪。 以Stan Lippeman為首的一小撮頑固分子對標準的顛覆和和平演變的陰謀從來就沒有停止過。 這不?在1996年的一個風雨交加的夜晚, 一個陰險的C++新標準草案出爐了。在這個草案裡, 加入了一個名為RVO (Return Value Optimization) 的放寬對最佳化的限制, 妄圖走資本主義道路, 給資本家張目的提案。其具體內容就是說:允許編譯器對命名過的區域性物件的返回進行最佳化, 即使複製建構函式/解構函式有side-effect也在所不惜。這個提議背後所隱藏的思想就是:為了提高, 寧可冒改變程式行為的風險。寧要資本主義的苗, 不要社會主義的草了!
我想, 這樣的一個罪大惡極的提案竟會被提交,應該是因為C++的值複製的語義的效率實在太“媽媽的”了。 當你寫一個 Complex operator+(const Complex& c1, const Complex& c2);的時候, 竟需要呼叫好幾次複製建構函式和解構函式!同志們!(沉痛地, 語重心長地)社會主義的生產關係的優越性怎麼體現啊?
接下來, 當我想Google C++最新的標準, 看RVO是否被最終採納時, 卻什麼也找不到了。 到ANSI的網站上去, 居然要付錢才能文件。 “老子在城裡下館子都不付錢, down你幾個爛文件還要給錢?!”
故事沒有結局, 實在是不爽。 也不知是不是因為標準還沒有敲定, 所以VC++6 就沒有最佳化, 還是VC根本就沒完全遵守標準。
不過,有一點是肯定的。 當寫程式的時候, 最好不要依賴於RVO (有人, 象Stan Lippeman, 又叫它NRV最佳化)。 因為, 不論對標準的爭論是否已經有了結果, 實際上各個編譯器的實現仍還是各自為政, 沒有統一。 一個叫tt Meyers的傢伙(忘了是賣什麼的了)就說, 如果你的程式依賴於RVO, 最好去掉這種依賴。也就是說, 不管RVO到底標準不標準, 你還是不能用。 不僅不能用, 還得時刻警惕著RVO可能帶來的程式行為上的變化。 (也不知這幫傢伙瞎忙了半天到底為啥!)
說到這裡, 倒想起了C#裡一個困惑了我很久的問題。記得讀C#的specification的時候, 非常不解為什麼C#不允許給value type 定義解構函式。
這裡, 先簡略介紹一下C#裡的value type (原始資料型別, struct 型別)。
在C#裡的value_type就象是值, 永遠只能copy, 取值。因此, 它永遠是in-place的。如果你把一個value type的資料放在一個物件裡,它的生命期就和那個物件相同;如果你宣告一個value type 的變數在函式中, 它的生命期就在lexical scope裡。
{
The_ValueType value;
}//value 到這裡就死菜了
啊呀呀! 這不正是我們懷念的C++的stack 嗎?
在C++裡,Auto_ptr, shared_ptr, 容器們, 不都是利用解構函式來管理資源的嗎?
C#, 雖然利用garbage collection技術來收集無用物件, 使我們不用再擔心的回收。 但garbage collection並不保證無用物件一定被收集, 並不保證Dispose()函式一定被呼叫, 更不保證一個物件什麼時候被回收。 所以對一些非記憶體的資源, 象資料庫連線, 連線, 我們還是希望能有一個類似於smart pointer的東西來幫我們管理啊。(try-finally 雖然可以用, 但因為它影響到lexical scope, 有時用起來不那麼方便)
於是, 我對C#的取消value type的解構函式充滿了深厚的階級仇恨。
不過, 現在想來, C#的這種設計一定是懲於C++失敗的教訓:
- value type 沒有複製建構函式。C#只做預設copy, 沒有side-effect
- value type 不準有解構函式。C#有garbage collection, 解構函式的唯一用途只會是做一些side-effect象關閉資料庫連線。 所以取消了解構函式, 就取消了value type的side-effect.
- 沒有了side-effect, 系統可以任意地做最佳化了
對以下程式:
The_Valuetype get(int I){return The_Valuetype(i);}
The_Valuetype t = get(1);
在C#裡我們可以快樂地說:只呼叫了一次建構函式。 再沒有side-effect的沙漠, 再沒有難以最佳化的荒原, smart pointer望而卻步, 效率之花處處開遍。 I have a dream, ……
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-990029/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- C# return dynamic/anonymous type value as function resultC#Function
- Value Type vs Reference Type in SwiftSwift
- PHP Deprecated: Assigning the return value of new by reference is deprecatedPHP
- LOW_VALUE、HIGH_VALUE、ENDPOINT_VALUE轉換--UTL_RAW、DBMS_STATS.CONVERT_RAW_VALUE
- C# 遍歷Dictionary並修改其中的ValueC#
- [轉]分析函式 last_value的使用函式AST
- 如何將列中的low_value和high_value轉換為實際的值
- TiDB從關係模型對映到key-value(圖)TiDB模型
- c# 對JSON字串排序(KEY/VALUE)C#JSON字串排序
- Can't use function return value in write context 使用empty遇到報錯FunctionContext
- solidity的msg.valueSolid
- Javascript得到CheckBoxList的ValueJavaScript
- mysql 主從同步 Error 'Out of range value for column的問題MySql主從同步Error
- 分析函式——FIRST_VALUE()和LAST_VALUE()函式AST
- Covariant return type
- new_value
- Oracle分析函式-first_value()和last_value()Oracle函式AST
- textarea中的innerHtml,innerText和valueHTML
- 函式引數的 Default value函式
- 【C語言】編寫函式 unsigned int reverse_bit(unsigned int value); 這個函式的返回值吧value的二進位制位模式從左到右翻轉後的值。C語言函式模式
- java.lang.IllegalArgumentException: Cannot convert value of type [$Proxy7 implemJavaException
- 如何拿到註解@ApiModelProperty(value = “單位名稱“, name = “orgName“)中的value值;API
- SpringBoot升級到3.2.0報錯Invalid value type for attribute ‘factoryBeanObjectType‘: java.lang.StringSpring BootBeanObjectJava
- JavaScript select valueJavaScript
- jQuery [attribute*=value]jQuery
- jQuery [attribute~=value]jQuery
- jQuery [attribute|=value]jQuery
- jQuery [attribute$=value]jQuery
- jQuery [attribute^=value]jQuery
- jQuery [attribute!=value]jQuery
- jQuery [attribute=value]jQuery
- 關於value objectObject
- Value Object 和 POJOObjectPOJO
- WPF ProgressBar show value
- jquery value的不一般jQuery
- Spring中Value註解的使用Spring
- 如何實現key, value有序的HashMap?HashMap
- 不使用@Value的優雅寫法