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

 
amrali #:
Я не считаю, что использование чистых макросов — это решение. Использование вспомогательной функции, например StringSubst() или Print(), которая вычисляется во время выполнения, может помочь избежать стандартного порядка раскрытия параметров макроса (2 шага). Такова семантика работы компилятора C.
Я не понял. Задача решена. Универсальный макрос перевода другого макроса в строку предоставлен, вне зависимости от количества запятых в исходном макросе.
 
amrali #:
Я не считаю это решением при использовании чистых макросов. Использование вспомогательной функции, например StringSubst() или Print(), которая оценивается во время выполнения, может помочь избежать стандартного порядка расширения параметров макроса (2 шага). Это семантика работы компилятора C.

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

К вашему сведению, комментарии удаляются парсером из файла исходного кода перед расширением любого макроса, поэтому комментарии внутри макросов удаляются.


Чистые макросы?

Макросы в MQL не обладают всеми возможностями C++. И даже макросы C++ очень ограничены, по сравнению с некоторыми существующими решениями (такими как M4, например).

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

В любом случае, нам придется с этим смириться.

 
Еще одно отличие MQL5 от MQL4.
void f( const string& ) {}

void OnStart()
{
  string Str = NULL;
  
//  f("Hello" + Str); // '+' - parameter passed as reference, variable expected
  f("Hello"); // OK.
}  
MQL4 такое не компилирует, в отличие от MQL5.
 

b5179, попробовал ускорить вычисления за счет создания таблицы заранее рассчитанных данных. Но ускорения не получил, т.к. каждое обращение к элементу массива по индексу внутри MQL5 вызывает проверку корректности индекса, даже если в коде все безошибочно.


Тогда решил попробовать создать объект, у которого обращение к индексу идет БЫСТРЕЕ, чем в массиве. И у меня "получилось"!

В кавычках, потому что получил необъяснимые результаты.

#include <fxsaber\TicksShort\Table.mqh> // https://www.mql5.com/ru/code/61126
#include <fxsaber\Benchmark\Benchmark.mqh> // https://www.mql5.com/ru/code/31279

#define AMOUNT 50 // Если сделать 60+, то будет замедление в сотни раз!

void OnStart()
{
  long Array[2048];
  ArrayInitialize(Array, 1);

  TABLE<long> Table;
  Table = Array;
    
  long Sum1 = 0;
  long Sum2 = 0;
    
  const ulong StartTime1 = GetMicrosecondCount();
    for (int i = 0; i < 1e10 / AMOUNT; i++)    
      for (uchar j = 0; j < AMOUNT; j++)
        Sum1 += Array[j];
  Print(GetMicrosecondCount() - StartTime1);

  const ulong StartTime2 = GetMicrosecondCount();
  _BV( // Если закомментировать эту строку и строку на четыре ниже, то замедление будет в 200 раз!
  for (int i = 0; i < 1e10 / AMOUNT; i++)    
    for (uchar j = 0; j < AMOUNT; j++)
      Sum2 += Table[j];
     , 1)
  Print(GetMicrosecondCount() - StartTime2);
      
  Print(Sum1);
  Print(Sum2);
}


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

432795 // Время на расчет суммы элементов массива по индексу.
Alert: Bench_Stack = 0, 1 <= Time[Test6.mq5 187 in OnStart: for (int i = 0; i < 1e10 / AMOUNT; i++) for (uchar j = 0; j < AMOUNT; j++) Sum2 += Table[j];] = 403864 mcs.
403951 // Время на расчет суммы элементов "таблицы" по индексу.
10000000000
10000000000

Оказывается, есть способ доступа к элементам по индексу на 8-9% быстрее, чем к элементам массива.


А дальше начинаются необъяснимые вещи, которые подсветил в исходнике. По какой-то причине таблица так быстро вычисляется только в том случае, если она облачена в макрос. Из-за этого макроса пришлось добавить соответствующий #include. И у меня не получается этот макрос воспроизвести без этого #include - замедление в сотни раз.


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


Еще претензия к компилятору. В исходнике индекс элемента массива uchar-типа, а количество элементов соответствующего статического массива 2048 - что явно больше UCHAR_MAX. Так зачем компилятор проверяет каждый раз индекс на выход за пределы массива?


Прошу не делать такие проверки в подобные ситуациях. И просьба объяснить аномальные поведения скрипта выше. Как и сообщить, возможно ли делать таблицы данных в статических массивах, чтобы не было проверок на индекс при каждом обращении?

Строка для поиска: Uluchshenie 130.
 
fxsaber #:

b5179, я пытался ускорить вычисления, создав таблицу с заранее рассчитанными данными. Но ускорения не получилось, так как каждое обращение к элементу массива по индексу в MQL5 вызывает проверку корректности индекса, даже если все в коде безошибочно.

Здесь есть несколько моментов, на которые стоит обратить внимание:

1. Проверка индексов для массивов выполняется только во время компиляции (если компилятор может определить границы массива) и никогда во время выполнения, поэтому вы можете получить ошибку "array out of index" во время выполнения, если ваш индекс находится за границами массива.

2. Более быстрое время доступа, которое было достигнуто здесь, никогда не связано с объектом или структурой, оно просто обусловлено оптимизацией компилятора для оператора "switch".

При компиляции небольшого или среднего переключателя с последовательными случаями (от 0 до n) в режиме оптимизации компилятор преобразует серию этих множественных операторов if-else O(n) в таблицу переходов, где массив указателей на блоки кода индексируется по номеру случая во время выполнения, обеспечивая прямой доступ O(1), аналогичный таблице поиска с использованием массива.

3. Оптимизация переключателя зависит от количества случаев + режима оптимизации. Большое количество или сложные переключатели не получат такой оптимизации таблицы переходов и останутся в виде нескольких if-else.

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

5. Было бы интереснее измерять время случайного доступа, а не последовательного, чтобы свести на нет влияние кэша процессора на результаты:

const ulong StartTime1 = GetMicrosecondCount();
for (int i = 0; i < INT_MAX; i++) { 
   uchar j = (uchar) (((ulong)i * 601) % AMOUNT);  // Случайный индекс. 601 - простое > AMOUNT
   Sum1 += Array[j];
}
Print(GetMicrosecondCount() - StartTime1);

 
amrali #:
Здесь есть несколько моментов, на которые стоит обратить внимание:

1. Проверка индексов для массивов выполняется только во время компиляции (если компилятор может определить границы массива) и никогда во время выполнения, поэтому вы можете получить ошибку "array out of index" во время выполнения, если ваш индекс находится за границами массива.

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

 
fxsaber #:

Я не согласен. Именно во время выполнения происходит проверка на выход массива за границы (а также проверка на валидность указателя при каждом обращении к нему).

Да, похоже, я был неправ в пункте №1, я не знал о новых проверках контроля доступа во время выполнения.

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

Еще один способ победить пространственную и временную локальность - это :
   // Создайте массив рандомизированных индексов
   int indices[][2];
   ArrayResize(indices, AMOUNT);
   for(int i = 0; i < AMOUNT; i++) {
      indices[i][0] = MathRand();
      indices[i][1] = i;
   }
   ArraySort(indices);

    // итерация массива в случайном порядке
   const ulong StartTime1 = GetMicrosecondCount();
   for (int i = 0; i < 1 e10 / AMOUNT; i++)    
      for (uchar j = 0; j < AMOUNT; j++) {
        int k = indices[j][1];
        Sum1 += Array[k];
      }
  Print(GetMicrosecondCount() - StartTime1);
 
amrali #:
Еще один способ победить пространственную и временную локальность - это :

Хороший способ перемешать индексы. Спасибо.

 

Более простой микс (Fixed):

const ulong StartTime1 = GetMicrosecondCount();
for (int i = 0; i < INT_MAX; i++) { 
   uchar j = (uchar) (((ulong)i * 67) % AMOUNT);  // Случайный индекс. 67 - простое число > СУММА
   Sum1 += Array[j];
}
Print(GetMicrosecondCount() - StartTime1);
 
b5179, еще одно интересное поведение компилятора.
#define AMOUNT 2000

void OnStart()
{
  long Array[AMOUNT];
  ArrayInitialize(Array, 1);
  
  long Sum = 0;

  const ulong StartTime1 = GetMicrosecondCount();
    for (int i = 0; i < 1e10 / AMOUNT; i++)    
      for (uint j = 0; j < AMOUNT; j++)
//        Sum += Array[j];    // 1880657
        Sum += Array[j] << 1; // 1154557: на 40% быстрее в AVX-режиме!
  Print(GetMicrosecondCount() - StartTime1);
      
  Print(Sum);
  Print("Compiler Version: " + (string)__MQLBUILD__ + " " + __CPU_ARCHITECTURE__); // Compiler Version: 5179 AVX
}

Если при сложении делать дополнительную битовую операцию, то скорость выполнения возрастает на 40%.

Строка для поискаOshibka 139.