記事「MQL5における高度なメモリ管理と最適化テクニック」についてのディスカッション

 

新しい記事「MQL5における高度なメモリ管理と最適化テクニック」はパブリッシュされました:

MQL5の取引システムにおけるメモリ使用を最適化するための実践的なテクニックを紹介します。効率的で安定性が高く、高速に動作するエキスパートアドバイザー(EA)やインジケーターの構築方法を学びましょう。MQL5でのメモリの仕組み、システムを遅くしたり不安定にしたりする一般的な落とし穴、そして、最も重要なこととして、それらを解決する方法について詳しく解説します。

MQL5は間違いなく強力な開発環境ですが、その力には責任が伴います。特にメモリ管理においてはなおさらです。多くの開発者が戦略ロジックやエントリーポイント、リスク管理に集中する一方で、メモリの取り扱いは見落とされがちです。しかし、コードの規模が大きくなり、複数の銘柄、高頻度データ、重たいデータセットを扱うようになると、メモリの問題はパフォーマンスの低下やシステムの不安定化、そして機会損失へとつながっていきます。

この記事では、その内部に踏み込みます。MQL5でのメモリの仕組み、システムを遅くしたり不安定にしたりする一般的な落とし穴、そして、最も重要なこととして、それらを解決する方法について詳しく解説します。取引プログラムをより高速に、無駄なく、安定して動作させるための実践的な最適化技術を学んでいきましょう。

以下のような場面では、メモリ効率が特に重要になります。

  • 高頻度取引:1ミリ秒ごとの差が優位性、あるいは損失につながります。

  • 多時間枠分析:複数のチャートを組み合わせると、メモリへの負荷が一気に増加します。

  • 重いインジケーターロジック:複雑な演算や大規模データは、適切に管理されなければ、処理全体の停滞を引き起こす可能性があります。

  • 長期履歴のバックテスト:適切な最適化なしでは、バックテストが非常に遅くなることもあります。

本気でパフォーマンス向上を目指すなら、今こそ取り組むべき時です。MQL5で構築するシステムを、インテリジェントであるだけでなく、効率的なものにしていきましょう。



作者: Sahil Bagdi

 

この記事は非常に議論の余地がありそうだ(ほんの2、3点)。

ここで言及されているクラスは何ですか?

// クラスのメンバ変数 - created once
double prices[];

void OnTick()
{
   // 既存の配列を再利用する
   for(int i = 0; i < 1000; i++)
   {
      prices[i] = iClose(_Symbol, PERIOD_M1, i);
   }
   
   // データを処理する
}

OnTickハンドラの存在と配列へのアクセス方法から、価格配列をグローバル・スコープに追加したことが推測されますが、これは悪い考えです(配列がハンドラのスコープでのみ必要な場合、名前空間汚染のため)。おそらく、同じ例の最初のコードはそのままにして、配列を静的にした方が適切でしょう:

// 効率的なアプローチ(配列を一度確保し、必要に応じてサイズを調整できる)
void OnTick()
{
   // これは、次の よう ものではありません。 create (nor allocate) array on every tick
   static double prices[];
   ArrayResize(prices, 1000);
   
   // 配列に価格データを入れる
   for(int i = 0; i < 1000; i++)
   {
      prices[i] = iClose(_Symbol, PERIOD_M1, i);
   }
   
   // データを処理する
}

また、OHLCVのためにArray of Structures (AoS)をStructure of Arrays (SoA)に置き換えると、同じバーの価格へのアクセスはより多くの参照を必要とし(単一の構造体内のオフセットをインクリメントする代わりに配列を切り替える)、処理を遅くしますが、そのような操作は非常に一般的です。

OHLCVを使ったこの例では、メモリ効率と時間効率をより適切にするために、すべての値を単一の2次元配列、あるいは1次元配列にまとめた方がおそらく面白いでしょう:

double TOHLCV[][6];

これは、すべての型(double、datetime、long)の値が同じサイズ8バイトであり、互いに直接キャストできるため可能である。

 
Stanislav Korotky #:
OHLCVを使ったこの例では、メモリ効率と時間効率をより適切にするために、すべての値を1つの2次元配列、あるいは1次元配列にまとめた方がおそらく面白いだろう:

構造体の配列の代わりに2次元配列にすることで、プロセッサの処理時間は若干短縮されるかもしれないが、開発者がコードの開発と保守に費やす時間は大幅に増えるだろう。個人的な意見ですが、私はあなたの他の意見に賛成です。

 

https://www.mql5.com/ja/articles/17693#sec2

問題のある例を見てみよう:

// 非効率的なアプローチ - 毎ティックで新しい配列を作成する
void OnTick()
{
   // これはティックごとに新しい配列を作成する
   double prices[];
   ArrayResize(prices, 1000);
   
   // 配列に価格データを入れる
   for(int i = 0; i < 1000; i++)
   {
      prices[i] = iClose(_Symbol, PERIOD_M1, i);
   }
   
   // データを処理する
   
   // 配列はいずれガベージコレクションされるが、これは
   // 不要なメモリ・チャーンを発生させる
}

より効率的なアプローチとしては

// クラス・メンバー変数 - 一度だけ作成される
double prices[];

void OnTick()
{
   // 既存の配列を再利用する
   for(int i = 0; i < 1000; i++)
   {
      prices[i] = iClose(_Symbol, PERIOD_M1, i);
   }
   
   // データを処理する
}

Stanislav Korotky#:

この記事は非常に議論の余地がありそうだ(ほんの2、3点)。

あなたがここで言及したクラスとは何ですか?

OnTickハンドラの存在と配列へのアクセス方法から、あなたが価格配列をグローバルスコープに追加したことが推測されます。おそらく、同じ例の最初のコードはそのままにして、配列を静的にした方が適切でしょう:

私が理解する限り、その例(上で引用したもの)は大雑把に言って擬似コードです。 つまり、作者は以下のことに注意を払っていません(正確に何を話しているのかに集中するためでしょう):

  • ループ条件から判断すると、配列のサイズはコンパイル時にわかっているが、それにもかかわらず、配列は動的である。
  • 配列が動的であるにもかかわらず、効率的なアプローチを示すコードではArrayResizeが呼び出されていない。
  • 効率という点では、以下のループ全体をCopySeries 呼び出し1回に置き換えた方が良いのではないだろうか:

   // 既存の配列を再利用する
   for(int i = 0; i < 1000; i++)
   {
      prices[i] = iClose(_Symbol, PERIOD_M1, i);
   }
 
Vladislav Boyko #:
効率という点では、次のループ全体を1つのCopySeries 呼び出しに置き換えた方が良いのではないでしょうか:

間違っていたら訂正してほしいが、私の記憶では、iCloseコールはすべてCopySeriesコールを含んでいる。

 

この提供された記事には、ディスカッションのための洞察に富み、示唆に富む内容が含まれている。

技術的な表現も明快で、よく説明されているため、読者は理解しやすく、飽きずに読み進めることができる。

ありがとうございました。