高速化テクニック
手島 知昭
Tomoaki Teshima
このページでは手島がプログラミングをしていて,「こうした方がプログラムが高速に動作する」と気づいたテクニックを紹介します.
想定している環境はWindows XP(SP2) + (VC++ .NET) + OpenCVです. あまり高度なことは書いていなくて,初心者を想定した内容となっています. アルゴリズム的な改善には触れておらず,実装面でのテクニックです. 画像処理以外のプログラミングに応用できるかどうかは未知です.

経験則やコンピュータの原理的に早くなるテクニックを紹介しているだけなので,間違い,記憶違い,昔は正しかったけれど今は嘘な情報など, 指摘があればTomoaki Teshimaまでメールでご連絡下さい.
目次
  1. Releaseモードを使う
  2. 変数の宣言は関数の始めに
  3. inline宣言を利用する
  4. 最適化を行う
  5. ライブラリを利用する
  6. ファイルへのアクセス回数を減らす
  7. 計算値を再利用する
  8. if文がある場合
  1. Releaseモードを使う
  2.  VC++での基本です.
     ビルド(B)→構成マネージャ(O)→アクティブソリューション構成(A) をDebug→からReleaseに変更します. もしくはツールバーのソリューション構成をReleaseモードに変更することでも可能です.
    Releaseモード
     デフォルトで起動した場合,VC++では「Debugモード」でコンパイルします. このモードでは内部状態をトレースし易い様に,要らぬ情報や機能が追加された状態でコンパイルされます. つまりお荷物が沢山付いているわけですね. Releaseモードにすることで,余分な機能が排除されます.

    (Dec. 23. 2008 追記)
    ぶっちゃけると,このページのこれ以降の話はこのスイッチ1つに集約されていると言っても過言ではありません. 多少がんばったところで,Releaseモードで得られるメリットに比べたら小さいでしょう.

    有名な名言を2つ紹介しておきます.

    We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. (Donald E. Knuth)

    We follow two rules in the matter of optimization:
    Rule 1. Don't do it.
    Rule 2 (for experts only). Don't do it yet - that is, not until you have a perfectly clear and unoptimized solution.
    (M.A. Jackson)

    (追記ここまで)


  3. 変数の宣言は関数の始めに
  4.  変数の宣言は関数の頭に行うようにしましょう.間違ってもループ内で宣言してはいけません.  変数の宣言を行うと,プログラム内では領域の確保と解放が行われます. これはスコープの範囲の最後で行われます.

    良い例:
    void test(){
     int a, i, j;
     for(i = 0;i < 100;i++){
      for(j = 0;j < 100;j++){
       a = i*j;
      }
     }
    }

    悪い例:
    void test(){
     for(int i = 0;i < 100;i++){
      for(int j = 0;j < 100;j++){
       int a = i*j;
      }
     }
    }

     良い例では最初に変数の宣言が行われています. この場合,変数の確保は宣言時に変数の数だけ行われ,関数が終わるときに解放されます.確保3+解放3=命令語6個です.
     一方で,悪い例では,ループ内でint型の変数aが宣言されています. このループを出るたびに宣言されるので,aは1万回確保され,解放されます. 同様に,変数jもiのループに入るたびに確保,解放されます. aで1万回,jで100回,確保と解放が行われるので,計20200個の命令語が実行されます.
     画像処理では,ピクセルの数だけループを回すことはよくあることです. その場合のループは軽く数万〜数十万回処理されます. 宣言は関数の最初にまとめる癖を付けましょう.

     補足
     実際に実験して試して見ました.
     明らかに有意な差が出たものの,Debugモードだけでの結果でした.Releaseモードではあらかじめ計算してしまうのか, 1844京回(ffffff × ffffff × ffffff回)ループを回しても,結果は0msでした.
     ここらへんはReleaseモードでの最適化に含まれているようです.
     I村先生,ご指摘ありがとうございました.

    実行環境

    • Windows XP SP2
    • Intel Core2 Duo U7500 1.06GHz
    • 786MB
    • Visual Studio .NET 2003(Debugモード)

    実行結果[ms]ループを8億回(xffff × x2fff回)

    外側で宣言内側で宣言
    1回目51719172
    2回目52668953
    3回目52658828
    4回目52658860
    5回目52509063
    6回目50938813

    実行結果[ms]ループを5億回(xffff × x1fff回)

    外側で宣言内側で宣言
    1回目34795910
    2回目30005908
    3回目34815725
    4回目34835939
    5回目33465540

    しかも単位がmsだからなぁ.実感することは少ないかも.



  5. inline宣言を利用する
  6.  コード内であちこちから何度も呼び出される関数はインライン展開した方が早くなります.
     インライン展開を利用する場合はinline宣言を関数の宣言の前に付けます.

    例:
    inline double Power(double a, int b){ ... }

     インライン展開をしない場合,この関数を呼ぶ度に利用していた変数を全てスタックに積みなおし, 関数内での処理を行い,結果をレジスタに保存して,スタックの情報を再び戻してくる,と言う一連の作業がプログラム内で行われます.
     一方で,インライン展開を行うと,関数Power内の処理が,呼び出される場所に全て挿入された状態でコンパイルが行われます. 関数を呼ぶ際のオーバヘッドが防げます.
     ただしループ内など,何度も関数が呼ばれるような状況でないと高速化は期待できません.
     また,クラスのメンバ関数をinline宣言する場合はヘッダファイル内で定義しないと,効果が出ません. これはヘッダ→ソースコードの順でコンパイルするC++の仕様で,クラス内でインライン展開する場合はヘッダ内で定義する必要があります.



  7. ファイルへのアクセス回数を減らす
  8.  メモリのアクセスに対して,ハードディスクへのアクセスは時間がかかります. ある一定長のデータを処理するため,ループで連続してデータを読み込むのは致し方ないでしょう.
     しかし,例えば処理A,B,Cそれぞれに対し画像を入力するとする場合,毎回毎回ハードディスクに読み込んでいると時間がかかります. これがハードディスクでなく,ネットワーク越しのドライブならば尚更です.
     アクセスに要する時間はネットワークディスク>ハードディスク>>メモリです. 極力ロードする回数を減らし,メモリ空間上でコピーして使いまわすようにしましょう.



  9. ライブラリを利用する
  10.  画像処理やコンピュータビジョン用のライブラリはOpenCVを始めとして多々存在します. これらのライブラリは,無駄な部分をそぎ落として作られたものなので,同じ処理結果でも計算時間は段違いだったりします. さらには動作が厳密に決まっていて,エラー処理がしっかりしている場合が多いです.
     1から画像処理の関数群を作る場合や,大きな処理のために細かい数多くの画像処理が必要になる場合,ライブラリを利用するのが圧倒的にエラーも少なく,高速に処理が行えます.
    ライブラリ依存になってしまうため開発が打ち切られたら終了,バグが内在しても自分で取り除くのが難しい,基本を理解しないままになってしまうなど,問題点が無い訳ではありませんが,利用して損は無いでしょう. 多種類の画像処理技術を利用する場合に特に有効です.



  11. 最適化を行う
  12. To be written.



  13. 計算値を再利用する
  14.  面倒臭がって,関数の返り値を直接if文に引き渡す場合は注意が必要です. 関数を複数回呼ぶくらいならば,1度求めた返り値を再利用しましょう.

    悪い例:
    //画像中の最大明度値を返す関数
    int nGetMaxValue(IplImage* pImage){
     int max = 0;  for(...){
      //ラスタ走査
     }
     return max;
    }

    void test(IplImage* pImage){
     if(nGetMaxValue(image) > 100){
      int nMaxValue = nGetMaxValue(image);
      ...
      ...
     }
    }

     関数test()内でnGetMaxValue()関数を2回呼んでいるのが分かります. この関数を呼ぶたびにメモリをスタックに積んで,Callして,Returnして,と オーバヘッドが掛かるというのはinline宣言を利用するで説明しましたが, 今回はもっと悪いです.  何故なら,nGetMaxValue()を呼ぶたびにラスタ走査をしている訳ですし,呼んでいる2回の間で明度の最大値は変わりません. ならば,if文の前にnMax = nGetMaxValue()..とでもして,変数での比較を行えば良い訳です. 私はこうやってものぐさな書き方をするので,if文の条件判定式で1回,中で数回同じ関数を呼んで居たりします.

    良い例:
    //画像中の最大明度値を返す関数
    int nGetMaxValue(IplImage* pImage){
     int max = 0;  for(...){
      //ラスタ走査
     }
     return max;
    }

    void test(IplImage* pImage){
     int nMax = nGetMaxValue(image);
     if(nMaxV > 100){
      ...
     }
    }

     素直に関数の返り値を変数に格納してしまいましょう.



  15. if文がある場合
  16. To be written.



  • PHPのループにおいて
  • PHPでif文を作る場合,PHPはインタプリンタ型の言語なのでスクリプトによる表現の揺らぎが実行速度に反映されます.
    if($a){
     function1();
    }else{
     function2();
    }

    と書くより
    $a ? function1() : function2();
    と書いた方が早いらしい.試したことはないけれど.

Last Updated 29, Mar, 2011.
Since 11, Mar, 2007.