RVO,NRVOのテスト

【環境】Visual Studio.NET 2003/ WindowsXP Pro.

折角やったので自分のためにもネタにしやすい部分を記録として残しておきます。

RVO(Return Value Optimization)とは値渡しでオブジェクトを返す関数において関数内で生成され計算される一時オブジェクトを生成せずに、戻り値となる一時オブジェクトに直接を計算をしようという最適化です。これにより一時オブジェクトの生成を1つ減らすことができ汎用される関数においてはパフォーンマンスの向上が見込めます。と、書いても分かりづらいので例を出します。通例、加算演算子は次の様に定義されます。

const Complex operator +( const Complex &lhs, const Complex &rhs ){ //…(1)
    Complex temp(lhs);
    lhs += rhs;
    return temp;
}

ですが、ここでは簡単のため加算代入演算子の内容をメンバ変数real,imagとして展開します。

const Complex operator +( const Complex &lhs, const Complex &rhs ){ //…(2)
    Complex temp(lhs);
    lhs.real += rhs.real;
    lhs.imag += lhs.imag;
    return temp;
}

ここでMSVC(Microsoft Visual C++)の場合一時オブジェクトに名前を付さなければRVOが適応されます。そのためには次の様にreturn文上で一時オブジェクトを生成します。

const Complex operator +( const Complex &lhs, const Complex &rhs ){ //…(3)
    //Complex temp(lhs); //名前付きオブジェクトでは駄目
    return Complex( lhs.real += rhs.real, lhs.imag += lhs.imag );
}

これで(MSVCでは)RVOが適応されます。但し見ての通り、場合によってはRVOのためだけにコピーコンストラクタの定義が必要となります。
ところで、もう一つNRVO(Named Return Value Optimization)というものがあります。これはその名の通り名前のついた一時オブジェクトをも最適化しようというものです。実はこれが適応されると苦労せずとも一般的書式の(1)の時点で最適化されるのですが、残念なことにMSVC7.1では実装されていません。MSVCに限らずRVO,NRVOは全てのコンパイラで実装されているわけではないようです。
NRVOを使うとRVOの冗長性もなく可読性を保てますし、さらに良いことに関数内に複数のreturn文があっても全て同じ名前のオブジェクトを返す限り、NRVOは適応されるようです*1


今回は、といっても2年前のコードですが、RVOの効果を試すべく以下の5つのバージョンを定義しました。

//NRVOを期待したバージョン(1)
const Complex version1( const Complex &lhs, const Complex &rhs){
    Complex retVal;
    retVal.real = lhs.real + rhs.real;
    retVal.imag = lhs.imag + rhs.imag;
    return retVal;
}

//RVOを期待したバージョン(2)
const Complex version2( const Complex &lhs, const Complex &rhs){
    double r = lhs.real + rhs.real;
    double i = lhs.imag + rhs.imag;
    return Complex(r,i);
}

//NRVOを期待しかつコピーコンストラクタ1文で実装したバージョン(3)
const Complex version3( const Complex &lhs, const Complex &rhs){
    Complex retVal(lhs.real + rhs.real, lhs.imag + rhs.imag);
    return retVal;
}

//RVOを期待しかつコピーコンストラクタ1文で実装したバージョン(最速)(4)
const Complex version4( const Complex &lhs, const Complex &rhs){
    return Complex(lhs.real + rhs.real, lhs.imag + rhs.imag);
}

//the version based on (12.2/2)(5)
const Complex version5( Complex lhs, const Complex &rhs){
    lhs.real += rhs.real;
    lhs.imag += rhs.imag;
    return lhs;
}

(5)はBoost::operatorsのドキュメントで紹介されていたNRVOの代替手段ですが、説明は割愛します。
さて、次のような形で1千万回回してみました。

Complex c1, c2, c3, local_c;

//a + b(2項)
for(int i=0; i<repetition; ++i){ \
    local_c = c1 + c2;
}

//a + b + c(3項)
for(int i=0; i<repetition; ++i){ \
    local_c = c1 + c2 + c3;
}

結果大体こんな感じで圧倒的に(4)の効率が良い結果となりました。具体的には2項で200後半、3項で300後半(msec)、安定しませんが(1)と比較すると2倍近くまで出ました。満足行く結果です。

a + b     : 4 < 2 = 3 < 5 < 1
a + b + c : 4 < 5 < 2 < 1 = 3

演算子を繋げると(5)の効率が上がるのでRVOも実装されていない場合は(5)も有用かもしれません。


何も動くものを置かないと干されそうとりあえず実行ファイル置いておきます*2。といっても何もおもしろいものでは無いです。上のをやってるだけ。しかもコマンドプロンプト専用です。コマンドプロンプトから実行して下さいね、って面倒なのでバッチファイルもくっつけておきます。拡張子(.bat)のファイルを実行して下さい。なお、うちと大分違う結果が出たりしたら報告して下さるとうれしいです。

あれ?これ書くのに2時間かかったヨ!・゚・(ノД`)・゚・



【参考文献】

  1. [Bulka00] Dov Bulka and David Mayhew著、浜田光之監修、浜田真理訳『Efficient C++』(ピアソン・エデュケーション、2000年)
  2. [Frey04] Daniel Frey. "Header Documentation". Boost C++ Libraries. (online), available fromhttp://boost.org/libs/utility/operators.htm, (accessed 2006-09-29).

*1:環境が無いため未確認。

*2:念のためウィルスチェックは通してあります。