mql5言語の特徴、微妙なニュアンスとテクニック - ページ 136

 
Nikolai Semko:
ベッドの下に転がっている10円玉を見つけるために100円札に火をつけるのか?

コインかもしれないし、そうでないかもしれない

   srand(GetTickCount());
   uint v10 = 0, v19900 = 0;
   for (uint i = 0;  i < UINT_MAX;  ++ i) {
      int cur = rand();
      if (cur % 20000  == 10)
         ++ v10;
      else if (cur % 20000  == 19900)
         ++ v19900;   
   }
   Alert("10 probability = ", (double)v10/UINT_MAX*100, "%;  1990 probability = ", (double)v19900/UINT_MAX*100, "%");
   // Alert: 10 probability = 0.006103515626421085%;  19900 probability = 0.003051757813210543%
        

10の確率は2倍です。そして何より、get_rand()のコスト性の主張が手から吸い取られます。正規分布ができるのに、なぜ確率をずらした乱数を バックドアから(一様分布を期待しながら)取得 するのでしょうか?100円札を救うのではなく、マッチを救うのです。

 
Vict:

コインかもしれないし、そうでないかもしれない

10の確率は2倍です。そして何より、get_rand()のコスト性の主張が手から吸い取られます。正規分布ができるのに、なぜ確率をずらした乱数を バックドアから(一様分布を期待しながら)取得 するのでしょうか?100円札を救うのではなく、マッチを救うのです。

そうです、あなたの機能の速度の高さについて、私は間違っていました。アルゴリズムを誤解していました。すみません。
しかし、私のアルゴリズムは、あなたのように32767に限定されず、より普遍的であるという事実にもかかわらず、提案されたすべてのものの中で最も速いです。
それを証明するコード。

このスクリプトは、ランダムな色とランダムな座標を持つ点の配列をランダムに生成します。配列のサイズは、チャート図上のピクセル数と同じです。5回繰り返される

  1. 通常の rand() 関数で
  2. get_rand() 関数で
  3. 私の関数ulong randUlong()を使って
  4. 私の関数 uint randUint() を使って
  5. AlexeyNavoykov 氏の ulongRandomLong() 関数を使用する。
2019.06.0903:42:25.958 TestSpeedRand (EURGBP,H4)       Время формирования случайных массивов = 9894 микросекунд.  Всего сгенерировано 5203975 случайных чисел rand()
2019.06.0903:42:28.010 TestSpeedRand (EURGBP,H4)       Время формирования случайных массивов = 24899 микросекунд. Всего сгенерировано 5203975 случайных чисел get_rand()
2019.06.0903:42:30.057 TestSpeedRand (EURGBP,H4)       Время формирования случайных массивов = 22172 микросекунд. Всего сгенерировано 5203975 случайных чисел randUlong()
2019.06.0903:42:32.098 TestSpeedRand (EURGBP,H4)       Время формирования случайных массивов = 16013 микросекунд. Всего сгенерировано 5203975 случайных чисел randUint()
2019.06.0903:42:34.145 TestSpeedRand (EURGBP,H4)       Время формирования случайных массивов = 25524 микросекунд. Всего сгенерировано 5203975 случайных чисел RandomLong()

rand()%20000を適用したときの、問題の本質を示すような数字を拾ってみました。


あるべき姿


//+------------------------------------------------------------------+
uint get_rand(uint max)
  {
   staticbool f=false;
   if(!f) 
     {
      f=true;
      srand(GetTickCount());
     }
   uint limit=(max+1) *((32767+1)/(max+1));
   uint val;
   while((val=rand())>=limit);
   return val % (max+1);
  }
//+------------------------------------------------------------------+
ulong randUlong(ulong max=ULONG_MAX){return(((ulong)rand()<<60)|((ulong)rand()<<45)|((ulong)rand()<<30)|((ulong)rand()<<15)|(ulong)rand())%max;}
//+------------------------------------------------------------------+
uint randUint(uint max=UINT_MAX) {return(((uint)rand()<<30)|((uint)rand()<<15)|(uint)rand())%max;}
//+------------------------------------------------------------------+
ulong RandomLong(ulong range)
  {
#define _MAXRND(rang,rnd_range) ((rnd_range) -((rnd_range)-rang)%rang-1)
#define _RND ulong(rand())
   ulong rnd,max,const bit=1;
   if(range <= bit<<15) { if(!range) return0;  max=_MAXRND(range, 1<<15);  while((rnd=_RND) > max);  return rnd%range; }
   if(range <= bit<<30) { max=_MAXRND(range, bit<<30);  while((rnd=(_RND | _RND<<15)) > max);  return rnd%range; }
   if(range <= bit<<45) { max=_MAXRND(range, bit<<45);  while((rnd=(_RND | _RND<<15 | _RND<<30)) > max);  return rnd%range;  }
   if(range <= bit<<60) { max=_MAXRND(range, bit<<60);  while((rnd=(_RND | _RND<<15 | _RND<<30 | _RND<<45)) > max);  return rnd%range; }
   else  { max=_MAXRND(range,bit<<64);  while((rnd=(_RND|_RND<<15|_RND<<30|_RND<<45|_RND<<60))>max);  return rnd%range; }
#undef _RND
#undef _MAXRND
  }
//+------------------------------------------------------------------+
しかし、99.9%はこの機能でも動作します。
uint randUint(uint max) {return(((uint)rand()<<15)|(uint)rand())%max;}
は、さらに高速に動作するようになります。
この関数は、0から1073741824までの乱数を生成します。 この数は、全履歴におけるあらゆる商品のティック数よりもさらに大きな数です。このような機能の「不公平感」は、99.9%のタスクでは微々たるものでしょう。
ファイル:
 
Nikolai Semko:

しかし、私のアルゴリズムは、あなたのように32767に限定されない普遍的なものであるにもかかわらず、提案されたすべてのアルゴリズムの中で最速であることが判明しています。
証明としてのコード。

rand()関数は、算術演算よりも 高速に動作することがわかりました。

 
Alexey Navoykov:

お疲れ様でした。本当に面白い結果ですね。 rand()は算術演算より 速いほど高速であることがわかりました。

いいえ、そんなことはありません。2倍した数字から平方根を取り出すように、ナノ秒くらい。ナノ秒の単位で+/-*/の演算が行われます。
しかし、平方根と同じように、最近のプロセッサではrand()はプログラムではなくハードウェアレベルで実行される。

 
Nikolai Semko:

いいえ、もっと速くありません。2倍した数字から平方根を取り出すように、ナノ秒くらい。ナノ秒の単位で+/-*/の演算が行われます。
しかし、平方根と同じように、最近のプロセッサではrand()はプログラムではなくハードウェアレベルで実行される。

あなたのコードではrand()が常に5回呼ばれるのに対し、私のコードでは20000レンジで平均1.64回、256レンジで1回呼ばれています。 あなたのコードでは繰り返しごとにrand()は25回呼ばれていますが、私のコードでは1.64*2+3 = 5.3 回となっています。もちろん、この状況はおかしいので、正確に原因を突き止めなければなりません。 そこでは、さらに多くのビット演算が実行されているのですね......。

 
Nikolai Semko:

1.あなたの機能では、問題は解決されず、ただ覆い隠されるだけであることに気づいています。

2.私たちやAlexeyのバリエーションでは、これは最悪のケースですが、他の多くの状況では、時間は一定でも速度はほとんどrand()のレベルになるでしょう。

rand()はなぜ狭い範囲の数字を生成するのか、不思議に思ったことはありませんか?これは疑似乱数発生器なので周期性があり、必要のないところに乱数を大量に発生させ、その後破棄することで、その品質が劣化している(さっきの繰り返しになる)。

4.もっと複雑な方法でランダムなデータをマイニングする人もいます。私など、ネットからヤフオクに出すと、誰かが買ってくれるかもしれません。苦労して手に入れたデータを、なぜ無駄に捨てることになるのか(ulongの生成、正しいアルゴリズムの記述は我々のやり方ではない)。

 
Alexey Navoykov:

あなたのバージョンは常に5回のrand()を呼び出すのに対し、私のは2万レンジで平均1.64回、256レンジで1回という違いがあります。 あなたのrand()は各反復で25回呼び出されていますが、私のは1.64*2+3 = 5.3回となっています。もちろん、この状況はおかしいので、正確な原因を突き止めなければなりません。 なぜなら、そこでも多くのビット演算が実行されているからです......。

ビットは最も安価なオペレーションです。ほぼ無償。

でも、全体としては賛成です。私も理由がわからないのですが...。最適化の妙味かもしれませんね。そこで最適化できることはあっても...。

 
Nikolai Semko:

ビットは最も安価なオペレーションです。ほぼ無償。

でも、全体としては賛成です。私も理由がわからないのですが...。最適化の妙味かもしれませんね。最適化とはいえ...

コンパイル時にすべての値が計算されるため、算術演算の 問題ではないようです。 原因は反復回数が不明なループの存在です(反復回数は平均2回以下ですが)。 つまり、rand()の呼び出し回数が分かっているので、コードが最適化されているのでしょう。
 
Vict:

1.あなたの機能では、問題は解決されず、ただ覆い隠されるだけであることに気づいています。

2.私たちとAlexeyのバージョンでは、これは最悪のケースですが、他の多くの状況では、一定の時間を持ちながら、速度はほとんどrand()のレベルになるでしょう。

rand()はなぜ狭い範囲の数字を生成するのか、不思議に思ったことはありませんか?これは疑似乱数発生器なので、周期性があり、必要のないところで乱数を大量に発生させ、その後破棄することで、その品質が劣化している(さっきの繰り返しになる)のです。

4.もっと複雑な方法でランダムなデータをマイニングする人もいます。私など、ネットからヤフオクに出すと、誰かが買ってくれるかもしれません。それなのに、せっかく苦労して作ったデータをバカみたいに捨てて(ulongを生成して、まともなアルゴリズムを書くのは我々のやり方ではない)いいのだろうか?

まあオタクっぽいですね。
この問題が0.1%でも顕著になる状況を再現するためには、以下の値以上のレンジで動作させる必要があります。

  • (ULONG_MAX)/1000= 18446744073709551 関数 randUlong() の場合
  • (UINT_MAX)/1000= 4294967 関数 randUint() の場合

このような範囲を使ったことがありますか? では、なぜこのようなチェックやループを入れたのでしょうか?
最高のものは、良いものの敵である。

個人的には、実務で使うランダム生成の 範囲は、せいぜい2000、4000程度が限界です。rand()はこの目的のために非常によく機能します。
私のコードにそのようなオプションを挿入してください。

ulong t=GetMicrosecondCount();
   for(int i=0;i<size;i++)
     {
      X[i]=ushort(rand()%W.Width);
      Y[i]=ushort(rand()%W.Width);
      clr[i]=XRGB(rand()%256,rand()%256,rand()%256);
     }
   t=GetMicrosecondCount()-t;
   Print("Время формирования случайных массивов = "+string(t)+" микросекунд. Всего сгенерировано "+string(size*5)+" случайных чисел rand()%W.Width");
   Canvas.Erase();
   for(int i=0;i<size;i++) Canvas.PixelSet(X[i],Y[i],clr[i]);
   Canvas.Update();
   Sleep(2000);

そのため、rand()関数の「不公平感」(rand()%20000のときのような)はなく、ポイントは視覚的に均一に配置されるので、最も速く、最も効率的な関数と言えるでしょう。

プロセッサの開発者がrand()を2^15=32768に制限したのは、無意味なことではありません。バカな人たちではありません。これで実用上の問題の99%はカバーできる。
そして、「極端な」アイデアの愛好家には、十分すぎるほどのオプションがあるのです。

ulong randUlong(ulong max=ULONG_MAX){return(((ulong)rand()<<60)|((ulong)rand()<<45)|((ulong)rand()<<30)|((ulong)rand()<<15)|(ulong)rand())%max;}
ファイル:
 
Nikolai Semko:

個人的には、実務で使う乱数生成の 範囲は2000まで、最大4000までです。これには、rand()が非常によく効きます。
このようなバリアントを私のコードに挿入してください。

で、rand()関数の「不公平感」(rand()%20000のときと同様)に気づかず、ポイントも視覚的に均一になるので、かなり有効で最速の方法です。

使ってくれても構わないよ。の右辺がRAND_MAX+1(256または1024)の倍数である場合はなおさらである。

プロセッサの開発者がrand()を2^15=32768に制限したのは、無意味なことではありません。バカな人たちではありません。実務の99%をカバーしています。
そして、「枠にはまらない」アイデアが好きな人には、十分すぎるほどのバリエーションが用意されているのです。

プロセッサーの開発者は何のためにいるのですか?ジェネレーターはソフトウェアで実装されています。RAND_MAX >= 32767 で、周期が 2^32 以上であることだけが条件です。つまり、µlは「最小値」において非常にまばらな発振器を持っているのです。

そして、最も先見の明のある人は、自分自身で公正なrand()を行います(多重度がない場合)。これは、参考書でも推奨されています。

理由: