Velocidade de execução das funções ceil(),round(),floor()

 

Quero compartilhar uma descoberta inesperada, simples e útil com os programadores.

As funções de arredondamento:

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

Provaram ser muito lentos. Para acelerar o processo de arredondamento em 4-5 vezes (de acordo com meus testes em MQL5), você pode substituir estas funções por uma alternativa simples:

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

Como estas funções são freqüentemente utilizadas em laços grandes e aninhados, o ganho de desempenho pode ser bastante significativo.

Provavelmente, o fato de chamar uma função é bastante demorado (armazenamento de diferentes dados, endereços, etc.). E neste caso você pode passar sem funções.

Arquivo de roteiro com teste de desempenho anexado.

Arquivos anexados:
TestSpeed.mq5  3 kb
 
Nikolai Semko:

Quero compartilhar uma descoberta inesperada, simples e útil com os programadores.

As funções de arredondamento:

Provaram ser muito lentos. Para acelerar o processo de arredondamento em 4-5 vezes (de acordo com meus testes em MQL5), você pode substituir estas funções por uma alternativa simples:

Como estas funções são freqüentemente utilizadas em laços grandes e aninhados, o ganho de desempenho pode ser bastante significativo.

Provavelmente, o fato de chamar uma função é bastante demorado (armazenamento de diferentes dados, endereços, etc.). E neste caso você pode passar sem funções.

Arquivo de roteiro com teste de desempenho anexado.

Somente eu com esta linha

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

Não estou de acordo com esta linha. De acordo com as regras da matemática, se a parte fracionária for inferior a 0,5, o arredondamento é feito para o lado inferior. Mas se você acrescentar 0,5 a 45,27, ele é arredondado para o lado superior.

 
Alexey Viktorov:

Só que eu não concordo com essa linha.

Eu não concordo. De acordo com as regras da matemática, se a parte fracionária for inferior a 0,5, ela é arredondada para baixo. Mas se você acrescentar 0,5 a 45,27, ele é arredondado para o lado superior.


#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ó que eu não concordo com essa linha.

Eu não concordo. De acordo com as regras da matemática, se a parte fracionária for inferior a 0,5, ela é arredondada para baixo. Mas se você acrescentar 0,5 a 45,27, ele é arredondado para o lado superior.


Você está confuso. Eu introduzi propositalmente um código de verificação no exemplo:

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

Se eu estivesse errado, o operador deimpressão("oops...",x) seria executado;

Experimente - está tudo bem.

 
Alexey Viktorov:

Eu sou o único com essa linha.

não concordam. De acordo com as regras da matemática, se a parte fracionária for inferior a 0,5, ela é arredondada para baixo. Mas se você acrescentar 0,5 a 45,27, ele é arredondado para o lado superior.


E que tal tomar e verificar? ))) Como int(45,27 + 0,5) daria 46? Os mesmos 45 permanecerão.

 
Lilita Bogachkova:


Eu não estava falando de velocidade.

 
Nikolai Semko:

Você deve estar confuso. Eu introduzi propositalmente um código de verificação no exemplo:

se eu estivesse errado, então a declaração deimpressão("oops...",x) seria executada;

Experimente - está tudo bem.


Mas ainda é interessante que a velocidade muda se a matriznão for preenchida com dados deantemão


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

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

Que tal um teste? ))) Como int(45,27 + 0,5) daria 46? Os mesmos 45 permanecerão.

Concordo, perdi minha linha de raciocínio. Retiro o que disse...

 
Lilita Bogachkova:

Mas ainda é interessante que a velocidade muda se a matriznão for preenchida com dados deantemão


e preenchê-lo

É bastante simples. O compilador ignora o comando:
y0[i]+=0; // !!!!!!!!!!!!!!
porque não muda nada. O conjunto permanece não inicializado. Parece que a matriz já inicializada é acessada mais rapidamente. No primeiro caso, a inicialização é feita no segundo loop ao calcular t1, portanto t1 é maior que t2. E no segundo caso, a inicialização ocorre no primeiro loop. Portanto, t1 e t2 são a mesma coisa.
 

Eu acho "#define" mais conveniente

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

Por que você não se lança ao longo do tempo? Embora você também possa transbordar, é muito mais fácil transbordar um Int.