Si vous remplacez
static const double Points[] = {1.0 e-0, 1.0 e-1, 1.0 e-2, 1.0 e-3, 1.0 e-4, 1.0 e-5, 1.0 e-6, 1.0 e-7, 1.0 e-8};
à la variante du commutateur, vous pouvez voir la qualité de l'implémentation du commutateur en chiffres.
Considérez la version nettoyée du script avec NormalizeDouble :
#define EPSILON (1.0 e-7 + 1.0 e-13) #define HALF_PLUS (0.5 + EPSILON) //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double MyNormalizeDouble(const double Value,const int digits) { static const double Points[]={1.0 e-0,1.0 e-1,1.0 e-2,1.0 e-3,1.0 e-4,1.0 e-5,1.0 e-6,1.0 e-7,1.0 e-8}; return((int)((Value > 0) ? Value / Points[digits] + HALF_PLUS : Value / Points[digits] - HALF_PLUS) * Points[digits]); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ ulong BenchStandard(const int Amount=1.0 e8) { double Price=1.23456; const double point=0.00001; const ulong StartTime=GetMicrosecondCount(); //--- for(int i=0; i<Amount;i++) { Price=NormalizeDouble(Price+point,5); } Print("Result: ",Price); // специально выводим результат, чтобы цикл не оптимизировался в ноль //--- return(GetMicrosecondCount() - StartTime); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ ulong BenchCustom(const int Amount=1.0 e8) { double Price=1.23456; const double point=0.00001; const ulong StartTime=GetMicrosecondCount(); //--- for(int i=0; i<Amount;i++) { Price=MyNormalizeDouble(Price+point,5); } Print("Result: ",Price); // специально выводим результат, чтобы цикл не оптимизировался в ноль //--- return(GetMicrosecondCount() - StartTime); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart(void) { Print("Standard: ",BenchStandard()," msc"); Print("Custom: ",BenchCustom(), " msc"); }
Résultats :
Custom: 1110255 msc Result: 1001.23456 Standard: 1684165 msc Result: 1001.23456
Remarques et explications immédiates :
- static est nécessaire ici pour que le compilateur prenne ce tableau en dehors de la fonction et ne le construise pas sur la pile à chaque fois que la fonction est appelée. Le compilateur C++ fait de même.
static const double Points
- Pour éviter que le compilateur ne rejette la boucle parce qu'elle est inutile, nous devons utiliser les résultats des calculs. Par exemple, imprimer la variable Prix.
- Il y a une erreur dans votre fonction - les limites des chiffres ne sont pas vérifiées, ce qui peut facilement conduire à des dépassements de tableau.
Par exemple, appelez-le comme MyNormalizeDouble(Price+point,10) et attrapez l'erreur :array out of range in 'BenchNormalizeDouble.mq5' (19,45)
La méthode consistant à accélérer en ne vérifiant pas est acceptable, mais pas dans notre cas. Nous devons gérer toute entrée de données erronées. - Pour simplifier le code, remplacez le type de la variable digits par uint, afin d'effectuer une seule comparaison pour >8 au lieu d'une condition supplémentaire <0
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double MyNormalizeDouble(const double Value,uint digits) { static const double Points[]={1.0 e-0,1.0 e-1,1.0 e-2,1.0 e-3,1.0 e-4,1.0 e-5,1.0 e-6,1.0 e-7,1.0 e-8}; //--- if(digits>8) digits=8; //--- return((int)((Value > 0) ? Value / Points[digits] + HALF_PLUS : Value / Points[digits] - HALF_PLUS) * Points[digits]); }
- Exécutons le code et... Nous sommes surpris !
Custom: 1099705 msc Result: 1001.23456 Standard: 1695662 msc Result: 1001.23456
Votre code a dépassé encore plus la fonction standard NormalizeDouble!
De plus, l'ajout de la condition réduit même le temps (en fait, il se situe dans la marge d'erreur). Pourquoi y a-t-il une telle différence de vitesse ? - Tout ceci est lié à une erreur standard des testeurs de performance.
Lorsque vous écrivez des tests, vous devez garder à l'esprit la liste complète des optimisations qui peuvent être appliquées par le compilateur. Vous devez être clair sur les données d'entrée que vous utilisez et sur la manière dont elles seront détruites lorsque vous écrivez un test type simplifié.
Évaluons et appliquons l'ensemble des optimisations effectuées par notre compilateur, étape par étape. - Commençons par la propagation constante - c'est l'une des erreurs importantes que vous avez commises dans ce test.
La moitié de vos données d'entrée sont des constantes. Réécrivons l'exemple en tenant compte de leur propagation.ulong BenchStandard(void) { double Price=1.23456; const ulong StartTime=GetMicrosecondCount(); //--- for(int i=0; i<1.0 e8;i++) { Price=NormalizeDouble(Price + 0.00001,5); } Print("Result: ",Price); //--- return(GetMicrosecondCount() - StartTime); } ulong BenchCustom(void) { double Price=1.23456; const ulong StartTime=GetMicrosecondCount(); //--- for(int i=0; i<1.0 e8;i++) { Price=MyNormalizeDouble(Price + 0.00001,5); } Print("Result: ",Price," ",1.0 e8); //--- return(GetMicrosecondCount() - StartTime); }
Après l'avoir lancé, rien n'a changé - il doit en être ainsi. - Continuez - inlinez votre code (notre NormalizeDouble ne peut pas être inline).
C'est ce que votre fonction deviendra en réalité après l'inelining. Économie sur les appels, économie sur les recherches de tableaux, les contrôles sont supprimés grâce à une analyse constante :ulong BenchCustom(void) { double Price=1.23456; const ulong StartTime=GetMicrosecondCount(); //--- for(int i=0; i<1.0 e8;i++) { //--- этот код полностью вырезается, так как у нас заведомо константа 5 //if(digits>8) // digits=8; //--- распространяем переменные и активно заменяем константы if((Price+0.00001)>0) Price=int((Price+0.00001)/1.0 e-5+(0.5+1.0 e-7+1.0 e-13))*1.0 e-5; else Price=int((Price+0.00001)/1.0 e-5-(0.5+1.0 e-7+1.0 e-13))*1.0 e-5; } Print("Result: ",Price); //--- return(GetMicrosecondCount() - StartTime); }
Je n'ai pas résumé les constantes pures pour gagner du temps. Elles sont toutes garanties de s'effondrer au moment de la compilation.
Exécutez le code et obtenez le même temps que dans la version originale :Custom: 1149536 msc Standard: 1767592 msc
ne faites pas attention à la gigue dans les chiffres - au niveau des microsecondes, l'erreur de minuterie et la charge flottante sur l'ordinateur, cela reste dans les limites normales. la proportion est entièrement maintenue. - Regardez le code que vous avez commencé à tester en raison des données sources fixes.
Comme le compilateur dispose d'une optimisation très puissante, votre tâche a été effectivement simplifiée. - Alors comment tester les performances ?
En comprenant comment le compilateur fonctionne, vous devez l'empêcher d'appliquer des pré-optimisations et des simplifications.
Par exemple, faisons du paramètre "chiffres" une variable :#define EPSILON (1.0 e-7 + 1.0 e-13) #define HALF_PLUS (0.5 + EPSILON) //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double MyNormalizeDouble(const double Value,uint digits) { static const double Points[]={1.0 e-0,1.0 e-1,1.0 e-2,1.0 e-3,1.0 e-4,1.0 e-5,1.0 e-6,1.0 e-7,1.0 e-8}; //--- if(digits>8) digits=8; //--- return((int)((Value > 0) ? Value / Points[digits] + HALF_PLUS : Value / Points[digits] - HALF_PLUS) * Points[digits]); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ ulong BenchStandard(const int Amount=1.0 e8) { double Price=1.23456; const double point=0.00001; const ulong StartTime=GetMicrosecondCount(); //--- for(int i=0; i<Amount;i++) { Price=NormalizeDouble(Price+point,2+(i&15)); } Print("Result: ",Price); // специально выводим результат, чтобы цикл не оптимизировался в ноль //--- return(GetMicrosecondCount() - StartTime); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ ulong BenchCustom(const int Amount=1.0 e8) { double Price=1.23456; const double point=0.00001; const ulong StartTime=GetMicrosecondCount(); //--- for(int i=0; i<Amount;i++) { Price=MyNormalizeDouble(Price+point,2+(i&15)); } Print("Result: ",Price); // специально выводим результат, чтобы цикл не оптимизировался в ноль //--- return(GetMicrosecondCount() - StartTime); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart(void) { Print("Standard: ",BenchStandard()," msc"); Print("Custom: ",BenchCustom()," msc"); }
Exécutez-le et... nous obtenons le même résultat de vitesse que précédemment.
Votre code gagne environ 35% comme avant. - Pourquoi en est-il ainsi ?
Nous ne pouvons toujours pas nous épargner l'optimisation due à l'inlining. Économiser 100 000 000 d'appels en faisant passer les données par la pile dans notre fonction NormalizeDouble, dont l'implémentation est similaire, pourrait bien donner le même gain de vitesse.
Il y a un autre soupçon que notre NormalizeDouble n'a pas été implémenté dans le mécanisme d'appel direct lors du chargement de la table de relocalisation des fonctions dans le programme MQL5.
Nous le vérifierons demain matin et si c'est le cas, nous le déplacerons vers direct_call et vérifierons à nouveau la vitesse.
Voici une étude de NormalizeDouble.
Notre compilateur MQL5 a battu notre fonction système, ce qui montre son adéquation par rapport à la vitesse du code C++.
Si vous remplacez
à la variante du commutateur, vous pouvez voir la qualité de l'implémentation du commutateur en chiffres.
Vous confondez accès direct indexé à un tableau statique par un index constant (qui dégénère en constante à partir d'un champ) et switch.
La Switch ne peut pas vraiment rivaliser avec un tel boîtier. Switch possède plusieurs optimisations de la forme fréquemment utilisées :
- "les valeurs notoirement ordonnées et courtes sont placées dans un tableau statique et indexées" - le plus simple et le plus rapide, peut concurrencer le tableau statique, mais pas toujours
- "plusieurs tableaux par morceaux ordonnés et rapprochés de valeurs avec contrôle des limites de zones" - ceci a déjà un frein
- "nous vérifions trop peu de valeurs par le biais de if" - pas de vitesse, mais c'est la faute du programmeur, il utilise le switch de manière inappropriée
- "tableau ordonné très clairsemé avec recherche binaire" - très lent dans le pire des cas
En fait, la meilleure stratégie pour le switch est celle où le développeur a délibérément essayé de faire un ensemble compact de valeurs dans l'ensemble inférieur de nombres.
Considérez la version nettoyée du script avec NormalizeDouble :
Résultats :
Remarques et explications immédiates :
- static est nécessaire ici pour que le compilateur place ce tableau en dehors de la fonction et ne le construise pas sur la pile à chaque appel de fonction. Le compilateur C++ fait la même chose.
- Pour éviter que le compilateur ne rejette la boucle en raison de son inutilité, nous devons utiliser les résultats des calculs. Par exemple, imprimer la variable Prix.
- Il y a une erreur dans votre fonction qui ne vérifie pas les limites des chiffres, ce qui peut facilement conduire à des dépassements de tableau.
Par exemple, appelez-le comme MyNormalizeDouble(Price+point,10) et attrapez l'erreur :
La méthode consistant à accélérer en ne vérifiant pas est acceptable, mais pas dans notre cas. Nous devons gérer toute entrée de données erronées. - Pour simplifier le code, remplaçons le type de la variable digits par uint, afin d'effectuer une comparaison pour >8 au lieu d'une condition supplémentaire <0
double MyNormalizeDouble( const double Value, const uint digits ) { static const double Points[] = {1.0 e-0, 1.0 e-1, 1.0 e-2, 1.0 e-3, 1.0 e-4, 1.0 e-5, 1.0 e-6, 1.0 e-7, 1.0 e-8}; const double point = digits > 8 ? 1.0 e-8 : Points[digits]; return((int)((Value > 0) ? Value / point + HALF_PLUS : Value / point - HALF_PLUS) * point); }
- Il s'agit d'une erreur standard des testeurs de performance.
Lorsque nous écrivons des tests, nous devons garder à l'esprit la liste complète des optimisations qui peuvent être appliquées par le compilateur. Vous devez être clair sur les données d'entrée que vous utilisez et sur la manière dont elles seront détruites lorsque vous écrivez un test type simplifié. - Alors comment tester les performances ?
En comprenant comment le compilateur fonctionne, vous devez l'empêcher d'appliquer des pré-optimisations et des simplifications.
Par exemple, faisons du paramètre "chiffres" une variable :
C'est l'étude NormalizeDouble.
Notre compilateur MQL5 a battu notre fonction système, ce qui montre son adéquation par rapport à la vitesse du code C++.
Vous confondez accès direct indexé à un tableau statique par un index constant (qui dégénère en constante à partir d'un champ) et switch.
La Switch ne peut pas vraiment rivaliser avec un tel boîtier. Switch possède quelques optimisations de ce type couramment utilisées :
- La solution "les valeurs délibérément ordonnées et courtes sont placées dans un tableau statique et indexées par switch" est la plus simple et la plus rapide, et peut rivaliser avec un tableau statique, mais pas toujours.
C'est justement un tel cas de commande.
En fait, la meilleure stratégie pour le switch est celle où le développeur a délibérément essayé de faire un ensemble compact de valeurs dans l'ensemble inférieur de chiffres.
Voici un tel cas d'ordre.
Je l'ai essayé sur un système 32 bits. Là, le remplacement de l'interrupteur dans l'exemple ci-dessus a provoqué un freinage grave. Je ne l'ai pas vérifié sur la nouvelle machine.
Il y a en fait deux programmes compilés dans chaque MQL5 : un simplifié pour 32 bits et un optimisé au maximum pour 64 bits. Dans MT5 32 bits, le nouvel optimiseur ne s'applique pas du tout et le code pour les opérations 32 bits est aussi simple que MQL4 dans MT4.
Toute l'efficacité du compilateur qui peut générer un code dix fois plus rapide uniquement lorsqu'il est exécuté dans la version 64 bits de MT5 : https://www.mql5.com/ru/forum/58241.
Nous sommes entièrement concentrés sur les versions 64 bits de la plate-forme.
- avis : 8
- www.mql5.com
Au sujet de NormalizeDouble, il y a cette absurdité
Forum sur le trading, les systèmes de trading automatisés et les tests de stratégie
Comment faire pour passer une énumération de manière cohérente ?
fxsaber, 2016.08.26 16:08
Il y a cette note dans la description de la fonction
Ceci n'est vrai que pour les symboles qui ont un échelon de prix minimum 10^N, où N est un nombre entier et non positif. Si le pas de prix minimum a une valeur différente, alors la normalisation des niveaux de prix avant l'envoi de l'ordre est une opération sans signification qui renverra un faux ordre dans la plupart des cas.
NormalizeDouble est complètement discrédité. Non seulement la mise en œuvre est lente, mais elle n'a aucun sens sur les symboles d'échange multiples (par exemple RTS, MIX, etc.).
double CTrade::CheckVolume(const string symbol,double volume,double price,ENUM_ORDER_TYPE order_type) { //--- check if(order_type!=ORDER_TYPE_BUY && order_type!=ORDER_TYPE_SELL) return(0.0); double free_margin=AccountInfoDouble(ACCOUNT_FREEMARGIN); if(free_margin<=0.0) return(0.0); //--- clean ClearStructures(); //--- setting request m_request.action=TRADE_ACTION_DEAL; m_request.symbol=symbol; m_request.volume=volume; m_request.type =order_type; m_request.price =price; //--- action and return the result if(!::OrderCheck(m_request,m_check_result) && m_check_result.margin_free<0.0) { double coeff=free_margin/(free_margin-m_check_result.margin_free); double lots=NormalizeDouble(volume*coeff,2); if(lots<volume) { //--- normalize and check limits double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP); if(stepvol>0.0) volume=stepvol*(MathFloor(lots/stepvol)-1); //--- double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN); if(volume<minvol) volume=0.0; } } return(volume); }
Eh bien, vous ne pouvez pas le faire maladroitement ! Il pourrait être plusieurs fois plus rapide, en oubliant NormalizeDouble.
double NormalizePrice( const double dPrice, double dPoint = 0 ) { if (dPoint == 0) dPoint = ::SymbolInfoDouble(::Symbol(), SYMBOL_TRADE_TICK_SIZE); return((int)((dPrice > 0) ? dPrice / dPoint + HALF_PLUS : dPrice / dPoint - HALF_PLUS) * dPoint); }
Et pour le même volume alors faire
volume = NormalizePrice(volume, stepvol);
Pour les prix, faire
NormalizePrice(Price, TickSize)
Il semble correct d'ajouter quelque chose de similaire pour surcharger la norme NormalizeDouble. Où le second paramètre "digits" sera un double au lieu d'un int.
En 2016, la plupart des compilateurs C++ sont arrivés aux mêmes niveaux d'optimisation.
MSVC suscite des interrogations quant aux améliorations apportées à chaque mise à jour, et Intel C++ en tant que compilateur a fusionné - il n'a jamais vraiment été guéri de son "erreur interne" sur les grands projets.
Une autre de nos améliorations du compilateur dans la version 1400 est qu'il est plus rapide pour compiler des projets complexes.
Sur le sujet. Vous devez créer des alternatives aux fonctions standard, car elles donnent parfois des résultats erronés. Voici un exemple d'alternative à SymbolInfoTick
// Получение тика, который на самом деле вызвал крайнее событие NewTick bool MySymbolInfoTick( const string Symb, MqlTick &Tick, const uint Type = COPY_TICKS_ALL ) { MqlTick Ticks[]; const int Amount = ::CopyTicks(Symb, Ticks, Type, 0, 1); const bool Res = (Amount > 0); if (Res) Tick = Ticks[Amount - 1]; return(Res); } // Возвращает в точности то, что SymbolInfoTick bool CloneSymbolInfoTick( const string Symb, MqlTick &Tick ) { MqlTick TickAll, TickTrade, TickInfo; const bool Res = (MySymbolInfoTick(Symb, TickAll) && MySymbolInfoTick(Symb, TickTrade, COPY_TICKS_TRADE) && MySymbolInfoTick(Symb, TickInfo, COPY_TICKS_INFO)); if (Res) { Tick = TickInfo; Tick.time = TickAll.time; Tick.time_msc = TickAll.time_msc; Tick.flags = TickAll.flags; Tick.last = TickTrade.last; Tick.volume = TickTrade.volume; } return(Res); }
Vous pouvez appeler SymbolInfoTick sur chaque événement NewTick dans le testeur et additionner le champ volume pour connaître la rotation des actions. Mais non, vous ne pouvez pas ! Nous devons créer un MySymbolInfoDouble beaucoup plus logique.
Au sujet de NormalizeDouble, il y a cette absurdité
Eh bien, il ne peut pas être si maladroit ! Il peut être plusieurs fois plus rapide en oubliant NormalizeDouble.
Et pour le même volume, faites
Pour les prix, faire
Il semble correct d'ajouter quelque chose comme ceci en tant que surcharge à la norme NormalizeDouble. Où le second paramètre "digits" sera un double au lieu d'un int.
Vous pouvez optimiser tout ce qui l'entoure.
C'est un processus sans fin. Mais dans 99% des cas, c'est économiquement non rentable.
- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation
NormalizeDouble
Le résultat est de 1123275 et 1666643 en faveur de MyNormalizeDouble (Optimize=1). Sans optimisation, il est quatre fois plus rapide (en mémoire).