Скорость выполнения функций ceil(),round(),floor()

 

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

Функции округления:

floor(), ceil(), round() 
они же
MathFloor(), MathCeil(),MathRound()

оказались весьма медлительными. Чтобы убыстрить процесс округления в 4-5 раз (по моим тестам на MQL5) можно заменить эти функции простой альтернативой:

double x=45.27;
int y;
//работает только для положительных чисел!!! 
y=floor(x); -> y=(int)x;
y=ceil(x);  -> y=(x-(int)x>0)?(int)x+1:(int)x;   //более быстрым вариантом является: -> y=(int)(x+0.9999999999999997), но при этом возможна некорректная отработка
y=round(x); -> y=(int)(x+0.5);

Удобнее использовать #define. Например:
#define _ceil(x) (x-(int)x>0)?(int)x+1:(int)x
y=_ceil(x);

// Для положительных и отрицательных чисел: ( выигрыш в скорости - в 3-4 раза)
#define _ceil(x) (x-(int)x>0)?(int)x+1:(int)x
#define _round(x) (x>0)?(int)(x+0.5):(int)(x-0.5)
#define _floor(x) (x>0)?(int)x:((int)x-x>0)?(int)x-1:(int)x

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

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

Файл скрипта с тестом производительности прилагаю.

Файлы:
TestSpeed.mq5  3 kb
 
Nikolai Semko:

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

Функции округления:

оказались весьма медлительными. Чтобы убыстрить процесс округления в 4-5 раз (по моим тестам на MQL5) можно заменить эти функции простой альтернативой:

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

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

Файл скрипта с тестом производительности прилагаю.

Только я с этой строкой

y=round(x); -> y=(int)(x+0.5);

не согласен. По правилам математики если дробная часть меньше 0.5 то округление делается в меньшую сторону. Но если к 45.27 ещё прибавить 0.5 то округлится до большего.

 
Alexey Viktorov:

Только я с этой строкой

не согласен. По правилам математики если дробная часть меньше 0.5 то округление делается в меньшую сторону. Но если к 45.27 ещё прибавить 0.5 то округлится до большего.


#define MUL(x) ((x)+(0.5)) 
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   ulong t0,t1,t2,t3;
   int y0[],y1[],y2[];
   ArrayResize(y0,10000000);
   ArrayResize(y1,10000000);
   ArrayResize(y2,10000000);
   double x=1.45;  

   for(int i=0;i<10000000;i++)
     {
      if ((int)(x+0.5)!=(int)round(x)) Print("ой...",x);
      x+=0.27;
      y0[i]+=0;
      y1[i]+=0;
      y2[i]+=0;
     }
     Print("y0[]: ",y0[9999999]," / y1[]: ",y1[9999999]," / y2[]: ",y2[9999999]);
   x=1.45;
   t0=GetMicrosecondCount();
   for(int i=0;i<10000000;i++)
     {
      y0[i]=(int)MathRound(x);
      x+=0.27;
     }
   t1=GetMicrosecondCount()-t0;
   Print("y0[]: ",y0[9999999]);
   x=1.45;
   t0=GetMicrosecondCount();
   for(int i=0;i<10000000;i++)
     {
      y1[i]=(int)(x+0.5);
      x+=0.27;
     }
   t2=GetMicrosecondCount()-t0;
   Print("y1[]: ",y1[9999999]);
   x=1.45;
   t0=GetMicrosecondCount();
   for(int i=0;i<10000000;i++)
     {
      y2[i]=(int)MUL(x);
      x+=0.27;
     }
   t3=GetMicrosecondCount()-t0;
   Print("y2[]: ",y2[9999999]);
   Print("Цикл округления 10 000 000 раз: (round) = ",IntegerToString(t1),"   альтернатива с (int) = ",IntegerToString(t2),"   альтернатива с (#define) = ",IntegerToString(t3)," микросекунд");
  }
//+------------------------------------------------------------------+
 
Alexey Viktorov:

Только я с этой строкой

не согласен. По правилам математики если дробная часть меньше 0.5 то округление делается в меньшую сторону. Но если к 45.27 ещё прибавить 0.5 то округлится до большего.


Что то вы путаете. Я в пример специально вставил проверочный код:

for(int i=0;i<10000000;i++)
     {
      if ((int)(x+0.5)!=(int)round(x)) Print("ой...",x);
      x+=0.27;
     }

если бы я был не прав, тогда бы выполнился бы оператор Print("ой...",x);

Попробуйте - все Ок.

 
Alexey Viktorov:

Только я с этой строкой

не согласен. По правилам математики если дробная часть меньше 0.5 то округление делается в меньшую сторону. Но если к 45.27 ещё прибавить 0.5 то округлится до большего.


А если взять и проверить? ))) Каким образом int(45.27 + 0.5) даст 46? Те же 45 и останутся.

 
Lilita Bogachkova:


Я не о скорости говорил.

 
Nikolai Semko:

Что то вы путаете. Я в пример специально вставил проверочный код:

если бы я был не прав, тогда бы выполнился бы оператор Print("ой...",x);

Попробуйте - все Ок.


Но все равно интересно что скорость меняется если массив заранее не заполнять данными


#define _round(x) (int)((x)+(0.5)) 
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   ulong t0,t1,t2;
   int y0[],y1[];
   ArrayResize(y0,10000000);

   double x=1.45;  

   for(int i=0;i<10000000;i++)
     {
      if ((int)(x+0.5)!=(int)round(x)) Print("ой...",x);
      x+=0.27;
      y0[i]+=0; // !!!!!!!!!!!!!!
     }

   x=1.45;
   t0=GetMicrosecondCount();
   for(int i=0;i<10000000;i++)
     {
      y0[i]=(int)(x+0.5);
      x+=0.27;
     }
   t1=GetMicrosecondCount()-t0;

   x=1.45;
   t0=GetMicrosecondCount();
   for(int i=0;i<10000000;i++)
     {
      y0[i]=_round(x);
      x+=0.27;
     }
   t2=GetMicrosecondCount()-t0;

   Print("Цикл округления 10 000 000 раз:  с (int) = ",IntegerToString(t1),"   с (#define) = ",IntegerToString(t2)," микросекунд");
  }
//+------------------------------------------------------------------+


 и заполнять

#define _round(x) (int)((x)+(0.5)) 
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   ulong t0,t1,t2;
   int y0[],y1[];
   ArrayResize(y0,10000000);

   double x=1.45;  

   for(int i=1;i<10000000;i++)
     {
      if ((int)(x+0.5)!=(int)round(x)) Print("ой...",x);
      x+=0.27;
      y0[i]+=1; // !!!!!!!!!!!!!!
     }

   x=1.45;
   t0=GetMicrosecondCount();
   for(int i=0;i<10000000;i++)
     {
      y0[i]=(int)(x+0.5);
      x+=0.27;
     }
   t1=GetMicrosecondCount()-t0;

   x=1.45;
   t0=GetMicrosecondCount();
   for(int i=0;i<10000000;i++)
     {
      y0[i]=_round(x);
      x+=0.27;
     }
   t2=GetMicrosecondCount()-t0;

   Print("Цикл округления 10 000 000 раз:  с (int) = ",IntegerToString(t1),"   с (#define) = ",IntegerToString(t2)," микросекунд");
  }
//+------------------------------------------------------------------+
 
Ihor Herasko:

А если взять и проверить? ))) Каким образом int(45.27 + 0.5) даст 46? Те же 45 и останутся.

Согласен, тормознул. Беру свои слова взад...

 
Lilita Bogachkova:

Но все равно интересно что скорость меняется если массив заранее не заполнять данными


 и заполнять

Всё просто.  Компилятор игнорирует команду: 
y0[i]+=0; // !!!!!!!!!!!!!!
т. к. она ничего не меняет. Массив остаётся неинициализированным. Похоже на то,  что уже к инициализированному  массиву доступ происходит быстрее. В первом случае инизиализация происходит во втором цикле при вычислении t1, поэтому и t1 больше t2. А во втором случае инизиализация происходит в первом цикле.  Поэтому t1 и t2 одинаковые. 
 

 С "#define" на мой взгляд удобней

#define _floor(x) (int)((x)) 
#define _ceil(x)  (int)((x)+(1)) 
#define _round(x) (int)((x)+(0.5)) 
 

А почему не к лонгу кастуете? Хотя и его можно переполнить, но переполнить инт много легче.

Причина обращения: