2016年6月10日金曜日

[MQL超入門] その025 プロファイリングを使った実行速度調査&クラス呼び出し速度改善

すこし超入門からは外れるかもしれませんが、速度改善のお話しです。

インジケータを作っていて、あー遅いなー?と思うことがあります。
どこで遅くなっているのか調査する場合、ソースコードを眺めて追いかける良いツールがメタエディタにはついています。

プロファイリング機能です。

これは、プログラムを実行した際、関数毎に時間を計測してくれる機能です。これを使うと、どこで遅くなっているのかが見た目で編別可能になります。

早速使ってみましょう。
遅いなーと思うプログラムをメタエディタで開きます。
ツールバーのプロファイルスタートボタンを押します。下画像の赤丸の箇所です。

■メタエディタ プロファイル開始ボタン

計測が開始されると、赤い■ボタンに変わりますのでチャートにインジケータが表示された後に、この赤■ボタンを押します。

■メタエディタ プロファイル終了ボタン

■メタエディタ プロファイル終了したあとの表示

計測が終了すると、メタエディタ上に実行にかかった時間が表示されます。ソースコード右端にもバーで表示されます。
このバーが大きいほど時間がかかっている個所になりますので、改善方法がないかどうかを調査していく形になります。

MQLにおける速度改善の基本は

・ループの中で余計な処理をしない。
・何度もループしない。

の2点にほぼ集約されます。特にループ外へ処理を追い出す修正は効果が高いため、具体例を挙げたいと思います。

■ダメな例
for(int i = 0; i < rates_total; i++)
{
    double currentClose = iClose(NULL,0, 0);
    if( currentClose > iClose(NULL, 0, i) )
    {
        //何か処理
    }
}

上記のプログラムでは、iClose(NULL, 0, 0)の結果はループ変数のiに全く影響をうけません。このような処理はループの外に追い出す必要があります。

■修正例
double currentClose = iClose(NULL,0, 0);
for(int i = 0; i < rates_total; i++)
{
    if( currentClose > iClose(NULL, 0, i) )
    {
        //何か処理
    }
}

この修正で、処理時間が約1/2になります。

修正前処理時間 = iClose処理時間 * 2 * rates_totals

修正後処理時間 = iClose処理時間 + iClose処理時間 * rates_totals

実際プロファイリング機能で見てみましょう。

■ループ内の処理をループ外に追い出した場合の速度差

InnnerCallに対してBeforeCallは処理時間を示すバーの幅が半分になっていることが見て取れるかと思います。
このようにループの中の処理見直しは改善効果が高いため、もし表示が遅いなーということがあれば、ループ内処理を中心に見直すことになります。


プロファイリングといえば、以前MQLのクラスメソッドの呼び出しが異常に遅い!と文句たらたらだったことがあります。
[MT4プログラミング]クラスメソッドの呼び出しにご用心

そろそろ治ってないかなーとおもい、プロファイリングの記事を書くついでに、MT4 Build971にて再計測してみました。使用したプログラムは次の通りです。

//------------------------------------------------------------------
// 速度テスト
#property copyright "Copyright 2016,  Daisuke"
#property link      "http://mt4program.blogspot.jp/"
#property version   "1.00"
#property strict
#property indicator_chart_window

#define MAXCOUNT 10000

class COpen
{
public:
   void IndirectionOpen()
   {
         iOpen(NULL, 0, 1);
   }
};

COpen m_open;

COpen *m_pOpen;


//------------------------------------------------------------------
// 初期化
int OnInit()
{
   m_pOpen = GetPointer(m_open);
   DirectCall();
   IndirectionCall();
   ClassCall();
   ClassPointerCall();

   return(INIT_SUCCEEDED);
}

//------------------------------------------------------------------
// 直接呼出し
void DirectCall()
{
   for(int i = 0; i < MAXCOUNT; i++)
   {
      iOpen(NULL, 0, 1);
   }
}

//------------------------------------------------------------------
// 間接呼出し
void IndirectionCall()
{
   for(int i = 0; i < MAXCOUNT; i++)
   {
      IndirectionOpen();
   }
}

void IndirectionOpen()
{
      iOpen(NULL, 0, 1);
}

//------------------------------------------------------------------
// クラス呼び出し
void ClassCall()
{
   for(int i = 0; i < MAXCOUNT; i++)
   {
      m_open.IndirectionOpen();
   }
}

//------------------------------------------------------------------
// クラス呼び出し
void ClassPointerCall()
{
   for(int i = 0; i < MAXCOUNT; i++)
   {
      m_pOpen.IndirectionOpen();
   }
}


//------------------------------------------------------------------
// 計算処理
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+


OnInitの中で、次の4つの関数を呼び出して速度比較してみます。
・直接iOpenをコール(DirectCall)
・一回関数をはさむ(IndirectionCall)
・クラスの直接参照(ClassCall)
・クラスのポインタ参照(ClassPointerCall)

結果は次の形になりました。
MQL プロファイラレポート - SpeedTest.mq4
関数 カウント 時間 パーセント グラフ
OnInit 27 1 2 422 99.71%
COpen::IndirectionOpen 20000 730 30.05%
IndirectionCall 31 1 686 28.24%
ClassCall 32 1 686 28.24%
ClassPointerCall 33 1 686 28.24%
iOpen 40000 419 17.25%
IndirectionOpen 54 10000 365 15.03%
DirectCall 30 1 363 14.94%
@global_initializations 1 6 0.25%
OnCalculate 86 26 1 0.04%
COpen::~COpen 1 0 0.00%
COpen::COpen 20 1 0 0.00%
@global_deinitializations 1 0 0.00%
GetPointer 29 1 0 0.00%
Copyright 2001-2013, MetaQuotes Software Corp.



直接参照が一番早いのは当然として、関数呼び出し~クラス呼び出しについては差がなくなっています。以前はクラス呼び出しが異常なほど(それこそ10倍ぐらい)遅かったのですが改善したようです。

これでもっと積極的にクラスが利用できます。

「MT4でFXを勝ち抜く研究をするブログ」で公開している無料インジケータは、こちらの一覧から。
インジケータ一覧

Twitterもよろしくお願いします。
https://twitter.com/mt4program

ブログランキングにご協力よろしくお願いします。m(._.)m
にほんブログ村 為替ブログ MetaTraderへ
にほんブログ村

お約束ですが、本ブログは、投資に対する利益を約束する物ではありません。最終的には自己責任によるご判断よろしくお願いいたします。