Merkmale der Sprache mql5, Feinheiten und Techniken - Seite 117

 

Dies ist die Variante, die ich mir ausgedacht habe:

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); 
}

Es soll die schnellste aller möglichen Varianten sein. Alle Berechnungen werden mit Konstanten durchgeführt, d.h. sie werden während der Kompilierung berechnet. So reduziert sich alles auf nur 6 aufeinanderfolgende Vergleiche und nichts weiter. Allerdings arbeitet diese Variante langsamer als die vorherige. Ich kann nicht verstehen, was der Grund dafür ist.

 
Alexey Navoykov:

Dies ist die Variante, die ich mir ausgedacht habe:

Im Prinzip ist dies die schnellste aller möglichen Varianten. Alle Berechnungen werden mit Konstanten durchgeführt, d.h. sie werden während der Kompilierung berechnet. So wird alles auf nur 6 aufeinanderfolgende Vergleiche reduziert, und nichts mehr. Allerdings arbeitet diese Variante langsamer als die vorherige. Ich kann nicht verstehen, was der Grund dafür ist.

Die Division durch zwei verlangsamt es? Ich vermute, dass die berechneten Konstanten - sofort berechnet werden müssen (in diesem Fall - und die Verschiebung in der Definition - muss auch durch eine Konstante ersetzt werden).

Außerdem ist "question" ein ziemlich umstrittener Operator, wie ich weiß. Vor zwanzig Jahren wurde er in C++ geprüft und manchmal erzeugt "question" viel längeren Code als der übliche if-Operator. Vielleicht ist es hier auch so?

Und ich würde den Rückgabecode uint machen - was, wenn es einige Prüfungen bei der Konvertierung von Werten mit und ohne Vorzeichen gibt?

Ich habe noch keine Gelegenheit gehabt, manuell zu experimentieren - die CPU ist stark überlastet... Selbst Text wird "langsam" getippt...

 
Georgiy Merts:

Das Dividieren durch zwei verlangsamt die ? Ich vermute, dass die berechneten Konstanten - sofort berechnet werden müssen (in diesem Fall - und die Verschiebung in der Definition - muss auch durch eine Konstante ersetzt werden).

Auch - "Frage" - wie ich weiß, ist es ein ziemlich umstrittener Operator ...

Ich vermute, dass der resultierende Ausdruck zu lang ist, so dass der Compiler ihn nicht bis zum Ende optimiert hat.

Aber ich habe die Tests mit Optimize=0 durchgeführt, während bei aktivierter Optimierung alles gut lief - die zweite Variante war eineinhalb Mal schneller. Bingo!

Wenn die Optimierung deaktiviert ist, ist die zweite Option bei kleinen Werten etwas langsamer, aber bei größeren Werten etwas schneller. Kurz gesagt, die zweite Option ist definitiv besser.

 
Alexey Navoykov:

Dies ist die Variante, die ich mir ausgedacht habe:

Dies ist angeblich die schnellste aller möglichen Varianten. Alle Berechnungen werden mit Konstanten durchgeführt, d.h. sie werden während der Kompilierung berechnet. So reduziert sich alles auf nur 6 aufeinanderfolgende Vergleiche und nichts mehr. Allerdings arbeitet diese Variante langsamer als die vorherige. Ich kann den Grund dafür nicht verstehen.

Das ist richtig - Ihre Variante ist die schnellste.

Es ist nur so, dass der Test untätig ist. Programmierer vergessen sehr oft einen wichtigen Punkt beim Testen der Leistung: Wenn ein berechneter Wert nirgendwo verwendet wird, führt der Compiler die Berechnung einfach nicht aus.

Das macht Sinn, was soll das bringen? Es ist wie in einer Quantensuperposition. Warum sollte es den Mond geben, wenn ihn niemand anschaut? "Gibt es den Mond, nur weil eine Maus ihn anschaut?" (Albert Einstein). :))

Diese Version des Tests mit der Berechnung der Prüfsumme und dem Ausdruck wäre also korrekter:

#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))}

Ergebnis:

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
Und der zweite Platz ist immer noch _FastLog2, nicht log2 :))
 
Nikolai Semko:

Es ist nur ein Test im Leerlauf. Ein wichtiger Punkt wird bei Leistungstests oft vergessen: Wenn der berechnete Wert nirgendwo verwendet wird, führt der Compiler die Berechnung einfach nicht durch.

Das macht Sinn, was soll das bringen? Es ist wie in einer Quantensuperposition. Warum sollte es den Mond geben, wenn ihn niemand anschaut? "Gibt es den Mond, nur weil eine Maus ihn anschaut?" (Albert Einstein). :))

Diese Version des Tests mit der Prüfsummenberechnung und dem Ausdruck wird also korrekter sein:

Ihr Code ist unübersichtlich. Die Variablen, die im Define verwendet werden, befinden sich am anderen Ende des Programmcodes - es ist unpraktisch, ein solches Chaos zu sortieren. Aber das ist nicht der Punkt. Der Punkt ist, dass die Ergebnisse Ihrer Tests nicht als zuverlässig angesehen werden können, weil der Compiler den Algorithmus der Werte, die der Funktion übergeben werden, im Voraus kennt. Also optimiert er Ihre Tests. Sie sollten mit Zufallszahlen rechnen .

Übrigens, warum haben Sie srand in Ihrem Code? Als ich es sah, dachte ich zuerst, dass Sie random verwenden, aber das ist nicht der Fall.

Hier ist mein Code:

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:

Ihr Code ist verwirrend. Die Variablen, die im Define verwendet werden, befinden sich am anderen Ende des Programmcodes - es ist unbequem, ein solches Chaos zu sortieren. Aber das ist nicht der Punkt, der Punkt ist, dass Ihre Testergebnisse nicht als zuverlässig angesehen werden können, weil der Compiler den Algorithmus der Werte, die der Funktion übergeben werden, im Voraus kennt. Deshalb optimiert er Ihre Tests. Sie sollten mit Zufallszahlen rechnen .

Übrigens, warum haben Sie srand in Ihrem Code? Als ich es sah, dachte ich zuerst, dass Sie random verwenden, aber das ist nicht der Fall.

Hier ist mein Code:

der Code ist nicht von mir. Ich habe es gerade optimiert und rand entfernt, um die gleichen Prüfsummen zu überprüfen und die relativ teure Funktion rand aus der Schleife zu entfernen, aber ich habe einfach vergessen, srand zu entfernen.

Ich gebe Rand zurück. Sie haben Recht - der Compiler optimiert die Schleife für die Summe der Logarithmen von aufeinanderfolgenden Werten. Ich bin allerdings überrascht. Ich verstehe nicht, wie sie das macht. Vielleicht gibt es etwas, das wir nicht berücksichtigen.

#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))}

Ergebnis:

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
   

Der aktuelle Gewinner ist _FastLog2

 
Nikolai Semko:

Ergebnis:

Aktueller Gewinner _FastLog2

Ich frage mich, wie Sie überall die gleiche Prüfsumme erhalten haben, wenn die Werte zufällig sind.

 
Alexey Navoykov:

Ich frage mich, wie man überall die gleiche Prüfsumme erhält, wenn die Werte zufällig sind.

srand(45) für alle Funktionen

Ich habe es zuerst so gemacht, aber ich habe unterschiedliche Prüfsummen erhalten, weil ich nicht berücksichtigt habe, dass rand()*rand() 0 sein kann, was die Prüfsumme zerstört. Jetzt habe ich eine hinzugefügt, um von der Null wegzukommen.

 
Nikolai Semko:

srand(45) für alle Funktionen

Ich habe zunächst dasselbe getan, aber unterschiedliche Prüfsummen erhalten, weil ich nicht berücksichtigt habe, dass rand()*rand() 0 sein kann, was die Prüfsumme bricht. Jetzt habe ich eine hinzugefügt, um von der Null wegzukommen.

Und warum braucht man die gleiche Prüfsumme, wenn es um Geschwindigkeitsmessungen geht? Der Sinn der Summe ist in diesem Fall einfach, den Compiler daran zu hindern, den Code zu kürzen, das ist alles. Und indem man srand(45) macht, kann man den Test wieder optimieren.

Apropos Null: FastLog2 prüft nicht auf Null, was ihm einen Vorsprung verschafft. Aber es ist immer noch eineinhalb bis zwei Mal langsamer als log2, wenn es richtig getestet wurde).

 
Alexey Navoykov:
Und warum brauchen Sie die gleiche Prüfsumme, wenn es um Geschwindigkeitsmessungen geht? Der Sinn der Summe ist in diesem Fall einfach, den Compiler daran zu hindern, den Code zu kürzen, das ist alles. Und mit srand(45) können Sie den Test wiederum optimieren.

Sie überschätzen hier die Fähigkeiten des Compilers. Entfernen Sie srand(45) - die Prüfsummen werden unterschiedlich sein, aber das Geschwindigkeitsergebnis bleibt das gleiche.

Außerdem habe ich mich von der Tatsache leiten lassen, dass die Berechnungen aus Gründen der Reinheit des Experiments gleich waren, weil ich nicht auf alle Funktionen im Detail eingegangen bin. Manchmal kann der Wert eines Funktionsparameters den Zeitpunkt der Ausführung beeinflussen.

Umso wichtiger ist es, die Korrektheit der Algorithmen zu überprüfen.