Velocità di esecuzione delle funzioni ceil(),round(),floor()

 

Voglio condividere con i programmatori una scoperta inaspettata, semplice e utile.

Le funzioni di arrotondamento:

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

Hanno dimostrato di essere molto lenti. Per accelerare il processo di arrotondamento di 4-5 volte (secondo i miei test in MQL5), potete sostituire queste funzioni con una semplice alternativa:

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

Poiché queste funzioni sono spesso utilizzate in cicli grandi e annidati, il guadagno di prestazioni può essere abbastanza significativo.

Probabilmente, il fatto di chiamare una funzione richiede molto tempo (memorizzazione di diversi dati, indirizzi, ecc.). E in questo caso si può fare a meno delle funzioni.

File di script con test di performance allegato.

File:
TestSpeed.mq5  3 kb
 
Nikolai Semko:

Voglio condividere con i programmatori una scoperta inaspettata, semplice e utile.

Le funzioni di arrotondamento:

Hanno dimostrato di essere molto lenti. Per accelerare il processo di arrotondamento di 4-5 volte (secondo i miei test in MQL5), potete sostituire queste funzioni con una semplice alternativa:

Poiché queste funzioni sono spesso utilizzate in cicli grandi e annidati, il guadagno di prestazioni può essere abbastanza significativo.

Probabilmente, il fatto di chiamare una funzione richiede molto tempo (memorizzazione di diversi dati, indirizzi, ecc.). E in questo caso si può fare a meno delle funzioni.

File di script con test di performance allegato.

Solo io con questa linea

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

Non sono d'accordo con questa linea. Secondo le regole della matematica, se la parte frazionaria è inferiore a 0,5, l'arrotondamento viene fatto al lato inferiore. Ma se si aggiunge 0,5 a 45,27, si arrotonda al lato superiore.

 
Alexey Viktorov:

Solo che non sono d'accordo con questa linea.

Non sono d'accordo. Secondo le regole della matematica, se la parte frazionaria è inferiore a 0,5, viene arrotondata per difetto. Ma se si aggiunge 0,5 a 45,27, si arrotonda al lato superiore.


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

Solo che non sono d'accordo con questa linea.

Non sono d'accordo. Secondo le regole della matematica, se la parte frazionaria è inferiore a 0,5, viene arrotondata per difetto. Ma se si aggiunge 0,5 a 45,27, si arrotonda per eccesso.


Sei confuso. Ho volutamente inserito un codice di verifica nell'esempio:

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

Se mi fossi sbagliato, verrebbe eseguito l'operatorePrint("oops...",x);

Provalo - va bene.

 
Alexey Viktorov:

Sono l'unico con quella linea.

non sono d'accordo. Secondo le regole della matematica, se la parte frazionaria è inferiore a 0,5, viene arrotondata per difetto. Ma se si aggiunge 0,5 a 45,27, si arrotonda al lato superiore.


Che ne dite di prenderlo e controllarlo? ))) In che modo int(45.27 + 0.5) darebbe 46? Gli stessi 45 rimarranno.

 
Lilita Bogachkova:


Non stavo parlando di velocità.

 
Nikolai Semko:

Devi essere confuso. Ho volutamente inserito un codice di controllo nell'esempio:

se mi fossi sbagliato, sarebbe stata eseguita l'istruzionePrint("oops...",x);

Provalo - va bene.


Ma è ancora interessante che la velocità cambia se l'arraynon èriempito di datiin anticipo


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


e riempito

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

Che ne dite di un test? ))) In che modo int(45.27 + 0.5) darebbe 46? Gli stessi 45 rimarranno.

Sono d'accordo, ho perso il filo del discorso. Mi rimangio tutto...

 
Lilita Bogachkova:

Ma è ancora interessante che la velocità cambia se l'arraynon è riempito di datiin anticipo


e riempirlo

È abbastanza semplice. Il compilatore ignora il comando:
y0[i]+=0; // !!!!!!!!!!!!!!
perché non cambia nulla. L'array rimane non inizializzato. Sembra che l'accesso all'array già inizializzato sia più veloce. Nel primo caso, l'inizializzazione viene eseguita nel secondo ciclo durante il calcolo di t1, quindi t1 è più grande di t2. E nel secondo caso l'inizializzazione avviene nel primo ciclo. Pertanto, t1 e t2 sono uguali.
 

Trovo che "#define" sia più conveniente

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

Perché non lanci alla lunga? Sebbene si possa anche traboccare, è molto più facile traboccare un Int.