【検証#07】MQL⅘コードの高速化テクニック¶
この記事の3行まとめ
- ⏱️
GetTickCountでコードの実行時間を計測できる - 🚀
static変数で計算結果をキャッシュして高速化 - ⚡ 関数呼び出しを減らし、
OnInitに移動できる処理は移動
はじめに¶
この記事は、「とあるMetaTraderの備忘秘録」様のブログ記事を検証・紹介するシリーズの第7弾です。
2010年に公開された「MQL4の高速化」連載は、現代のMQL5開発でも有効な知見が満載です。
元ネタ
MQL4の高速化 連載記事
- MQL4の高速化(1) 速度測定(2010-02-14)
- MQL4の高速化(2) バックテスト時間(2010-02-15)
- MQL4の高速化(3) 1秒でも速く(2010-02-17)
時代背景に関する注意
この記事の元となるブログ記事は2010年頃に公開されたものです。
MQL5のコンパイラ最適化技術の向上により、当時の高速化テクニックの一部は効果が薄れている可能性があります。
なぜ高速化が重要なのか¶
| シナリオ | 高速化の効果 |
|---|---|
| バックテスト | テスト時間短縮 → 多くの条件を検証可能 |
| 最適化 | パラメータ探索範囲を広げられる |
| リアルタイム | ティック処理の遅延を減らす |
| MTF計算 | 複数時間足の計算負荷を軽減 |
特にMT5の最適化機能では、1回のバックテストが0.1秒速くなるだけで、1000回の最適化で100秒(約2分)の節約になります。
ベンチマーク手法¶
コードの速度を計測するには GetTickCount を使用します。
基本的な計測方法¶
void OnStart()
{
int iterations = 1000000; // 100万回
// 計測開始
uint start = GetTickCount();
// 計測対象のコード
for(int i = 0; i < iterations; i++)
{
// テストしたい処理
double result = MathSqrt(12345.6789);
}
// 計測終了
uint elapsed = GetTickCount() - start;
PrintFormat("処理時間: %d ms (%d 回)", elapsed, iterations);
}
より精密な計測¶
MQL5では GetMicrosecondCount も使用可能です:
ulong start = GetMicrosecondCount();
// 処理
ulong elapsed = GetMicrosecondCount() - start;
PrintFormat("処理時間: %d μs", elapsed);
高速化テクニック集¶
1. static変数で計算結果をキャッシュ¶
Before(遅い):
void OnTick()
{
// 毎ティックで同じ計算を繰り返している
double pipValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
// 使用...
}
After(速い):
void OnTick()
{
// static変数にキャッシュ
static double pipValue = 0;
static double lotStep = 0;
static bool initialized = false;
if(!initialized)
{
pipValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
initialized = true;
}
// 使用...
}
さらに良い方法
変化しない値は OnInit で取得し、グローバル変数に格納するのがベスト。
2. OnInitに移動できる処理を移動¶
Before:
int indicatorHandle;
void OnTick()
{
// 毎ティックでハンドル取得(非効率)
indicatorHandle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);
double buffer[];
CopyBuffer(indicatorHandle, 0, 0, 1, buffer);
// 使用...
IndicatorRelease(indicatorHandle); // 毎回解放
}
After:
int indicatorHandle;
int OnInit()
{
// 初期化時に一度だけ取得
indicatorHandle = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_SMA, PRICE_CLOSE);
return INIT_SUCCEEDED;
}
void OnTick()
{
double buffer[];
CopyBuffer(indicatorHandle, 0, 0, 1, buffer);
// 使用...
}
void OnDeinit(const int reason)
{
// 終了時に解放
IndicatorRelease(indicatorHandle);
}
3. 文字列連結を避ける¶
Before(遅い):
string message = "";
for(int i = 0; i < 100; i++)
{
message = message + "Item " + IntegerToString(i) + ", ";
}
Print(message);
After(速い):
string message = "";
for(int i = 0; i < 100; i++)
{
message += StringFormat("Item %d, ", i);
}
Print(message);
StringConcatenate
MQL4では StringConcatenate 関数を使うとさらに高速でした。
MQL5では StringFormat や += 演算子が最適化されています。
4. 短絡評価を活用¶
Before:
After:
// 左の条件がfalseなら右は評価されない(短絡評価)
if(CheckComplexCondition() && IsTradeAllowed()) // 重い処理を後に
{
Trade();
}
順序に注意
軽い条件を先に、重い条件を後にすることで、
軽い条件で弾かれた場合に重い計算をスキップできます。
5. 配列の事前確保¶
Before:
double buffer[];
for(int i = 0; i < 1000; i++)
{
ArrayResize(buffer, ArraySize(buffer) + 1); // 毎回リサイズ(遅い)
buffer[i] = i * 1.5;
}
After:
double buffer[];
ArrayResize(buffer, 1000); // 最初に確保
for(int i = 0; i < 1000; i++)
{
buffer[i] = i * 1.5;
}
ベンチマーク用スクリプト¶
以下は各テクニックの効果を計測するスクリプトです:
//+------------------------------------------------------------------+
//| Benchmark_Sample.mq5 |
//| 「とあるMetaTraderの備忘秘録」様の記事を検証 |
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property link "https://fx-omoshiro-lab.com/"
#property version "1.00"
void OnStart()
{
int iterations = 100000;
Print("=== 高速化テクニック ベンチマーク ===");
Print("反復回数: ", iterations);
Print("");
// テスト1: SymbolInfoDouble キャッシュなし vs あり
uint start1 = GetTickCount();
for(int i = 0; i < iterations; i++)
{
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
}
uint time1 = GetTickCount() - start1;
static double cachedTickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
uint start2 = GetTickCount();
for(int i = 0; i < iterations; i++)
{
double tickSize = cachedTickSize;
}
uint time2 = GetTickCount() - start2;
PrintFormat("SymbolInfoDouble 毎回呼び出し: %d ms", time1);
PrintFormat("キャッシュ使用 : %d ms", time2);
PrintFormat("高速化率: %.1f 倍", (double)time1 / MathMax(time2, 1));
Print("");
// テスト2: 文字列連結
uint start3 = GetTickCount();
string s1 = "";
for(int i = 0; i < 1000; i++)
{
s1 = s1 + "test";
}
uint time3 = GetTickCount() - start3;
uint start4 = GetTickCount();
string s2 = "";
for(int i = 0; i < 1000; i++)
{
s2 += "test";
}
uint time4 = GetTickCount() - start4;
PrintFormat("文字列連結 (+ 演算子) : %d ms", time3);
PrintFormat("文字列連結 (+= 演算子) : %d ms", time4);
Print("");
Print("=== ベンチマーク完了 ===");
}
まとめ¶
| テクニック | 効果 | 適用場面 |
|---|---|---|
| static変数キャッシュ | 高 | 変化しない値の再計算防止 |
| OnInit移動 | 高 | ハンドル取得、初期設定 |
| 文字列連結最適化 | 中 | ログ出力、文字列処理 |
| 短絡評価 | 中 | 複合条件 |
| 配列事前確保 | 中 | 動的配列の使用時 |
オリジナル記事への謝辞
この記事は「とあるMetaTraderの備忘秘録」様の貴重な連載記事をもとに、
MQL5での検証と解説を加えたものです。
オリジナル記事に心より感謝いたします。
ソースコードのダウンロード¶
この記事で紹介した高速化テクニックの効果を 実際に数値で確認できる ベンチマークスクリプトを用意しました。
ファイルの種類:スクリプト
保存先: MQL5/Scripts/ フォルダ
使い方: MT5のナビゲーターから「スクリプト」を展開し、チャートにドラッグ&ドロップで実行
ベンチマーク結果の見方¶
スクリプトを実行すると、エキスパートログに以下のような結果が出力されます。
=== 高速化テクニック ベンチマーク ===
反復回数: 100000
SymbolInfoDouble 毎回呼び出し: 94 ms
キャッシュ使用 : 0 ms
高速化率: 94.0 倍
文字列連結 (+ 演算子) : 0 ms
文字列連結 (StringAdd) : 0 ms
高速化率: 0.0 倍
=== テスト完了 ===
結果の解釈¶
| テスト項目 | 意味 |
|---|---|
| SymbolInfoDouble比較 | SymbolInfoDouble を毎回呼び出す vs 一度取得してキャッシュする比較。94倍の高速化は、キャッシュの威力を示しています。 |
| 文字列連結比較 | + 演算子 vs StringAdd の比較。現代のMQL5では最適化が進んでおり、10000回程度では差が出にくくなっています。 |
ベンチマークのポイント
- SymbolInfoDouble などの関数呼び出しコストは依然として大きい
- 現代のMQL5コンパイラは文字列処理を最適化しているため、昔ほどの差は出ない
- 重要なのは「キャッシュの考え方」— 変化しない値は何度も取得しない
関連用語¶
この記事で登場した用語は用語集でも解説しています。