Caractéristiques du langage mql5, subtilités et techniques - page 117

 

Voici la variante que j'ai trouvée :

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

Elle est censée être la plus rapide de toutes les variantes possibles. Tous les calculs sont effectués avec des constantes, donc ils sont calculés pendant la compilation. Ainsi, tout est réduit à seulement 6 comparaisons consécutives et rien de plus. Cependant, cette variante fonctionne plus lentement que la précédente. Je ne peux pas comprendre la raison de cela.

 
Alexey Navoykov:

Voici la variante que j'ai trouvée :

Dans l'idée, c'est la plus rapide de toutes. Tous les calculs sont faits avec des constantes, donc ils sont calculés pendant la compilation. Ainsi, tout est réduit à seulement 6 comparaisons consécutives, et rien de plus. Cependant, cette variante fonctionne plus lentement que la précédente. Je n'arrive pas à comprendre la raison.

Diviser par deux le ralentit ? Essayer de remplacer par un décalage ? Je soupçonne que les constantes calculées - doivent être calculées immédiatement (dans ce cas - et le décalage dans la définition - doit également être remplacé par une constante).

En outre, "question" est un opérateur plutôt controversé, comme je le sais. Il y a vingt ans, il a été vérifié en C++ et parfois "question" génère un code beaucoup plus long que l'opérateur if habituel. Peut-être que c'est la même chose ici ?

Et, je ferais en sorte que le code de retour soit uint - et s'il y a des contrôles lors de la conversion des valeurs signées et non signées ?

Je n'ai pas encore eu l'occasion d'expérimenter manuellement - le processeur est surchargé... Même le texte est tapé "avec lenteur"...

 
Georgiy Merts:

Diviser par deux le ralentit ? Essayez de le remplacer par un décalage ? Je soupçonne que les constantes calculées - doivent être calculées immédiatement (dans ce cas - et le décalage dans la définition - doit également être remplacé par une constante).

Par ailleurs, le terme "question", comme je le sais, est un opérateur assez controversé...

Remplacer la division par shift n'a aucun effet. Je soupçonne que l'expression résultante est trop longue, et que le compilateur ne l'a pas optimisée jusqu'au bout.

Mais j'ai effectué les tests avec Optimize=0, alors que lorsque l'optimisation était activée, tout s'est bien passé - la deuxième variante était une fois et demie plus rapide. Bingo !

Si l'optimisation est désactivée, la deuxième option est légèrement plus lente pour les petites valeurs, mais légèrement plus rapide pour les grandes. En bref, la deuxième option est nettement meilleure.

 
Alexey Navoykov:

Voici la variante que j'ai trouvée :

Cette variante est censée être la plus rapide de toutes les variantes possibles. Tous les calculs sont effectués avec des constantes, c'est-à-dire qu'ils sont calculés pendant la compilation. Ainsi, tout est réduit à seulement 6 comparaisons consécutives et rien de plus. Cependant, cette variante fonctionne plus lentement que la précédente. Je ne peux pas en comprendre la raison.

C'est exact - votre variante est la plus rapide.

C'est juste que le test est au ralenti. Les programmeurs oublient très souvent une chose importante lorsqu'ils testent les performances : si une valeur calculée n'est utilisée nulle part, le compilateur n'effectuera tout simplement pas le calcul.

C'est logique, quel est l'intérêt ? C'est comme dans une superposition quantique. Pourquoi la lune devrait-elle exister si personne ne la regarde. "La lune existe-t-elle juste parce qu'une souris la regarde ?" (Albert Einstein). :))

Cette version du test avec le calcul de la somme de contrôle et son impression serait donc plus correcte :

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

Résultat :

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
Et la deuxième place est toujours _FastLog2, pas log2 :))
 
Nikolai Semko:

C'est juste un test au ralenti. Un point important est souvent oublié dans les tests de performance : si la valeur calculée n'est utilisée nulle part, le compilateur n'effectue tout simplement pas le calcul.

C'est logique, quel est l'intérêt ? C'est comme dans une superposition quantique. Pourquoi la lune devrait-elle exister si personne ne la regarde. "La lune existe-t-elle juste parce qu'une souris la regarde ?" (Albert Einstein). :))

Cette version du test avec le calcul de la somme de contrôle et son impression serait donc plus correcte :

Votre code est enchevêtré. Les variables utilisées dans la définition sont situées à l'autre bout du code du programme - il n'est pas pratique de faire le tri dans un tel chaos. Mais là n'est pas la question. La question est que les résultats de vos tests ne peuvent pas être considérés comme fiables parce que le compilateur connaît à l'avance l'algorithme des valeurs passées dans la fonction. Il optimise donc vos tests. Vous devriez calculer sur des nombres aléatoires .

Au fait, pourquoi avez-vous srand dans votre code ? Quand je l'ai vu, j'ai d'abord pensé que vous utilisiez random, mais en fait ce n'est pas le cas.

Voici mon 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:

Votre code est confus. Les variables utilisées dans la définition sont situées à l'autre bout du code du programme - il n'est pas pratique de faire le tri dans un tel chaos. Mais ce n'est pas le point, le point est que les résultats de vos tests ne peuvent pas être considérés comme fiables, parce que le compilateur connaît à l'avance l'algorithme des valeurs passées dans la fonction. Par conséquent, il optimise vos tests. Vous devriez calculer sur des nombres aléatoires .

Au fait, pourquoi avez-vous srand dans votre code ? Quand je l'ai vu, j'ai d'abord pensé que vous utilisiez random, mais ce n'est pas le cas.

Voici mon code :

le code n'est pas le mien. Je viens de le modifier et de supprimer rand pour vérifier les mêmes sommes de contrôle et supprimer la fonction rand relativement coûteuse de la boucle, mais j'ai simplement oublié de supprimer srand.

Je renvoie le rand. Vous avez raison - le compilateur optimise la boucle pour la somme des logarithmes de valeurs consécutives. Je suis surpris, cependant. Je ne comprends pas comment il fait ça. Il y a peut-être quelque chose que nous ne prenons pas en compte.

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

Résultat :

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
   

Le gagnant actuel est _FastLog2

 
Nikolai Semko:

Résultat :

Gagnant actuel _FastLog2

Je me demande comment vous avez obtenu la même somme de contrôle partout, si les valeurs sont aléatoires.

 
Alexey Navoykov:

Je me demande comment on peut obtenir la même somme de contrôle partout, si les valeurs sont aléatoires.

srand(45) pour toutes les fonctions

J'ai fait comme ça au début, mais j'ai obtenu des sommes de contrôle différentes, car je n'ai pas pris en compte le fait que rand()*rand() peut être égal à 0, ce qui casse la somme de contrôle. Maintenant, j'en ai ajouté un pour m'éloigner du zéro.

 
Nikolai Semko:

srand(45) pour toutes les fonctions

J'ai fait la même chose au début, mais j'ai obtenu des sommes de contrôle différentes, car je n'ai pas tenu compte du fait que rand()*rand() peut être 0, ce qui casse la somme de contrôle. Maintenant, j'en ai ajouté un pour m'éloigner du zéro.

Et pourquoi avez-vous besoin de la même somme de contrôle si nous parlons spécifiquement de mesures de vitesse ? L'intérêt de la somme dans ce cas est simplement d'empêcher le compilateur de couper le code, c'est tout. Et en faisant srand(45), vous permettez à nouveau d'optimiser le test.

D'ailleurs, en parlant de zéro, FastLog2 ne vérifie pas le zéro, ce qui lui donne une longueur d'avance. Mais il est toujours une fois et demie à deux fois plus lent que log2 s'il est testé correctement).

 
Alexey Navoykov:
Et pourquoi avez-vous besoin de la même somme de contrôle si nous parlons spécifiquement de mesures de vitesse ? L'intérêt de la somme dans ce cas est simplement d'empêcher le compilateur de couper le code, c'est tout. Et faire srand(45), encore une fois, vous permet d'optimiser le test.

Vous surestimez les capacités du compilateur ici. Supprimez srand(45) - les sommes de contrôle seront différentes, mais le résultat en termes de vitesse reste le même.

De plus, j'ai été guidé par le fait que les calculs étaient les mêmes pour la pureté de l'expérience, car je ne suis pas entré dans le détail de toutes les fonctions. Parfois, la valeur d'un paramètre de fonction peut affecter le temps de son exécution.

Raison de plus pour vérifier l'exactitude des algorithmes.

Raison: