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会减慢速度?试着用移位来代替? 我怀疑计算的常数--必须立即计算(在这种情况下--定义中的移位--也必须用常数来代替)。

此外,据我所知,"question "是一个相当有争议的操作符,20年前它在C++中被检查过,有时 "question "产生的代码比通常的if操作符长得多。也许这里也是如此?

而且,我会让返回代码变成uint--如果在转换有符号和无符号值时有一些检查怎么办?

我还没有机会进行手动实验--CPU已经超负荷了......。即使是文字也是 "缓慢地 "打出来的......

 
Georgiy Merts:

除以2的速度减慢?试着用移位来代替它? 我怀疑计算的常数--必须立即计算(在这种情况下--定义中的移位--也必须用常数来代替)。

另外--"问题" --我知道,这是一个相当有争议的操作者......

用shift代替除法没有效果。 我怀疑产生的表达式太长了,所以编译器没有把它优化到最后。

但我在优化=0时进行了测试,而当优化启用时,一切都很顺利--第二个变体的速度快了1.5倍。中奖了!

如果禁用优化,那么第二个选项在小数值时稍慢,但在大数值时稍快。 总之,第二个选项肯定更好。

 
Alexey Navoykov:

这是我想出的变体。

这据说是所有可能的变体中最快的。 所有的计算都是用常数进行的,所以它们是在编译过程中计算的。 因此,一切都被减少到只有6个连续的比较,而没有其他。 然而,这个变体的工作速度比前一个慢。 我不明白这其中的原因。

这就对了--你的变体是最快的。

只是这个测试是闲置的。程序员在测试性能时经常忘记一件重要的事情:如果一个计算值没有在任何地方使用,编译器就不会进行计算。

这是有道理的,有什么意义呢?这就像在量子叠加中。如果没有人看它,为什么月亮要存在。"月亮的存在只是因为一只老鼠在看它吗?"(Albert Einstein)。:))

因此,这个版本的测试中的校验计算和打印会更正确。

#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
而第二名仍然是_FastLog2,而不是log2 :))
 
Nikolai Semko:

这只是一个闲置的测试。在性能测试中经常会忘记一个重要的问题:如果计算值没有在任何地方使用,编译器就不会进行计算。

这是有道理的,有什么意义呢?这就像在量子叠加中。如果没有人看它,为什么月亮要存在。"月亮的存在只是因为一只老鼠在看它吗?"(Albert Einstein)。:))

因此,这个版本的测试,加上校验计算和打印,将更加正确。

你的代码很纠结。 定义中使用的变量位于程序代码的另一端--在这样的混乱中进行排序并不方便。 但这不是重点。 重点是你的测试结果不能被认为是可靠的,因为编译器事先知道传入函数的值的算法。 所以它优化了你的测试。你应该用随机数进行计算

顺便说一下,为什么你的代码里有srand? 当我看到它时,起初我以为你在使用随机,但实际上你没有。

以下是我的代码。

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:

你的代码很混乱。 定义中使用的变量位于程序代码的另一端--在这样的混乱中进行分类是很不方便的。 但这不是重点,重点是你的测试结果不能被认为是可靠的,因为编译器事先知道传入函数的值的算法。 因此它优化了你的测试。你应该用随机数来 计算。

顺便说一下,为什么你的代码里有srand? 当我看到它时,起初我以为你在使用随机,但实际上你没有。

以下是我的代码。

该代码不是我的。我只是调整了一下,删除了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,这就破坏了校验和。现在我加了一个,以摆脱零。

 
Nikolai Semko:

srand(45)的所有函数

我一开始也是这么做的,但得到的校验和不同,因为我没有考虑到rand()*rand()可以为0,这就破坏了校验和。现在我加了一个,以摆脱零。

如果我们专门讨论速度测量,为什么需要同样的校验呢? 在这种情况下,和的意义只是为了防止编译器切割代码,仅此而已。 而通过做srand(45),你又允许优化测试。

顺便说一下,说到零,FastLog2不检查零,这给了它一个先机。但如果测试正确,它仍然比log2慢1.5到2倍)。

 
Alexey Navoykov:
如果我们专门讨论速度测量,为什么需要同样的校验? 在这种情况下,和的意义只是为了防止编译器切割代码,仅此而已。 而做srand(45),又可以让你优化测试。

你在这里高估了编译器的能力。删除srand(45)--校验和会有所不同,但速度结果仍然是一样的。

此外,我的指导思想是,为了实验的纯洁性,计算结果是一样的,因为我没有深入研究所有函数的细节。有时,一个函数参数的值会影响其执行的时间。

所以更有理由检查算法的正确性。