C++ワンポイントレッスン Lesson-1 i++ と ++i の違い

前置きと後置きの演算子

  • C++には、C言語と同様の変数をインクリメント・デクリメントする演算子(++、--)がある。
  • C言語の時は、おもに整数,ポインタ変数などに対し、アセンブラ命令のインクリメント・デクリメントに相当する命令を直接記述することにより高速で効率の良いコード生成を可能としてきた。
  • C++では、オブジェクトの演算子をユーザーが定義できるようになったため、演算子の持つ特性と役割が大きく拡張された。

前置き++と後置き++の違い < レジスタ変数時>

  • C言語の時と同様に、レジスタ変数における前置きと後置きのコード生成を考える。

◆ C++での記述

if (i++ < 10) Hoge(i)

◆ 生成されるアセンブラコード

mov ebx, eax
inc eax
cmp ebx, 10
jnc L1
push eax
call Hoge
L1: 

◆ C++での記述

if (++i < 11) Hoge(i);

◆ 生成されるアセンブラコード

inc eax
cmp eax, 11
jnc L1
push eax
call Hoge
L1: 

前置き++と後置き++の違い < レジスタ変数時> 結果

  • 前置き++の場合のデメリット
  • 命令が1つされる。(メモリの増加、速度の低下)
  • レジスタ変数が1つ消費される。(最適化の効率低下)
  • コンパイル時間の増加。(最適化の余地が多いため、最適化ルーチンのコストが増加する)

※実際には、この程度のコードならほとんどのコンパイラは最適化が可能なので、実行時の差異は生じません。
※生成されるアセンブラはイメージです。実際にはコンパイラによって生成されるコードは異なります。

前置き++と後置き++の違い <クラスオブジェクト時>

  • クラスオブジェクトでオーバーライドされた演算子における前置きと後置きのコード生成を考える。

◆ C++での記述 (後置き++の場合)

CFoo foo;
if (foo++ < 10) Hoge(foo)

◆ インライン展開されるコード

CFoo tmp = foo;
foo.m_value.plus1(); // m_valueをインクリメントする関数
if (CFoo::operator < (tmp.m_value, 10))
{
  Hoge(foo)
}
  • foo++は、if文の成否にかかわらず実行されなければならない。
  • if文の比較(< 10)は、foo++を実行する前の状態をif文で比較する必要がある。
  • コンパイラは、まずfooの状態を記憶し、++fooを実行してからif文の処理を行う。

◆ C++での記述 (前置き++の場合)

CFoo foo;
if (++foo < 11) Hoge(foo);

◆ インライン展開されるコード

if (CFoo::operator < (foo.m_value.plus1() , 11)) 
{
  Hoge(foo)
}
  • if文の実行は、++fooの後の値を参照するため、プログラム通りの手順で処理される。
  • 不要なオブジェクトの生成とコピーが無いため、高速でコードサイズも少なくて済む。

前置き++と後置き++の違い <クラスオブジェクト時> 実装

CFooの実装は以下のようになる。

// CFooのインスタンスとなるオブジェクト
struct CValue {
  int hohoho;
  CValue & plus1() { ++hohoho; }
};
bool operator < (const CValue& v, int i) { return v.hohoho < i; }

struct CFoo {
  CValue m_value;  // オペレータ++でインクリメントさせたいオブジェクト
  CFoo() {}
  bool operator < (int n) { return m_value  < n; }       
  CFoo& operator ++() {
    m_value .plus1();
    return *this; 
  }
  CFoo operator ++(int) {
    CFoo tmp = *this;   //<- オブジェクトの生成とコピーが発生
    m_value .plus1();
    return tmp;         //<- オブジェクトの生成とコピーが発生
  }
}

前置き++と後置き++の違い <クラスオブジェクト時> 結果

  • 前置き++時のデメリット
  • CFooのオブジェクトの生成の追加
  • CFooオブジェクトの代入(コピー)の追加(インライン展開されない場合は2回発生する)
  • コード量の増加
  • 最適化コストの増加

前置き++と後置き++のパフォーマンス測定

STLのvector<int>とmap<int,int>のiteratorをインクリメント(++)する実行時間を、前置きと後置きで比較してみました。

オブジェクトmap<> iterator++map<> ++iteratorvector<> iterator++vector<> ++iterator
実行時間(Releaseビルド)1.3008150.3004590.5672460.566541
比率100.00%23.10%100.00%99.98%
実行時間(Debugビルド)1.3617220.1081101.2031010.157290
比率100.0%7.94%100.00%13.07%
  • vector<int>::iteratorの場合は、int*としてコンパイルされるため全く差は出ませんが、map<int,int>::iteratorの場合は4倍以上の速度差が出ています。
  • 最適化が行われないデバッグビルドにおいては、map<int,int>::iteratorは12倍以上、vector<int>でも7倍以上、前置き++のほうが高速になっています。
  • つまり、最適化が効きにくい複雑なプログラムほど、後置++のロスは大きくなることが予想されます。

この結果を見ても、意味のない i++ を書きますか?


[ 戻る ]

※測定環境 Athlon64 3.0GHz x2 VisualStudio 2009

ssacontents


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2010-02-08 (月) 23:58:31 (3230d)