Особенности языка mql5, тонкости и приёмы работы - страница 275

 

Сравнение 3 имеющихся в моем распоряжении режимов компиляции (у меня нет AVX512).

Compiler Version: 4702 AVX2 + FMA3, optimization - true
Intel Core i7-9750H  @ 2.60GHz, AVX2 + FMA3
Without hours (dt.hour+ dt.min+ dt.sec - off), random datetimes[].
1970.01.01 00:01:44 - 2097.11.29 23:59:41
 6.29 ns, checksum = 1269354861838493   // TimeToStruct2100
 5.90 ns, checksum = 1269354861838493   // TimeToStructFast
 6.49 ns, checksum = 1269354861838493   // TimeToStructFast_fxsaber
 3.60 ns, checksum = 1269354861838493   // TimeToCalendar
 6.47 ns, checksum = 1269354861838493   // TimeToJulian
31.65 ns, checksum = 1269354861838493  /// MQL's TimeToStruct()

Compiler Version: 4702 AVX, optimization - true
Intel Core i7-9750H  @ 2.60GHz, AVX2 + FMA3
Without hours (dt.hour+ dt.min+ dt.sec - off), random datetimes[].
1970.01.01 00:00:47 - 2097.11.29 23:59:41
 6.30 ns, checksum = 1286714313014774   // TimeToStruct2100
 5.92 ns, checksum = 1286714313014774   // TimeToStructFast
 6.40 ns, checksum = 1286714313014774   // TimeToStructFast_fxsaber
 6.46 ns, checksum = 1286714313014774   // TimeToCalendar
 6.11 ns, checksum = 1286714313014774   // TimeToJulian
31.89 ns, checksum = 1286714313014774  /// MQL's TimeToStruct()

Compiler Version: 4702 X64 Regular, optimization - true
Intel Core i7-9750H  @ 2.60GHz, AVX2 + FMA3
Without hours (dt.hour+ dt.min+ dt.sec - off), random datetimes[].
1970.01.01 00:00:09 - 2097.11.29 23:58:42
 6.13 ns, checksum = 1257992266603185   // TimeToStruct2100
 5.69 ns, checksum = 1257992266603185   // TimeToStructFast
 6.37 ns, checksum = 1257992266603185   // TimeToStructFast_fxsaber
 6.27 ns, checksum = 1257992266603185   // TimeToCalendar
13.06 ns, checksum = 1257992266603185   // TimeToJulian
31.63 ns, checksum = 1257992266603185  /// MQL's TimeToStruct()

 
2024.11.27 21:44:37.971 TimeToStruct_benchmarks (BTCUSDT,M1)    Compiler Version: 4695 X64 Regular, optimization - true
2024.11.27 21:44:37.971 TimeToStruct_benchmarks (BTCUSDT,M1)    AMD Ryzen 3 PRO 3200GE w/ Radeon Vega, AVX2 + FMA3
2024.11.27 21:44:37.971 TimeToStruct_benchmarks (BTCUSDT,M1)    Without hours (dt.hour+ dt.min+ dt.sec - off), random datetimes[].
2024.11.27 21:44:39.630 TimeToStruct_benchmarks (BTCUSDT,M1)    1970.01.01 00:00:03 - 2097.11.29 23:59:41
2024.11.27 21:44:40.659 TimeToStruct_benchmarks (BTCUSDT,M1)     6.78 ns, checksum = 1282374964710342   // TimeToStruct2100
2024.11.27 21:44:41.651 TimeToStruct_benchmarks (BTCUSDT,M1)     6.53 ns, checksum = 1282374964710342   // TimeToStructFast
2024.11.27 21:44:42.705 TimeToStruct_benchmarks (BTCUSDT,M1)     6.94 ns, checksum = 1282374964710342   // TimeToStructFast_fxsaber
2024.11.27 21:44:43.747 TimeToStruct_benchmarks (BTCUSDT,M1)     6.86 ns, checksum = 1282374964710342   // TimeToCalendar
2024.11.27 21:44:45.990 TimeToStruct_benchmarks (BTCUSDT,M1)    14.77 ns, checksum = 1282374964710342   // TimeToJulian
2024.11.27 21:44:51.837 TimeToStruct_benchmarks (BTCUSDT,M1)    38.51 ns, checksum = 1282374964710342  /// MQL's TimeToStruct()
2024.11.27 21:46:10.525 TimeToStruct_benchmarks (BTCUSDT,M1)    
2024.11.27 21:46:10.525 TimeToStruct_benchmarks (BTCUSDT,M1)    Compiler Version: 4695 AVX, optimization - true
2024.11.27 21:46:10.525 TimeToStruct_benchmarks (BTCUSDT,M1)    AMD Ryzen 3 PRO 3200GE w/ Radeon Vega, AVX2 + FMA3
2024.11.27 21:46:10.525 TimeToStruct_benchmarks (BTCUSDT,M1)    Without hours (dt.hour+ dt.min+ dt.sec - off), random datetimes[].
2024.11.27 21:46:12.188 TimeToStruct_benchmarks (BTCUSDT,M1)    1970.01.01 00:00:47 - 2097.11.29 23:59:41
2024.11.27 21:46:13.287 TimeToStruct_benchmarks (BTCUSDT,M1)     7.24 ns, checksum = 1290059549437091   // TimeToStruct2100
2024.11.27 21:46:14.328 TimeToStruct_benchmarks (BTCUSDT,M1)     6.85 ns, checksum = 1290059549437091   // TimeToStructFast
2024.11.27 21:46:15.437 TimeToStruct_benchmarks (BTCUSDT,M1)     7.30 ns, checksum = 1290059549437091   // TimeToStructFast_fxsaber
2024.11.27 21:46:16.599 TimeToStruct_benchmarks (BTCUSDT,M1)     7.66 ns, checksum = 1290059549437091   // TimeToCalendar
2024.11.27 21:46:17.405 TimeToStruct_benchmarks (BTCUSDT,M1)     5.31 ns, checksum = 1290059549437091   // TimeToJulian
2024.11.27 21:46:23.284 TimeToStruct_benchmarks (BTCUSDT,M1)    38.72 ns, checksum = 1290059549437091  /// MQL's TimeToStruct()
2024.11.27 21:46:56.638 TimeToStruct_benchmarks (BTCUSDT,M1)    
2024.11.27 21:46:56.638 TimeToStruct_benchmarks (BTCUSDT,M1)    Compiler Version: 4695 AVX2 + FMA3, optimization - true
2024.11.27 21:46:56.638 TimeToStruct_benchmarks (BTCUSDT,M1)    AMD Ryzen 3 PRO 3200GE w/ Radeon Vega, AVX2 + FMA3
2024.11.27 21:46:56.638 TimeToStruct_benchmarks (BTCUSDT,M1)    Without hours (dt.hour+ dt.min+ dt.sec - off), random datetimes[].
2024.11.27 21:46:58.296 TimeToStruct_benchmarks (BTCUSDT,M1)    1970.01.01 00:00:03 - 2097.11.29 23:59:19
2024.11.27 21:46:59.388 TimeToStruct_benchmarks (BTCUSDT,M1)     7.19 ns, checksum = 1242845810931935   // TimeToStruct2100
2024.11.27 21:47:00.434 TimeToStruct_benchmarks (BTCUSDT,M1)     6.89 ns, checksum = 1242845810931935   // TimeToStructFast
2024.11.27 21:47:01.518 TimeToStruct_benchmarks (BTCUSDT,M1)     7.14 ns, checksum = 1242845810931935   // TimeToStructFast_fxsaber
2024.11.27 21:47:02.044 TimeToStruct_benchmarks (BTCUSDT,M1)     3.47 ns, checksum = 1242845810931935   // TimeToCalendar
2024.11.27 21:47:02.899 TimeToStruct_benchmarks (BTCUSDT,M1)     5.63 ns, checksum = 1242845810931935   // TimeToJulian
2024.11.27 21:47:08.741 TimeToStruct_benchmarks (BTCUSDT,M1)    38.47 ns, checksum = 1242845810931935  /// MQL's TimeToStruct()
Если интересно... Извиняюсь, не стал чистить.
 
Alain Verleyen #:

Сравнение 3 имеющихся в моем распоряжении режимов компиляции (у меня нет AVX512).

Edgar Akhmadeev #:
Если интересно... Извиняюсь, не стал чистить.

Хорошо видно, что сильная зависимость от настроек компилятора. AVX2+FMA3 существенным образом влияет на TimeToCalendar.

Видимо, есть какие-то законы ускорения, когда много вот таких простых действий - выгоднее, чем мало сложных выражений.


Много простых.

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Особенности языка mql5, тонкости и приёмы работы

amrali, 2024.11.26 18:33

   uint  K = 536895458;
   uint  L = 1468000  ;
   uint  m = 146097   ;
   uint  q = 2939745  ;
   uint  r = 2141     ;
   uint  p = 197913   ;
   ulong t = (ulong)time;
   int   n = (int)(t / 86400)                   ;  // Unix day
   uint  N = ((uint)n) + K                      ;  // Computational calendar day
   uint  a = 4 * N + 3                          ;
   uint  c = a / m                              ;
   uint  e = a % m / 4                          ;
   uint  b = 4 * e + 3                          ;
   ulong f = ((ulong)q) * b                     ;
   uint  z = (uint)(f >> 32)                    ;
   uint  h = ((uint)f) / q / 4                  ;
   uint  d = r * h + p                          ;
   uint  Y = 100 * c + z                        ;
   uint  M = d >> 16                            ;
   uint  D = ((ushort)d) / r                    ;
   uint  J = h >= 306                           ;  // Map from Computational to Gregorian calendar
   int Y_G = int((Y - L) + J)                   ;
   int M_G = int(J ? M - 12 : M)                ;
   int D_G = int(D + 1)                         ;


Мало сложных.

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Особенности языка mql5, тонкости и приёмы работы

fxsaber, 2024.11.27 14:32

   static const int Months[] = {0, 11512692, 11512196, 11511744, 11511248, 11510766, 11510272,
                                   11509790, 11509296, 11508797, 11508318, 11507822, 11507342};
   uint t       = (uint)time;
   int  n       = (int)(t / (24 * 3600));
   int  tn      = (n << 2) + 2;
   int  doy     = (tn % 1461) / 4;
   int  year    = (tn / 1461) + 1970; // to 2100
   int  mon     = (doy < 59) ? ((doy + 1) >> 5) + 1 : (((doy - !(year & 3)) * 67 + 2209 ) >> 11);   
   int  day     = n - (int)((year * 5844 - Months[mon]) >> 4);

Как по коду понять, какой из низ будет быстрее,- непонятно.

 

для чистоты, надо сравнивать с системной/библиотечной функций. (в системе несколько вариантов, что из них более "основное" затруднюсь ответить, кому интересно - выяснят)

struct struct_tm {
   int tm_sec;   // секунды
   int tm_min;   // минуты
   int tm_hour;  // часы
   int tm_mday;  // дни
   int tm_mon;   // месяц (от 0 январь)
   int tm_year;  // год (от 1900)
   int tm_wday;  // день недели
   int tm_yday;  // день года
   int tm_isdst; // флаг летнего времени   
};
#import "ucrtbase.dll"
   ulong _localtime64_s(struct_tm &res,datetime &time);
#import 
void OnStart()
{
   struct_tm tm;
   datetime time=TimeLocal();
   _localtime64_s(tm,time);
   PrintFormat("tm_year=%d tm_mon=%d tm_day=%d tm_hour=%d tm_min=%d tm_sec=%d",
      tm.tm_year+1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec);
}

вообще функций преобразования времени в структуры, всего 2: (1)Штатная от авторов терминала и MQL, (2) от авторов ОС и системных библиотек . И почти точно 1-я прямо или косвенно вызывает 2-ю. 

PS/ ни в коем случае не буду использовать и никому не советую использовать их самопальные "быстрые" аналоги. Все измерения скорости это просто спорт ради соревнований и "чьё конфу круче" :-)

 
fxsaber #:

Как по коду понять, какой из низ будет быстрее,- непонятно.

Эх, где времена программирования на ассемблере... Я вычислял скорость, зная кол-во тактов каждой команды. Надо было, когда в 80х писал драйвер для нашей лабораторной аудиокарты и синтезатор речи для ГТС.

А по делу - если посчитать к-во мат. операций и оценить скорость каждого типа ( * / + - )?

 
Edgar Akhmadeev #:

А по делу - если посчитать к-во мат. операций и оценить скорость каждого типа ( * / + - )?

Считал. И не увидел причин такой разницы.

 
Edgar Akhmadeev #:

Эх, где времена программирования на ассемблере... Я вычислял скорость, зная кол-во тактов каждой команды. Надо было, когда в 80х писал драйвер для нашей лабораторной аудиокарты и синтезатор речи для ГТС.

А по делу - если посчитать к-во мат. операций и оценить скорость каждого типа ( * / + - )?

сейчас не увидишь и влёт не посчитаешь..ну или надо сильно вкурить конвееры, регистер-алиасинг,кеши и прочее. 

это раньше было просто : вот процессор, он по одной исполняет команды. Общее время = сумма всех. Сейчас он всасывает пачку команд в кеш, внутри раскидывает в цепочки, планирует операции и одновременно всё это дело исполняет. примерно так

 
Maxim Kuznetsov #:

Сегодня вы этого не увидите и не вычислите... ну, или вам придется изучать конвекторы, сглаживание регистров, кэши и так далее.

Раньше все было просто: вот процессор, он выполняет команды одну за другой. Общее время = сумме всех. Теперь он засасывает кучу команд в кэш, выстраивает их в цепочки, планирует операции и выполняет все это одновременно. Примерно так

Очень приблизительно. x86 ISA - это интерпретируемый "язык".

Edit: на самом деле Intel указывает задержку для каждой команды.

См. этот пост для более подробной информации:

Но в целом, да, вы правы. Это не просто
 

Мои наблюдения за результатами:

   uint  K = 536895458;
   uint  L = 1468000  ;
   uint  m = 146097   ;
   uint  q = 2939745  ;
   uint  r = 2141     ;
   uint  p = 197913   ;
   ulong t = (ulong)time;
   int   n = (int)(t / 86400)                   ;  // Unix day
   uint  N = ((uint)n) + K                      ;  // Computational calendar day
   uint  a = 4 * N + 3                          ;
   uint  c = a / m                              ;
   uint  e = a % m / 4                          ;
   uint  b = 4 * e + 3                          ;

   ulong f = ((ulong)q) * b                     ;
   uint  z = (uint)(f >> 32)                    ;
   uint  h = ((uint)f) / q / 4                  ;
   uint  d = r * h + p                          ;
   uint  Y = 100 * c + z                        ;
   uint  M = d >> 16                            ;
   uint  D = ((ushort)d) / r                    ;
   uint  J = h >= 306                           ;  // Map from Computational to Gregorian calendar
   int Y_G = int((Y - L) + J)                   ;
   int M_G = int(J ? M - 12 : M)                ;
   int D_G = int(D + 1)                         ;

Этот код выполняется быстро, потому что:

* он использует наименьшее возможное количество делений и модуло (занимает много процессорных циклов).

* большинство переменных - 32-битные, беззнаковые типы.

* только одно ветвление (оператор conditionasl), поэтому конвейер процессора не перегружается.

* Код использует преимущества набора инструкций AVX2, который поддерживает FMA (fused multiply addition), в коде в основном используются * и +.

Посмотрите на контрасте, почему этот код медленнее:

bool TimeToJulian(datetime time, MqlDateTime& dt_struct)
  {
   int x = (int)(time / 86400)                           ;  // Unix day
   int J = x + 2440588                                   ;  // Julian day
   int f = J + j + (((4 * J + B) / 146097) * 3) / 4 + C  ;
   int e = r * f + v                                     ;
   int g = (e % p) / r                                   ;
   int h = u * g + w                                     ;
   int D = (h % s) / u + 1                               ;  // Map from Julian to Gregorian calendar
   int M = (h / s + m) % n + 1                           ;
   int Y = (e / p) - y + (n + m - M) / n                 ;

без ветвлений, но с большим количеством div/модов.

Один трюк, который я недавно узнал о типе datetime -> приведение переменных datetime к ulong (или еще лучше к uint) дает огромное ускорение работы с переменными datetime (в зависимости от окружающего кода).

Здесь, в этом коде, вторая версия TimeHour() работает быстрее:

int TimeHour(const datetime t)
  {
   return (int)(t / 60) % 24;
  }

int TimeHour2(const datetime t)
  {
   return (int)((uint) t / 60) % 24;
  }
  1.10 ns, checksum = 20499563265481417   // TimeHour
  0.67 ns, checksum = 20499563265481417   // TimeHour2

именно поэтому fxsaber обнаружил замедление в TimeToJulian, когда вычисляются hh:mm:ss. Потому что я забыл привести к ulong :-)

bool TimeToJulian(datetime time, MqlDateTime& dt_struct)
  {
   int x = (int)(time / 86400)                           ;  // Unix day
   int J = x + 2440588                                   ;  // Julian day
   int f = J + j + (((4 * J + B) / 146097) * 3) / 4 + C  ;
   int e = r * f + v                                     ;
   int g = (e % p) / r                                   ;
   int h = u * g + w                                     ;
   int D = (h % s) / u + 1                               ;  // Map from Julian to Gregorian calendar
   int M = (h / s + m) % n + 1                           ;
   int Y = (e / p) - y + (n + m - M) / n                 ;
#ifndef  WITHOUT_HOURS
   int HH  = (int)((time / 3600) % 24)                   ;
   int MM  = (int)((time / 60) % 60)                     ;
   int SS  = (int)(time % 60)                            ;
#endif //#ifndef WITHOUT_HOURS      

Использование предсказателя ветвей процессора может принести пользу микрооптимизации, см. здесь: https: //stackoverflow.com/a/11227902.

bool TimeToStructFast_fxsaber(datetime time, MqlDateTime& dt_struct)
  {
//   int  isleap  = ((year & 3) == 0);
//   int  leapadj = ((doy < (isleap + 59)) ? 0 : (2 - isleap));
//   int  mon     = ((((doy + leapadj) * 12) + 373) / 367);
//   int  day     = doy - Months[mon] - (isleap && doy > 59);
     int  mon     = (doy < 59) ? ((doy + 1) >> 5) + 1 : (((doy - !(year & 3)) * 67 + 2209 ) >> 11);   

Хотя проверка isleap исключена из основного пути инструкций, она все равно выполняется в 84% случаев. (1 - 59/365). Поэтому большого улучшения здесь не произошло. Процессор предсказывает правильное ответвление в 84 % случаев при (doy < 59), но он находит там другое ответвление !(year & 3), поэтому конвейер инструкций приходится промывать и заполнять заново.

Наконец, я думаю, что это максимум, что мы можем оптимизировать, любая из первых 4 функций находится в пределах допустимой 10% вариативности, и любая из них даже быстрее встроенного TimeToStruct в 4-5 раз в MQL.

IMHO эти функции можно оптимизировать еще больше, добавив кэш, подобно встроенной функции TimeToStruct https://www.mql5.com/ru/forum/170952/page274#comment_55238816.

 3.42 ns, checksum = 1230902265328330   // TimeToStruct2100
 2.97 ns, checksum = 1230902265328330   // TimeToStructFast
 3.65 ns, checksum = 1230902265328330   // TimeToStructFast_fxsaber
 3.18 ns, checksum = 1230902265328330   // TimeToCalendar
 7.04 ns, checksum = 1230902265328330   // TimeToJulian
18.47 ns, checksum = 1230902265328330  /// MQL's TimeToStruct()

TimeToJulian может быть менее оптимизирована для работы.

Коэффициенты ускорения для подфункций (TimeYear, TimeDayOfWeek и т.д.) для извлечения компонентов времени, таких как yyyy/mm/dd hh:mm:ss, находятся в пределах 10-20 раз быстрее, чем TimeToStruct().

  0.64 ns, checksum = 20497723281054842   // TimeYear
 18.21 ns, checksum = 20497723281054842  /// MQL's TimeToStruct()

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

Обычный код для торгового робота также может выиграть, но в меньшей степени.

С другой стороны медали, составление переменных datetime из компонентов времени (yyyy/mm/dd hh:mm:ss) с помощью пользовательской CreateDateTime() в качестве альтернативы StructToTime ускорится более чем в 30 раз.

  1.45 ns, checksum = 40671201835781217   // CreateDateTime
 34.83 ns, checksum = 40671201835781217  /// MQL's StructToTime()

Составление переменных времени действительно медленно на платформе MT5 и требует некоторого внимания со стороны разработчиков.

 

Для всех кэшированных версий я предлагаю следующее исправление возможной ошибки.

Изначально, когда функция вызывается несколько раз со временем 1970/1/1, она возвращает 000/00/00 для полей даты год, месяц.

-1 исправит эту ошибку, так что возвращаемые даты будут правильно 1970/1/1.

bool TimeToStructFast_Cached(datetime time, MqlDateTime& dt_struct)
  {
   static const int Months[13] = {0, -1, 30, 58, 89, 119, 150, 180, 211,242, 272, 303, 333};
   //static int last_days = 0;
   static int last_days = -1;   
   static MqlDateTime last_result = {};

   const uint t = (uint)time;
   const int  n = (int)(t / (24 * 3600));
   if (last_days != n)
     {