文章 "MQL5中的高级内存管理与优化技术"

 

新文章 MQL5中的高级内存管理与优化技术已发布:

探索在MQL5交易系统中优化内存使用的实用技巧。学习构建高效、稳定且运行速度快的智能交易系统(EA)和指标。我们将深入探究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);
   }
   
   // 处理数据...
}

另外,如果用数组结构(SoA)代替结构数组(AoS)来处理 OHLCV,那么访问同一栏的价格就需要更多的引用(在数组之间切换,而不是在单个结构内部递增偏移量),从而会减慢处理速度,但这种操作非常常见。

在这个使用 OHLCV 的示例中,为了提高内存和时间效率,将所有值打包到一个二维甚至一维数组中可能更有意思:

double TOHLCV[][6];

之所以可以这样做,是因为所有类型(double、datetime、long)的值都有相同的 8 字节大小,可以直接相互转换。

 
Stanislav Korotky #:
在这个使用 OHLCV 的示例中,为了提高内存效率和时间效率,将所有值打包到一个二维甚至一维数组中可能会更有趣:

用二维数组代替结构数组可能会稍微节省处理器时间,但会大大增加开发人员开发和维护代码的时间。就我个人而言,我同意您的其他观点。

 

https://www.mql5.com/zh/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#:

这篇文章看起来很有争议(只有几点)。

您在这里提到的类是什么?

从 OnTick 处理程序的存在和数组的访问方式来看,这意味着你将价格数组添加到了全局作用域中,这不是个好主意(因为如果数组只需要在处理程序的作用域中使用,那么就会造成命名空间污染)。也许更合适的做法是保留同一示例中的初始代码,但将数组设为静态,这样大家就能清楚地看到区别了:

据我所知,那个示例(我在上面引用过)大致上是伪代码,也就是说,作者并没有注意下面的内容(我猜是为了专注于他到底在说什么):

  • 从循环条件来看,数组的大小在编译时是已知的,但数组是动态的。
  • 尽管数组是动态的,但在演示高效方法的代码中并没有调用 ArrayResize。
  • 就效率而言,我认为用一次CopySeries 调用来替换下面的整个循环会更好:

   // 重复使用现有数组
   for(int i = 0; i < 1000; i++)
   {
      prices[i] = iClose(_Symbol, PERIOD_M1, i);
   }
 
Vladislav Boyko #:
就效率而言,我认为用一个CopySeries 调用来取代整个下面的循环会更好:

如果我说错了,请指正我,但在我的记忆中,每次 iClose 调用都包含一个 CopySeries 调用。

 

所提供的这篇文章包含深刻而发人深省的讨论内容。

技术表述清晰明了,使读者易于理解和参与。

非常感谢。

 

此类文章需要有激励性的比较测试,以真正显示所建议方法的有效性。

翻译歪曲,不解析代码 就不容易理解。