Wenn Sie die
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};
auf die Switch-Variante, können Sie die Qualität der Switch-Implementierung in Zahlen sehen.
Betrachten Sie die bereinigte Version des Skripts mit 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"); }
Ergebnisse:
Custom: 1110255 msc Result: 1001.23456 Standard: 1684165 msc Result: 1001.23456
Unmittelbare Bemerkungen und Erklärungen:
- static ist hier notwendig, damit der Compiler dieses Array außerhalb der Funktion nimmt und es nicht bei jedem Funktionsaufruf auf dem Stack aufbaut. Der C++-Compiler tut dasselbe.
static const double Points
- Um zu verhindern, dass der Compiler die Schleife verwirft, weil sie nutzlos ist, sollten wir die Ergebnisse der Berechnungen verwenden. Drucken Sie zum Beispiel die Variable Preis.
- Es gibt einen Fehler in Ihrer Funktion - die Grenzen der Ziffern werden nicht überprüft, was leicht zu Array-Überläufen führen kann.
Rufen Sie es zum Beispiel als MyNormalizeDouble(Price+point,10) auf und fangen Sie den Fehler ab:array out of range in 'BenchNormalizeDouble.mq5' (19,45)
Die Methode der Beschleunigung durch Nichtkontrolle ist akzeptabel, aber nicht in unserem Fall. Wir müssen jede fehlerhafte Dateneingabe behandeln. - Fügen wir eine einfache Bedingung für einen Index größer als 8 hinzu. Um den Code zu vereinfachen, ersetzen wir den Typ der Variablen digits durch uint, um einen Vergleich für >8 anstelle einer zusätzlichen Bedingung <0 durchzuführen
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ 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]); }
- Führen wir den Code aus und... Wir sind überrascht!
Custom: 1099705 msc Result: 1001.23456 Standard: 1695662 msc Result: 1001.23456
Ihr Code hat die Standardfunktion NormalizeDouble noch mehr überholt!
Außerdem verkürzt sich durch die Hinzufügung der Bedingung sogar die Zeit (sie liegt sogar innerhalb der Fehlermarge). Warum gibt es einen solchen Geschwindigkeitsunterschied? - All dies hat mit einem Standardfehler von Leistungstestern zu tun.
Beim Schreiben von Tests sollten Sie die vollständige Liste der Optimierungen im Auge behalten, die vom Compiler angewendet werden können. Sie müssen sich darüber im Klaren sein, welche Eingabedaten Sie verwenden und wie diese zerstört werden , wenn Sie einen vereinfachten Mustertest schreiben.
Lassen Sie uns die gesamte Palette der Optimierungen, die unser Compiler vornimmt, Schritt für Schritt bewerten und anwenden. - Beginnen wir mit der konstanten Ausbreitung - dies ist einer der wichtigsten Fehler, die Sie in diesem Test gemacht haben.
Sie haben die Hälfte Ihrer Eingabedaten als Konstanten. Lassen Sie uns das Beispiel mit Blick auf ihre Verbreitung umschreiben.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); }
Nach dem Start hat sich nichts geändert - so muss es sein. - Machen Sie weiter - inline Ihren Code (unser NormalizeDouble kann nicht inlined werden)
So sieht Ihre Funktion in der Realität aus, nachdem sie zwangsläufig inline ist. Einsparungen bei Aufrufen, Einsparungen bei Array-Abrufen, Prüfungen werden aufgrund der ständigen Analyse entfernt: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); }
Ich habe keine reinen Konstanten zusammengefasst, um Zeit zu sparen. Sie sind alle garantiert zur Kompilierzeit kollabiert.
Führen Sie den Code aus und Sie erhalten die gleiche Zeit wie in der Originalversion:Custom: 1149536 msc Standard: 1767592 msc
stören Sie sich nicht am Rattern der Zahlen - auf der Ebene der Mikrosekunden, des Timerfehlers und der schwebenden Belastung des Computers liegt dies im normalen Bereich. das Verhältnis bleibt voll erhalten. - Schauen Sie sich den Code an, mit dessen Prüfung Sie aufgrund der festen Quelldaten begonnen haben.
Da der Compiler über eine sehr leistungsfähige Optimierung verfügt, wurde Ihre Aufgabe effektiv vereinfacht. - Wie sollten Sie also die Leistung testen?
Wenn Sie verstehen, wie der Compiler arbeitet, müssen Sie ihn daran hindern, Voroptimierungen und Vereinfachungen vorzunehmen.
Machen wir zum Beispiel den Parameter Ziffern variabel:#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"); }
Starten Sie es und... erhalten wir das gleiche Geschwindigkeitsergebnis wie zuvor.
Ihr Code wird um etwa 35 % besser als zuvor. - Und warum ist das so?
Wir können uns immer noch nicht vor der Optimierung durch Inlining retten. Die Einsparung von 100 000 000 Aufrufen durch die Weitergabe von Daten über den Stack an unsere Funktion NormalizeDouble, die ähnlich implementiert ist, könnte die gleiche Geschwindigkeitssteigerung bewirken.
Es gibt einen weiteren Verdacht, dass unser NormalizeDouble nicht in den direct_call-Mechanismus implementiert wurde, wenn die Funktionsverschiebungstabelle im MQL5-Programm geladen wird.
Wir werden es morgen früh überprüfen, und wenn ja, werden wir es in direct_call verschieben und die Geschwindigkeit erneut überprüfen.
Hier ist eine Studie von NormalizeDouble.
Unser MQL5-Compiler hat unsere Systemfunktion geschlagen, was seine Angemessenheit im Vergleich zur Geschwindigkeit von C++-Code zeigt.
Wenn Sie die
auf die Switch-Variante, können Sie die Qualität der Switch-Implementierung in Zahlen sehen.
Sie verwechseln den direkten indizierten Zugriff auf ein statisches Array durch einen konstanten Index (der zu einer Konstanten aus einem Feld degeneriert) mit einem Schalter.
Switch kann mit einem solchen Gehäuse nicht wirklich konkurrieren. Switch verfügt über mehrere häufig verwendete Optimierungen der Form:
- "notorisch geordnete und kurze Werte werden in ein statisches Array gestellt und indiziert" - die einfachste und schnellste, kann mit dem statischen Array konkurrieren, aber nicht immer
- "mehrere Arrays durch geordnete und geschlossene Werteblöcke mit Zonengrenzprüfungen" - dies hat bereits eine Bremse
- "wir prüfen zu wenige Werte durch if" - keine Geschwindigkeit, aber der Programmierer ist selbst schuld, er benutzt den Schalter unangemessen
- "sehr spärliche geordnete Tabelle mit binärer Suche" - sehr langsam für die schlimmsten Fälle
In der Tat ist die beste Strategie für den Wechsel, wenn der Entwickler bewusst versucht, eine kompakte Menge von Werten in der unteren Reihe von Zahlen zu machen.
Betrachten Sie die bereinigte Version des Skripts mit NormalizeDouble:
Ergebnisse:
Unmittelbare Bemerkungen und Erklärungen:
- static wird hier benötigt, damit der Compiler dieses Array außerhalb der Funktion ablegt und nicht bei jedem Funktionsaufruf auf dem Stack aufbaut. Der C++-Compiler tut dasselbe.
- Um zu verhindern, dass der Compiler die Schleife wegen ihrer Nutzlosigkeit verwirft, müssen wir die Ergebnisse von Berechnungen verwenden. Drucken Sie zum Beispiel die Variable Preis.
- Es gibt einen Fehler in Ihrer Funktion, der die Grenzen der Ziffern nicht überprüft, was leicht zu Array-Überläufen führen kann.
Rufen Sie es zum Beispiel als MyNormalizeDouble(Price+point,10) auf und fangen Sie den Fehler ab:
Die Methode der Beschleunigung durch Nichtkontrolle ist akzeptabel, aber nicht in unserem Fall. Wir müssen jede fehlerhafte Dateneingabe behandeln. - Fügen wir eine einfache Bedingung für den Index größer als 8 hinzu. Um den Code zu vereinfachen, ersetzen wir den Typ der Variablen digits durch uint, um einen Vergleich für >8 anstelle der zusätzlichen Bedingung <0 durchzuführen
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); }
- Dies ist ein Standardfehler von Leistungstestern.
Beim Schreiben von Tests sollten wir die gesamte Liste der Optimierungen im Auge behalten, die vom Compiler angewendet werden können. Sie müssen sich darüber im Klaren sein, welche Eingabedaten Sie verwenden und wie diese zerstört werden , wenn Sie einen vereinfachten Beispieltest schreiben. - Wie sollten Sie also die Leistung testen?
Wenn Sie verstehen, wie der Compiler arbeitet, müssen Sie ihn daran hindern, Voroptimierungen und Vereinfachungen vorzunehmen.
Machen wir zum Beispiel den Parameter Ziffern variabel:
Dies ist die Studie NormalizeDouble.
Unser MQL5-Compiler hat unsere Systemfunktion geschlagen, was seine Eignung im Vergleich zur Geschwindigkeit von C++-Code zeigt.
Sie verwechseln den direkten indizierten Zugriff auf ein statisches Array durch einen konstanten Index (der zu einer Konstanten aus einem Feld degeneriert) mit einem Schalter.
Switch kann mit einem solchen Gehäuse nicht wirklich konkurrieren. Switch verfügt über einige häufig verwendete Optimierungen dieser Art:
- Die Variante "absichtlich geordnete und kurze Werte werden in ein statisches Array gestellt und per Schalter indiziert" ist die einfachste und schnellste und kann mit einem statischen Array konkurrieren, aber nicht immer.
Dies ist ein solcher Fall einer Bestellung.
In der Tat ist die beste Strategie für den Wechsel, wenn der Entwickler bewusst versucht hat, eine kompakte Menge von Werten in der unteren Gruppe von Zahlen zu machen.
Hier ist ein solcher Fall von Ordnungsmäßigkeit.
Ich habe es auf einem 32-Bit-System ausprobiert. Der Wechsel zum Schalter im obigen Beispiel führte zu einer starken Bremsung. Ich habe es auf dem neuen Rechner nicht überprüft.
Es gibt eigentlich zwei kompilierte Programme in jedem MQL5: ein vereinfachtes für 32 Bit und ein maximal optimiertes für 64 Bit. In 32-Bit MT5 gilt der neue Optimierer überhaupt nicht und der Code für 32-Bit-Operationen ist so einfach wie MQL4 in MT4.
Die ganze Effizienz des Compilers, der den Code zehnmal schneller erzeugen kann, wenn er in der 64-Bit-Version von MT5 ausgeführt wird: https://www.mql5.com/ru/forum/58241
Wir konzentrieren uns voll und ganz auf 64-Bit-Versionen der Plattform.
- Bewertungen: 8
- www.mql5.com
Zum Thema NormalizeDouble gibt es diesen Unsinn
Forum zum Thema Handel, automatisierte Handelssysteme und Strategietests
Wie gehe ich eine Aufzählung konsequent durch?
fxsaber, 2016.08.26 16:08
In der Funktionsbeschreibung findet sich folgender Hinweis
Dies gilt nur für Symbole, die einen Mindestpreisschritt von 10^N haben, wobei N eine ganze Zahl und nicht positiv ist. Wenn der Mindestpreisschritt einen anderen Wert hat, dann ist die Normalisierung der Preisniveaus vor OrderSend eine sinnlose Operation, die in den meisten Fällen zu einem falschen OrderSend führt.
NormalizeDouble ist völlig diskreditiert. Nicht nur langsame Umsetzung, sondern auch sinnlos bei mehreren Börsensymbolen (z.B. 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); }
Nun, so ungeschickt können Sie nicht vorgehen! Es könnte um ein Vielfaches schneller sein, wenn man NormalizeDouble vergisst.
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); }
Und für das gleiche Volumen dann tun
volume = NormalizePrice(volume, stepvol);
Für Preise tun
NormalizePrice(Price, TickSize)
Es scheint richtig, etwas Ähnliches hinzuzufügen, um den NormalizeDouble-Standard zu überladen. Der zweite Parameter "digits" ist ein double anstelle von int.
Im Jahr 2016 haben die meisten C++-Compiler den gleichen Optimierungsgrad erreicht.
MSVC lässt einen bei jedem Update über die Verbesserungen staunen, und Intel C++ als Compiler ist zusammengewachsen - es hat sich immer noch nicht von seinem "internen Fehler" bei großen Projekten erholt.
Eine weitere Verbesserung des Compilers in der 1400er Version besteht darin, dass er komplexe Projekte schneller kompilieren kann.
Zum Thema. Sie müssen Alternativen zu den Standardfunktionen erstellen, da diese manchmal die falsche Ausgabe liefern. Hier ein Beispiel für eine Alternative zu 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); }
Sie können SymbolInfoTick bei jedem Ereignis NewTick im Tester aufrufen und das Volumen-Feld summieren, um den Aktienumsatz zu ermitteln. Aber nein, das können Sie nicht! Wir müssen ein viel logischeres MySymbolInfoDouble erstellen.
Zum Thema NormalizeDouble gibt es diesen Unsinn
Nun, so ungeschickt kann es nicht sein! Man kann es um ein Vielfaches schneller machen, wenn man NormalizeDouble vergisst.
Und für das gleiche Volumen tun
Für Preise tun
Es scheint richtig, so etwas als Überladung zum NormalizeDouble-Standard hinzuzufügen. Der zweite Parameter "digits" ist ein double anstelle von int.
Sie können alles drum herum optimieren.
Dies ist ein endloser Prozess. Aber in 99 % der Fälle ist es wirtschaftlich unrentabel.
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
NormalisierenDouble
Das Ergebnis ist 1123275 und 1666643 zu Gunsten von MyNormalizeDouble (Optimize=1). Ohne Optimierung ist er viermal schneller (pro Speicher).