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

 
Наткнулся на ситуацию, когда switch может быть ускорен
#define MACROS2 \
  MACROS(0)     \
  MACROS(1)     \
  MACROS(2)     \
  MACROS(3)     \
  MACROS(4)     \
  MACROS(5)     \
  MACROS(6)     \
  MACROS(7)     \
  MACROS(8)     \
  MACROS(9)

double Math( const double Value ) { return(MathSqrt(Value)); }

typedef double(*SUMFUNC)( void );

#define MACROS(A) double SumFunc##A() { return(Math(A + 1)); }
  MACROS2
#undef MACROS

SUMFUNC SumFunc[10];

bool Init()
{
#define MACROS(A) SumFunc[A] = SumFunc##A;
  MACROS2
#undef MACROS
  
  return(true);
}

const bool Init = Init();

// Через switch
void SwitchFunc1( const int Value, double &Sum )
{    
  switch (Value)
  {
  #define MACROS(A) case A: Sum += Math(A + 1); break;
    MACROS2
  #undef MACROS    
  }
}

// Через массив указателей на функции
void SwitchFunc2( const int Value, double &Sum )
{
  Sum += SumFunc[Value]();
}

// Через switch2
void SwitchFunc3( const int Value, double &Sum )
{    
  switch (Value)
  {
  #define MACROS(A) case A: Sum += SumFunc##A(); break;
    MACROS2
  #undef MACROS    
  }
}

typedef void(*SWITCHFUNC)( const int, double& );

void Bench( SWITCHFUNC SwitchFunc )
{
  double Sum = 0;
  
  MathSrand(0);
  
  for (int i = 0; i < 1e8; i++)
    SwitchFunc(MathRand() % 10, Sum);
    
  Print(Sum);
}

#define BENCH(A)                                                              \
{                                                                             \
  const ulong StartTime = GetMicrosecondCount();                              \
  A;                                                                          \
  Print("Time[" + #A + "] = " + (string)(GetMicrosecondCount() - StartTime)); \
}  

void OnStart()
{
  BENCH(Bench(SwitchFunc1))
  BENCH(Bench(SwitchFunc2))  
  BENCH(Bench(SwitchFunc3))  
}


Результат

224677638.4805779
Time[Bench(SwitchFunc1)] = 635995
224677638.4805779
Time[Bench(SwitchFunc2)] = 1646031
224677638.4805779
Time[Bench(SwitchFunc3)] = 1593283


Стабильно третий вариант (switch) медленнее второго (указатели на функции). По какой причине такое происходит?


ЗЫ Торможу. Третий быстрее второго. Все норм.


ЗЫЫ Получается, что если есть неизменяемый массив указателей на функции, то быстрее будет, если его заменить на switch.

Почему Switch/Case, а не If/Else If?
  • 2009.06.22
  • OB OB
  • qaru.site
Этот вопрос в основном указывается на C/С++, но я думаю, что другие языки также актуальны. Я не могу понять, почему используется параметр switch/case вместо if/else if. Мне очень нравится использование goto's и приводит к тому же виду беспорядочного кода, в то время как те же результаты могут быть достигнуты с if/else, если более организованным...
 
fxsaber:


ЗЫЫ Получается, что если есть неизменяемый массив указателей на функции, то быстрее будет, если его заменить на switch.

Ну в данном случае - это логично, т.к. массив заполнялся динамически, а значит идут постоянные проверки валидности указателей.  Хотя могло бы конечно и оптимизироваться...

Вот если бы MQL поддерживал инициализацию массива константными указателями, тогда, возможно, было бы одинаково.

p.s. Ваш код совершенно не читабелен. Я конечно понимаю, что вам удобны эти навороты с макросами, ибо вы сами их писали, поэтому знаете, что они делают.  Но для стороннего читателя - это просто головоломка. Зачем выкладывать такое?  Думаю вы и сами через полгода вряд ли поймёте, что здесь наворотили )  Хоть бы комментарии делали чтоль...

 
Alexey Navoykov:

p.s. Ваш код совершенно не читабелен. Я конечно понимаю, что вам удобны эти навороты с макросами, ибо вы сами их писали, поэтому знаете, что они делают.  Но для стороннего читателя - это просто головоломка. Зачем выкладывать такое?  Думаю вы и сами через полгода вряд ли поймёте, что здесь наворотили )  Хоть бы комментарии делали чтоль...

Иначе была бы портянка. Более того, экспериментировал с количеством переходов. Без макросов это делать было бы сложно. По поводу доп. комментариев - учту в будущем.

 
fxsaber:

Иначе была бы портянка. Более того, экспериментировал с количеством переходов. Без макросов это делать было бы сложно. По поводу доп. комментариев - учту в будущем.

Иногда гораздо проще разобрать понятную портянку, чем начать разбирать компактный ребус и тут же забросить сиё бесполезное занятие.

 
Artyom Trishkin:

Иногда гораздо проще разобрать понятную портянку, чем начать разбирать компактный ребус и тут же забросить сиё бесполезное занятие.

double Math( const double Value ) { return(MathSqrt(Value)); }

typedef double(*SUMFUNC)( void );

double SumFunc0() { return(Math(0 + 1)); }
double SumFunc1() { return(Math(1 + 1)); }
double SumFunc2() { return(Math(2 + 1)); }
double SumFunc3() { return(Math(3 + 1)); }
double SumFunc4() { return(Math(4 + 1)); }
double SumFunc5() { return(Math(5 + 1)); }
double SumFunc6() { return(Math(6 + 1)); }
double SumFunc7() { return(Math(7 + 1)); }
double SumFunc8() { return(Math(8 + 1)); }
double SumFunc9() { return(Math(9 + 1)); }

SUMFUNC SumFunc[10];

bool Init()
{
  SumFunc[0] = SumFunc0;
  SumFunc[1] = SumFunc1;
  SumFunc[2] = SumFunc2;
  SumFunc[3] = SumFunc3;
  SumFunc[4] = SumFunc4;
  SumFunc[5] = SumFunc5;
  SumFunc[6] = SumFunc6;
  SumFunc[7] = SumFunc7;
  SumFunc[8] = SumFunc8;
  SumFunc[9] = SumFunc9;
  
  return(true);
}

const bool Init = Init();

// Через switch
void SwitchFunc1( const int Value, double &Sum )
{    
  switch (Value)
  {
  case 0: Sum += Math(0 + 1); break;
  case 1: Sum += Math(1 + 1); break;
  case 2: Sum += Math(2 + 1); break;
  case 3: Sum += Math(3 + 1); break;
  case 4: Sum += Math(4 + 1); break;
  case 5: Sum += Math(5 + 1); break;
  case 6: Sum += Math(6 + 1); break;
  case 7: Sum += Math(7 + 1); break;
  case 8: Sum += Math(8 + 1); break;
  case 9: Sum += Math(9 + 1); break;
  }
}

// Через массив указателей на функции
void SwitchFunc2( const int Value, double &Sum )
{
  Sum += SumFunc[Value]();
}

// Через switch2
void SwitchFunc3( const int Value, double &Sum )
{    
  switch (Value)
  {
  case 0: Sum += SumFunc0(); break;
  case 1: Sum += SumFunc1(); break;
  case 2: Sum += SumFunc2(); break;
  case 3: Sum += SumFunc3(); break;
  case 4: Sum += SumFunc4(); break;
  case 5: Sum += SumFunc5(); break;
  case 6: Sum += SumFunc6(); break;
  case 7: Sum += SumFunc7(); break;
  case 8: Sum += SumFunc8(); break;
  case 9: Sum += SumFunc9(); break;
  }
}

typedef void(*SWITCHFUNC)( const int, double& );

void Bench( SWITCHFUNC SwitchFunc )
{
  double Sum = 0;
  
  MathSrand(0);
  
  for (int i = 0; i < 1e8; i++)
    SwitchFunc(MathRand() % 10, Sum);
    
  Print(Sum);
}

#define BENCH(A)                                                              \
{                                                                             \
  const ulong StartTime = GetMicrosecondCount();                              \
  A;                                                                          \
  Print("Time[" + #A + "] = " + (string)(GetMicrosecondCount() - StartTime)); \
}  

void OnStart()
{
  BENCH(Bench(SwitchFunc1))
  BENCH(Bench(SwitchFunc2))  
  BENCH(Bench(SwitchFunc3))  
}
 

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


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


Чтобы поймать такие потенциальные проблемы, можно в начале советника вставить такие строки

// Помогает найти причину потенциальной ошибочной инициализации
template <typename T>
int ArrayResize2( T &Array[], const int NewSize, const string Str )
{
  const int PrevSize = ArraySize(Array);
  const int Res = ArrayResize(Array, NewSize);
  
  if ((PrevSize < NewSize) || (Res != NewSize))
  {
  #define TOSTRING(A) ", " + #A + " = " + (string)(A)
    Print(Str + (string)Res + TOSTRING(PrevSize) + TOSTRING(NewSize));
  #undef TOSTRING     
  
    TesterStop();
  }
  
  return(Res);
}

// Вариант с Reserve != 0 не предусмотрен
#define ArrayResize(A,B) ArrayResize2(A, B, __FUNCSIG__ + ", Line = " + (string)__LINE__ + \
                                            ": ArrayResize(" + #A + ", " + #B + ") = ")
// Помогает найти причину потенциальной ошибочной инициализации
template <typename T1, typename T2>
int ArrayInitialize2( T1 &Array[], const T2 Value, const string Str )
{
  const int Res = ArrayInitialize(Array, Value);
  
  if (!ArraySize(Array) || !Res)
  {
  #define TOSTRING(A) ", " + #A + " = " + (string)(A)
    Print(Str + (string)Res + TOSTRING(ArraySize(Array)));
  #undef TOSTRING     
  
    TesterStop();
  }
  
  return(Res);
}

#define ArrayInitialize(A,B) ArrayInitialize2(A, B, __FUNCSIG__ + ", Line = " + (string)__LINE__ +  \
                                                    ": ArrayInitialize(" + #A + ", " + #B + ") = ")


Если ситуация будет поймана, то в лог будет выведена подробная информация и прогон будет остановлен.


ЗЫ Пример применения.

 
C ресайзом понятно, я сам часто пользуюсь аналогичным способом,  а с инициализацией то что? Как она может быть ошибочной?
 
Alexey Navoykov:
C ресайзом понятно, я сам часто пользуюсь аналогичным способом,  а с инициализацией то что? Как она может быть ошибочной?

Например, перепутаны местами ArrayResize и ArrayInitialize. Или, например, в индикаторе делается ArrayInitialize буфера в OnInit, ошибочно думая, что буфер инициализировался.

 
fxsaber:

Например, перепутаны местами ArrayResize и ArrayInitialize. 

ну это совсем детская ошибка. стоит ли её нахождение таких усилий

 
Ilya Malev:

ну это совсем детская ошибка. стоит ли её нахождение таких усилий

Нахождение любой ошибки требует усилий. Особенно, когда код большой и не свой.