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

 

これが、私が考えたバリエーションです。

int log2_(ulong n)
{
  if (n==0) return -1;

  #define  M(n, i, base, S) ( n >= (ulong(1)<<(i)) ? S(n, i+base/2) : S(n, i-(base+1)/2) )

  #define  S_0(n, i)  i
  #define  S_1(n, i)  M(n, i, 1, S_0)
  #define  S_2(n, i)  M(n, i, 2, S_1)
  #define  S_4(n, i)  M(n, i, 4, S_2)
  #define  S_8(n, i)  M(n, i, 8, S_4)
  #define  S_16(n, i) M(n, i, 16, S_8)
  #define  S(n)       M(n, 32, 32, S_16)

  return S(n); 
}

すべての計算は定数で行われ、コンパイル時に計算されます。 そのため、すべては6回の連続した比較だけになります。 しかし、この変形は前のものよりも動作が遅くなります。 その理由は何なのか理解できません。

 
Alexey Navoykov:

これが、私が考えたバリエーションです。

考えようによっては、これが最も速い。 すべての計算は定数で行われるので、コンパイル時に計算される。 そのため、すべては6つの連続した比較だけになり、それ以上はない。 しかし、この変形は前のものよりも動作が遅い。 その理由は何であるのか理解できない。

2で割ると遅くなる?シフトに置き換えてみる? 計算された定数-はすぐに計算されなければならない(この場合-とdefineのシフト-も定数に置き換えなければならない)のではないでしょうか?

それに、"question "はかなり議論のある演算子です。 20年前にC++でチェックされましたが、"question "は通常のif演算子よりはるかに長いコードを生成することがあります。もしかしたら、こちらも同じかもしれませんね。

また、戻り値をuintにするのですが、符号付きと符号無しの値を変換するときに何かチェックがあったらどうでしょうか?

まだ手動で実験する機会がないのですが、CPUに負荷がかかっているので......。テキストも「ゆっくり」入力...。

 
Georgiy Merts:

2で割ると遅くなる?計算された定数は、すぐに計算されなければならない(この場合、defineのシフトも定数に置き換えなければならない)と思われます。

また、「質問」ですが、ご存知のように、これはかなり議論を呼ぶオペレータです・・・。

除算をシフトに置き換えても効果はありません。 結果の式が長すぎるため、コンパイラが最後まで最適化しなかったのではないかと思います。

しかし、Optimize=0のときにテストを実行したのに対して、最適化を有効にしたときはすべてがうまくいき、2番目のバリアントは1.5倍速くなったのです。ビンゴ!

最適化を無効にした場合、2番目のオプションは小さな値では若干遅くなりますが、大きな値では若干速くなります。 要するに、2番目のオプションの方が断然良いのです。

 
Alexey Navoykov:

これが、私が考えたバリエーションです。

これは、すべての計算を定数で行うため、コンパイル時に計算されます。 そのため、すべては6回の連続した比較だけになります。 しかし、このバリエーションは前のものよりも動作が遅くなります。 その理由は理解できません。

そうです。あなたのバリアントは最速なのです。

ただ、テストはアイドリングストップです。計算された値がどこにも使われていない場合、コンパイラは単にその計算を実行しないだけです。

意味があるんだ、何が言いたいんだ?量子的な重ね合わせの中にあるようなものです。誰も見ていないのに、なぜ月が存在しなければならないのか。"ネズミが見ているだけで月は存在するのか?"(アルベルト・アインシュタイン)。:))

ですから、チェックサムを計算して印刷するこのバージョンのテストの方が正しいでしょう。

#property strict
#define    test(M,S,EX)        {long sum=0; uint nn=(uint)pow(10,M); ulong mss=GetMicrosecondCount(); for(uint t12=1;t12<=nn;t12++){EX;sum+=(long)n1;} \
                                Print(S+": loops="+(string)nn+" μs="+string(GetMicrosecondCount()-mss)+" Контрольная сумма="+string(sum));}

int log2(ulong n){
  if (n==0) return -1;
  #define  S(k) if (n >= (ulong(1)<<k)) { i += k;  n >>= k; }
  int i=0;  S(32);  S(16);  S(8);  S(4);  S(2);  S(1);  return i;
  #undef  S}


static const uint ulLogTable[64] = {
0, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54, 33, 42, 3, 61,
51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22, 4, 62,
57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21, 56,
45, 25, 31, 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5, 63 };

uint _FastLog2(ulong ulInput){
   ulInput |= ulInput >> 1;
   ulInput |= ulInput >> 2;
   ulInput |= ulInput >> 4;
   ulInput |= ulInput >> 8;
   ulInput |= ulInput >> 16;
   ulInput |= ulInput >> 32;  
   return(ulLogTable[(uint)((ulInput * 0x03f6eaf2cd271461) >> 58)]);};
   
int log2_(ulong n)
{
  if (n==0) return -1;

  #define  M(n, i, base, S) ( n >= (ulong(1)<<(i)) ? S(n, i+base/2) : S(n, i-(base+1)/2) )

  #define  S_0(n, i)  i
  #define  S_1(n, i)  M(n, i, 1, S_0)
  #define  S_2(n, i)  M(n, i, 2, S_1)
  #define  S_4(n, i)  M(n, i, 4, S_2)
  #define  S_8(n, i)  M(n, i, 8, S_4)
  #define  S_16(n, i) M(n, i, 16, S_8)
  #define  S(n)       M(n, 32, 32, S_16)

  return S(n); 
}

void OnStart(){
  srand(GetTickCount());
  ulong n1;
  test(8,"MathLog",n1=ulong(MathLog((double)t12)/MathLog(2.0)))
  test(8,"log2",n1=log2(t12))
  test(8,"log2_",n1=log2_(t12))
  test(8,"_FastLog2",n1=_FastLog2(t12))}

結果

2019.01.05 02:30:03.681 TestLog (.BrentCrud,H4) MathLog:   loops=100000000 μs=805196 Контрольная сумма=2465782300
2019.01.05 02:30:04.092 TestLog (.BrentCrud,H4) log2:      loops=100000000 μs=410657 Контрольная сумма=2465782300
2019.01.05 02:30:04.234 TestLog (.BrentCrud,H4) log2_:     loops=100000000 μs=141975 Контрольная сумма=2465782300
2019.01.05 02:30:04.432 TestLog (.BrentCrud,H4) _FastLog2: loops=100000000 μs=198015 Контрольная сумма=2465782300
そして2位はやはりlog2ではなく_FastLog2です :))
 
Nikolai Semko:

あくまでアイドル的なテストです。性能テストでは、重要なポイントが忘れられがちです。計算された値がどこにも使われない場合、コンパイラは単に計算を実行しないのです。

意味があるんだ、何が言いたいんだ?量子的な重ね合わせの中にあるようなものです。誰も見ていないのに、なぜ月が存在しなければならないのか。"ネズミが見ているだけで月は存在するのか?"(アルベルト・アインシュタイン)。:))

ですから、チェックサムを計算して印刷するこのバージョンのテストの方が正しいでしょう。

defineで使われる変数がプログラムの端にあるなど、コードがこんがらがっていて、整理するのが大変です。 でも、そんなことより、関数に渡される値のアルゴリズムをコンパイラがあらかじめ知っているので、テストの結果が信頼できるとは思えません。 だから、テストを最適化するのです。乱数で計算 すればいいじゃないですか。

ところで、なぜコードに srand が入っているのですか? これを見たとき、最初は random を使っているのかと思ったのですが、実は違うんですね。

以下は私のコードです。

void OnStart()
{
  Print("OnStart");
  srand(GetTickCount());
  int count= 50000000;
   
  #define  TEST(func) { \ 
    ulong sum=0; \
    ulong mcscount= GetMicrosecondCount(); \
    for (int i=0; i<count; i++) \
      sum += func(rand() | rand()<<15); \
    Print("Result "+#func+":  sum=",sum,"  time=",(GetMicrosecondCount()-mcscount)/1000," ms"); \    
  }
  
   TEST(log2);
  TEST(log2_);
}
 
Alexey Navoykov:

あなたのコードは混乱しています。 defineで使用されている変数は、プログラムコードの別の端にあり、このようなカオスを整理するのは不便です。 しかし、これはポイントではありません。ポイントは、あなたのテスト結果が信頼できるとは考えられないことです。 コンパイラは、関数に渡される値のアルゴリズムを事前に知っています。 したがって、テストは最適化されます。あなたは乱数で計算すべきな のです。

ところで、なぜコードに srand が入っているのですか? これを見たとき、最初は random を使っているのかと思ったのですが、実は違うんですね。

以下は私のコードです。

は、私のコードではありません。私はちょうどそれを微調整し、同じチェックサムをチェックし、ループから比較的高価なrand関数を削除するためにrandを削除しましたが、私は単にsrandを削除するのを忘れていました。

ランドを返します。その通りです。コンパイラは、連続した値からの対数の和に対するループを最適化します。でも、驚きましたね。どうしてそんなことができるのか、理解できません。もしかしたら、私たちが考慮していないことがあるのかもしれません。

#property strict
#define    test(M,S,EX)        {srand(45); long sum=0; uint nn=(uint)pow(10,M); ulong mss=GetMicrosecondCount(); for(uint t12=1;t12<=nn;t12++){EX;sum+=(long)n1;} \
                                Print(S+": loops="+(string)nn+" μs="+string(GetMicrosecondCount()-mss)+" Контрольная сумма="+string(sum));}

int log2(ulong n){
  if (n==0) return -1;
  #define  S(k) if (n >= (ulong(1)<<k)) { i += k;  n >>= k; }
  int i=0;  S(32);  S(16);  S(8);  S(4);  S(2);  S(1);  return i;
  #undef  S}


static const uint ulLogTable[64] = {
0, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54, 33, 42, 3, 61,
51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22, 4, 62,
57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21, 56,
45, 25, 31, 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5, 63 };

uint _FastLog2(ulong ulInput){
   ulInput |= ulInput >> 1;
   ulInput |= ulInput >> 2;
   ulInput |= ulInput >> 4;
   ulInput |= ulInput >> 8;
   ulInput |= ulInput >> 16;
   ulInput |= ulInput >> 32;  
   return(ulLogTable[(uint)((ulInput * 0x03f6eaf2cd271461) >> 58)]);};
   
int log2_(ulong n)
{
  if (n==0) return -1;

  #define  M(n, i, base, S) ( n >= (ulong(1)<<(i)) ? S(n, i+base/2) : S(n, i-(base+1)/2) )

  #define  S_0(n, i)  i
  #define  S_1(n, i)  M(n, i, 1, S_0)
  #define  S_2(n, i)  M(n, i, 2, S_1)
  #define  S_4(n, i)  M(n, i, 4, S_2)
  #define  S_8(n, i)  M(n, i, 8, S_4)
  #define  S_16(n, i) M(n, i, 16, S_8)
  #define  S(n)       M(n, 32, 32, S_16)

  return S(n); 
}

void OnStart(){
  ulong n1,n;
  test(8,"MathLog",n=(rand()+1)*(rand()+1);n1=ulong(MathLog((double)n)/MathLog(2.0)))
  test(8,"log2",n=(rand()+1)*(rand()+1);n1=log2(n))
  test(8,"log2_",n=(rand()+1)*(rand()+1);n1=log2_(n))
  test(8,"_FastLog2",n=(rand()+1)*(rand()+1);n1=_FastLog2(n))}

結果

2019.01.05 04:10:25.808 TestLog (EURUSD,H1)     MathLog:   loops=100000000 μs=1168737 Контрольная сумма=2661391201
2019.01.05 04:10:26.474 TestLog (EURUSD,H1)     log2:      loops=100000000 μs=665631  Контрольная сумма=2661391201
2019.01.05 04:10:27.315 TestLog (EURUSD,H1)     log2_:     loops=100000000 μs=841299  Контрольная сумма=2661391201
2019.01.05 04:10:27.694 TestLog (EURUSD,H1)    _FastLog2:  loops=100000000 μs=378627   Контрольная сумма=2661391201
   

現在の優勝は_FastLog2です。

 
Nikolai Semko:

結果

現在の勝者 _FastLog2

値がランダムなのに、どうしてどこでも同じチェックサムになったのか不思議です。

 
Alexey Navoykov:

値がランダムなのに、どうやってどこでも同じチェックサムを得るのか不思議です。

全ての関数に対してsrand(45)

私も最初はそのようにしただけなのですが、rand()*rand()が0になることがあり、チェックサムが壊れることを考慮していなかったため、異なるチェックサムが得られました。今度はゼロから離れるために1つ追加しました。

 
Nikolai Semko:

全ての関数に対してsrand(45)

最初は同じようにやっただけなのに、違うチェックサムが出たのは、rand()*rand()が0になることがあり、チェックサムが壊れることを考慮に入れていなかったからです。今度はゼロから離れるために1つ追加しました。

また、速度測定に特化した話であれば、なぜ同じチェックサムが必要なのでしょうか? この場合のサムのポイントは、単にコンパイラがコードをカットするのを防ぐこと、それだけです。 そして srand(45) を行うことで、再びテストの最適化が可能になるのです。

ちなみに、ゼロといえば、FastLog2はゼロをチェックしないので、頭一つ抜けています。それでも正しくテストすればlog2より1.5倍から2倍は遅い)。

 
Alexey Navoykov:
また、特に速度測定について話しているのであれば、なぜ同じチェックサムが必要なのでしょうか? この場合のサムのポイントは、単にコンパイラがコードをカットするのを防ぐこと、それだけです。 そして、srand(45)を行うことで、やはりテストを最適化することができるのです。

あなたはここで、コンパイラの能力を過大評価しすぎています。srand(45) を削除 - チェックサムは異なりますが、スピードの結果は同じです。

しかも、すべての関数の詳細に踏み込まないため、実験の純度を高めるために計算が同じになるように誘導したのです。関数のパラメータの値が、その実行時間に影響を与えることがあります。

それだけに、アルゴリズムの正しさをチェックする必要があります。

理由: