Velocidad de ejecución de las funciones ceil(),round(),floor()

 

Quiero compartir con los programadores un hallazgo inesperado, sencillo y útil.

Las funciones de redondeo:

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

Han demostrado ser muy lentos. Para acelerar el proceso de redondeo entre 4 y 5 veces (según mis pruebas en MQL5), puedes sustituir estas funciones por una alternativa sencilla:

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

Dado que estas funciones se utilizan a menudo en bucles grandes y anidados, la ganancia de rendimiento puede ser bastante significativa.

Probablemente, el hecho de llamar a una función consume bastante tiempo (almacenamiento de diferentes datos, direcciones, etc.). Y en este caso se puede prescindir de las funciones.

Se adjunta el archivo del script con la prueba de rendimiento.

Archivos adjuntos:
TestSpeed.mq5  3 kb
 
Nikolai Semko:

Quiero compartir con los programadores un hallazgo inesperado, sencillo y útil.

Las funciones de redondeo:

Han demostrado ser muy lentos. Para acelerar el proceso de redondeo entre 4 y 5 veces (según mis pruebas en MQL5), puedes sustituir estas funciones por una alternativa sencilla:

Dado que estas funciones se utilizan a menudo en bucles grandes y anidados, la ganancia de rendimiento puede ser bastante significativa.

Probablemente, el hecho de llamar a una función consume bastante tiempo (almacenamiento de diferentes datos, direcciones, etc.). Y en este caso se puede prescindir de las funciones.

Se adjunta el archivo de script con la prueba de rendimiento.

Sólo yo con esta línea

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

No estoy de acuerdo con esta línea. Según las reglas de las matemáticas, si la parte fraccionaria es inferior a 0,5, el redondeo se realiza hacia el lado inferior. Pero si se añade 0,5 a 45,27, se redondea hacia el lado más alto.

 
Alexey Viktorov:

Sólo que no estoy de acuerdo con esa línea.

No estoy de acuerdo. Según las reglas de las matemáticas, si la parte fraccionaria es inferior a 0,5, se redondea hacia abajo. Pero si se añade 0,5 a 45,27, se redondea hacia el lado más alto.


#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:

Sólo que no estoy de acuerdo con esa línea.

No estoy de acuerdo. Según las reglas de las matemáticas, si la parte fraccionaria es inferior a 0,5, se redondea hacia abajo. Pero si añades 0,5 a 45,27, se redondea hacia el lado más alto.


Estás confundido. He insertado a propósito un código de verificación en el ejemplo:

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

Si me equivocara, se ejecutaría el operadorPrint("oops...",x);

Pruébalo, está bien.

 
Alexey Viktorov:

Soy el único con esa línea.

no están de acuerdo. Según las reglas de las matemáticas, si la parte fraccionaria es inferior a 0,5, se redondea hacia abajo. Pero si añades 0,5 a 45,27, se redondea hacia el lado más alto.


¿Qué tal si lo tomas y lo compruebas? ))) ¿Cómo es que int(45,27 + 0,5) da 46? Se mantendrán los mismos 45.

 
Lilita Bogachkova:


No me refería a la velocidad.

 
Nikolai Semko:

Debes estar confundido. He insertado a propósito un código de verificación en el ejemplo:

si estuviera equivocado, entonces se ejecutaría la sentenciaPrint("oops...",x);

Pruébalo, está bien.


Pero no deja de ser interesante que la velocidad cambie si el arrayno estálleno de datosde antemano


#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)," микросекунд");
  }
//+------------------------------------------------------------------+


y llenado

#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:

¿Qué tal si lo compruebas? ))) ¿Cómo es que int(45,27 + 0,5) da 46? Se mantendrán los mismos 45.

Estoy de acuerdo, he perdido el hilo. Me retracto...

 
Lilita Bogachkova:

Pero no deja de ser interesante que la velocidad cambie si el arrayno está lleno de datosde antemano


y llenarlo

Es muy sencillo. El compilador ignora la orden:
y0[i]+=0; // !!!!!!!!!!!!!!
porque no cambia nada. El array permanece sin inicializar. Parece que se accede más rápido al array ya inicializado. En el primer caso, la inicialización se realiza en el segundo bucle al calcular t1, por lo que t1 es mayor que t2. Y en el segundo caso la inicialización se produce en el primer bucle. Por lo tanto, t1 y t2 son iguales.
 

Me parece que "#define" es más conveniente

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

¿Por qué no lanzas a lo largo? Aunque también se puede desbordar, es mucho más fácil desbordar un Int.