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

これらの関数は大規模なループやネストされたループで使用されることが多いため、性能向上は非常に大きなものとなります。

おそらく、関数を呼び出す こと自体にかなりの時間(異なるデータ、アドレスの保存など)がかかっているのだろう。そしてこの場合、機能はなくても大丈夫です。

パフォーマンステストを行ったスクリプトのファイルを添付します。

ファイル:
TestSpeed.mq5  3 kb
 
Nikolai Semko:

意外でシンプルで便利な発見をプログラマーと共有したい。

丸め機能です。

非常に遅いことが証明されている。丸め処理を4-5倍速くするには(MQL5でテストしたところ)、これらの関数を単純な代替関数に置き換えることができます。

これらの関数は大規模なループやネストされたループで使用されることが多いため、性能向上は非常に大きなものとなります。

おそらく、関数を呼び出す こと自体にかなりの時間(異なるデータ、アドレスの保存など)がかかっているのだろう。そしてこの場合、機能はなくても大丈夫です。

パフォーマンステストのスクリプトファイルを添付します。

このセリフで私だけが

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("oops...",x) 演算子が実行されます。

試してみてください-大丈夫です。

 
Alexey Viktorov:

その台詞は私一人です。

は納得いかない。数学のルールでは、端数が0.5未満になると切り捨てになります。しかし、45.27に0.5を足すと、高い方に丸められます。


持ってみて確認してみてはいかがでしょうか。)))int(45.27 + 0.5)で46はどうなる?同じ45が残ります。

 
Lilita Bogachkova:


スピードの話じゃないんです。

 
Nikolai Semko:

戸惑っているのでは?例ではあえてチェックコードを挿入しています。

というのは、間違っていたら、Print("oops...",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; // !!!!!!!!!!!!!!
というのは、何も変わらないからです。配列は初期化されないままです。すでに初期化されている配列の方が高速にアクセスできるようです。1の場合、2番目のループでt1を計算する際に初期化を行うため、t1はt2より大きくなる。そして、2つ目のケースでは、最初のループで初期化が行われます。したがって、t1 と t2 は同じである。
 

私は「#define」の方が便利だと思います。

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

なぜロングにキャストしないのか?混雑することもありますが、Intの混雑はかなり緩和されます。

理由: