Обсуждение статьи "Передовые методы управления и оптимизации памяти в MQL5"

 

Опубликована статья Передовые методы управления и оптимизации памяти в MQL5:

Откройте для себя практические методы оптимизации использования памяти в торговых системах MQL5. Научитесь создавать эффективные, стабильные и быстродействующие советники и индикаторы. Рассмотрим, как в действительности работает память в MQL5, распространенные ловушки, которые замедляют ваши системы или приводят их к сбоям, и — самое важное! — как их исправить.

Несомненно, MQL5 — мощный язык, но эта мощь поставляется в комплекте с ответственностью, особенно когда речь идет о памяти. Многие разработчики сосредотачиваются исключительно на логике стратегии, точках входа и управлении рисками, в то время как обработка памяти на заднем плане незаметно превращается в бомбу замедленного действия. По мере увеличения масштабности вашего кода — обработки большего количества символов и более объемных наборов данных — игнорирование памяти может привести к узким местам в производительности, к нестабильности и упущенным возможностям.

В этой статье мы рассмотрим, как все это работает с точки зрения внутренней структуры. Рассмотрим, как в действительности работает память в MQL5, распространенные ловушки, которые замедляют ваши системы или приводят их к сбоям, и — самое важное! — как их исправить. Вы изучите практические методы оптимизации, которые сделают ваши торговые программы быстрее, эффективнее и надежнее.

Вот где эффективное использование памяти действительно имеет значение:

  • Высокочастотная торговля: каждая миллисекунда — это потенциальное преимущество или потенциальная потеря.

  • Анализ на нескольких таймфреймах: комбинирование графиков? Ожидайте, что нагрузка на память увеличится в разы.

  • Тяжелая логика индикаторов: сложные математические расчеты и большие наборы данных могут все застопорить, если ими не управлять.

  • Тестирование на исторических данных больших объемов: без продуманной оптимизации, тесты на истории могут напоминать наблюдение за высыханием краски.

Если вы готовы всерьез заняться производительностью, давайте углубимся в это — и сделаем ваши MQL5-системы настолько же эффективными, насколько они интеллектуальны.



Автор: Sahil Bagdi

 

Статья выглядит очень спорной (только пара моментов).

Что за класс вы здесь упомянули?

// Переменная-член класса - 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);
   }
   
   // Обработайте данные...
}

Также, если заменить массив структур (AoS) на структуру массивов (SoA) для OHLCV - доступ к ценам одного и того же бара требует больше ссылок (переключение между массивами вместо увеличения смещения внутри одной структуры) и замедляет процесс, но такие операции встречаются очень часто.

Для данного примера с OHLCV, чтобы повысить эффективность использования памяти и времени, было бы, вероятно, интереснее упаковать все значения в один двумерный или даже одномерный массив:

double TOHLCV[][6];

Это возможно, поскольку все значения типов (double, datetime, long) имеют одинаковый размер 8 байт и могут быть приведены друг к другу напрямую.

 
Stanislav Korotky #:
Для данного примера с OHLCV, чтобы сделать его более подходящим для экономии памяти и времени, вероятно, было бы интереснее упаковать все значения в один двумерный или даже одномерный массив:

2D массив вместо массива структур может немного сэкономить процессорное время, но значительно увеличит время разработчика на разработку и поддержку кода. По моему личному мнению, я согласен с остальными вашими утверждениями.

 

https://www.mql5.com/ru/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);
   }
   
   // Обработайте данные...
}

Станислав Короткий #:

Статья выглядит очень спорной (всего пара моментов).

Что за класс вы здесь упомянули?

Из наличия обработчика OnTick и способа доступа к массиву следует, что вы добавили массив цен в глобальную область видимости, что является плохой идеей (из-за загрязнения пространства имен, если массив нужен только в области видимости обработчика). Вероятно, было бы правильнее сохранить исходный код из того же примера, но сделать массив статическим, чтобы все ясно видели разницу:

Насколько я понимаю, тот пример (я его цитировал выше) - это, грубо говоря, псевдокод, то есть автор не обращает внимания на следующее (чтобы сосредоточиться на том, о чем именно он говорит, я полагаю):

  • Судя по условию цикла, размер массива известен во время компиляции, но, тем не менее, массив динамический.
  • Несмотря на то, что массив динамический, в коде, демонстрирующем эффективный подход, ArrayResize не вызывался.
  • С точки зрения эффективности, я подозреваю, что было бы лучше заменить весь следующий цикл одним вызовом CopySeries:

   // Повторное использование существующего массива
   for(int i = 0; i < 1000; i++)
   {
      prices[i] = iClose(_Symbol, PERIOD_M1, i);
   }
 
Vladislav Boyko #:
С точки зрения эффективности, я подозреваю, что было бы лучше заменить весь следующий цикл одним вызовом CopySeries:

Поправьте меня, если я ошибаюсь, но, насколько я помню, каждый вызов iClose содержит вызов CopySeries под капотом.

 

Эта статья содержит глубокий и заставляющий задуматься материал для обсуждения.

Техническое изложение ясное и хорошо объясненное, что позволяет читателю легко следить за ним и оставаться вовлеченным.

Большое спасибо.

 

В подобных статьях нужны мотивирующие сравнительные тесты, реально показывающие эффективность предлагаемых подходов.

Перевод кривоват, без разбора кода не просто воспринимается.