読者です 読者をやめる 読者になる 読者になる

ichirin2501's diary

いっちりーん。

C++のmapについてのめも

速度向上を考える上で、mapの使い方で差が出たのでめも。


mapは[ ]演算子オーバーロードされているので、演算子の書き方を多用していた。演算子で書くと存在しないkeyを指定した場合、自動で生成してくれる。これが便利なときもあれば不便なときもある。
例えば、メモ化探索にmapを利用していた場合、既に探索した状態かどうかを調べるときに[]演算子で勝手に状態生成してもらっては困る。
なので、今まではこんな感じで書いてた。

map<int,int> mii;

int dfs(int S){
  if( mii.count(S) ){
    return mii[S];
  }
  int ret = 0;
  /*
    処理
   */
  return mii[S] = ret;
}

countメソッドはkeyがあるかどうか(0or1)を返す。勝手に状態数は増えない。
でも、どう考えても 探索3回してるよねこれ


mapの[]演算子の内部処理をgdbデバッガで軽く見てみたところ、
lower_boundメソッドを呼んで、end()と比較して、等しくないならvalueを更新、そうでないならpairの挿入という処理の流れっぽかった。
countメソッドも覗いてみたら、内部でfindを呼んでいる。findの内部処理も、lower_boundの内部処理も同じような感じだった。
というわけで、無駄な探索を減らす。

map<int,int> mii;

int dfs(int S){
  map<int,int>::iterator it = mii.lower_bound(S);
  if( it!=mii.end() && it->first==S ){
    return it->second;
  }
  int ret = 0;
  /*
    処理
   */
  mii.insert(it,make_pair(S,ret));
  return ret;
}

insertメソッドでは、挿入位置のヒントが適切なら定数時間で済む、らしい。
以下のコードで簡単に時間計測してみた。

int main(){
  map<int,int> mii;
  map<int,int>::iterator it;
  for(int i=0; i<1000000; i+=2){
    mii[i] = i;
  }
  for(int i=0; i<1000000; i++){
    it = mii.lower_bound(i);
    
    //ver1
    //mii.insert(make_pair(i,i));

    //ver2
    mii.insert(it,make_pair(i,i));
  }
  return 0;
}

最適化なしでコンパイルした実行ファイルで、5回計測した平均値がこれ。
測定環境は、Gateway GT5020jにDDR2の1GB追加したマシンで、OSはカーネル2.6.35.11-83のFedora14

ver1 :: 2.4462
ver2 :: 1.8040

うおお、地味に差が出た。
mapの[]演算子は便利だけど、てきとーに使うと速度に影響が出る。


2011/03/25 13:22 追記
説明不足で誤解を招くような書き方をしてしまいましたが、
要素を追加するためだけに、lower_boundで探索してからinsertするのは無意味です。
上記は 一度探索してる という前提の元で書いた記事です。
勘違いされた方、本当にごめんなさい…。