Se si sostituisce
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};
alla variante dello switch, si può vedere la qualità dell'implementazione dello switch in numeri.
Considerate la versione ripulita dello script con 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"); }
Risultati:
Custom: 1110255 msc Result: 1001.23456 Standard: 1684165 msc Result: 1001.23456
Osservazioni e spiegazioni immediate:
- static è necessario qui in modo che il compilatore prenda questo array fuori dalla funzione e non lo costruisca sullo stack ogni volta che la funzione viene chiamata. Il compilatore C++ fa lo stesso.
static const double Points
- Per evitare che il compilatore butti via il ciclo perché è inutile, dovremmo usare i risultati dei calcoli. Per esempio, stampare la variabile Price.
- C'è un errore nella tua funzione - i confini delle cifre non sono controllati, il che può facilmente portare a un overrun dell'array.
Per esempio, chiamatelo come MyNormalizeDouble(Price+point,10) e catturate l'errore:array out of range in 'BenchNormalizeDouble.mq5' (19,45)
Il metodo di accelerare non controllando è accettabile, ma non nel nostro caso. Dobbiamo gestire qualsiasi inserimento di dati errati. - Aggiungiamo una semplice condizione per un indice maggiore di 8. Per semplificare il codice, sostituiamo il tipo della variabile digits con uint, per fare un confronto per >8 invece di una condizione aggiuntiva <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]); }
- Eseguiamo il codice e... Siamo sorpresi!
Custom: 1099705 msc Result: 1001.23456 Standard: 1695662 msc Result: 1001.23456
Il tuo codice ha superato ancora di più la funzione standard NormalizeDouble!
Inoltre, l'aggiunta della condizione riduce addirittura il tempo (in realtà rientra nel margine di errore). Perché c'è una tale differenza di velocità? - Tutto questo ha a che fare con un errore standard dei tester di prestazioni.
Quando si scrivono i test si dovrebbe tenere a mente l'elenco completo delle ottimizzazioni che possono essere applicate dal compilatore. Dovete essere chiari su quali dati di input state usando e come saranno distrutti quando scrivete un test di esempio semplificato.
Valutiamo e applichiamo l'intera serie di ottimizzazioni che il nostro compilatore fa, passo dopo passo. - Cominciamo con la propagazione costante - questo è uno degli errori importanti che hai fatto in questo test.
Avete metà dei vostri dati di input come costanti. Riscriviamo l'esempio tenendo presente la loro propagazione.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); }
Dopo averlo lanciato, non è cambiato nulla - deve essere così. - Andate avanti - inlineate il vostro codice (il nostro NormalizeDouble non può essere inlineato)
Questo è ciò che la vostra funzione diventerà in realtà dopo l'inelining. Il risparmio sulle chiamate, il risparmio sulle ricerche di array, i controlli vengono rimossi grazie all'analisi costante: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); }
Non ho riassunto le costanti pure per risparmiare tempo. sono tutte garantite per collassare al momento della compilazione.
Esegui il codice e ottieni lo stesso tempo della versione originale:Custom: 1149536 msc Standard: 1767592 msc
non prestare attenzione al jitter nei numeri - a livello di microsecondi, errore del timer e carico fluttuante sul computer, questo è entro limiti normali. la proporzione è completamente mantenuta. - Guardate il codice che avete effettivamente iniziato a testare a causa dei dati sorgente fissi.
Poiché il compilatore ha un'ottimizzazione molto potente, il vostro compito è stato effettivamente semplificato. - Quindi come dovreste testare le prestazioni?
Comprendendo come funziona il compilatore, dovete impedirgli di applicare preottimizzazioni e semplificazioni.
Per esempio, rendiamo variabile il parametro digits:#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"); }
Eseguilo e... otteniamo lo stesso risultato di velocità di prima.
Il vostro codice guadagna circa il 35% come prima. - Allora perché è così?
Non possiamo ancora salvarci dall'ottimizzazione dovuta all'inlining. Risparmiare 100 000 000 chiamate passando i dati attraverso lo stack nella nostra funzione NormalizeDouble, che ha un'implementazione simile, potrebbe dare lo stesso aumento di velocità.
C'è un altro sospetto che il nostro NormalizeDouble non sia stato implementato nel meccanismo direct_call quando si carica la tabella di riposizionamento delle funzioni nel programma MQL5.
Lo controlleremo domattina e se è così, lo sposteremo su direct_call e controlleremo di nuovo la velocità.
Ecco uno studio di NormalizeDouble.
Il nostro compilatore MQL5 ha battuto la nostra funzione di sistema, il che dimostra la sua adeguatezza rispetto alla velocità del codice C++.
Se si sostituisce
alla variante dello switch, si può vedere la qualità dell'implementazione dello switch in numeri.
State confondendo l'accesso diretto indicizzato a un array statico tramite un indice costante (che degenera in una costante da un campo) e lo switch.
Switch non può davvero competere con un caso del genere. Switch ha diverse ottimizzazioni di uso frequente della forma:
- "i valori notoriamente ordinati e brevi sono messi in un array statico e indicizzati" - il più semplice e veloce, può competere con l'array statico, ma non sempre
- "diversi array per pezzi di valori ordinati e vicini con controlli dei confini di zona" - questo ha già un freno
- "controlliamo troppo pochi valori attraverso if" - nessuna velocità, ma è colpa del programmatore, che usa lo switch in modo inappropriato
- "tabella ordinata molto rada con ricerca binaria" - molto lento nei casi peggiori
Infatti, la migliore strategia per lo switch è quando lo sviluppatore ha deliberatamente cercato di fare un insieme compatto di valori nell'insieme inferiore di numeri.
Considerate la versione ripulita dello script con NormalizeDouble:
Risultati:
Osservazioni e spiegazioni immediate:
- static è necessario qui perché il compilatore metta questo array fuori dalla funzione e non lo costruisca sullo stack ad ogni chiamata di funzione. Il compilatore C++ fa la stessa cosa.
- Per evitare che il compilatore butti via il ciclo a causa della sua inutilità, dobbiamo usare i risultati dei calcoli. Per esempio, stampare la variabile Price.
- C'è un errore nella vostra funzione che non controlla i limiti delle cifre, il che può facilmente portare all'overrun dell'array.
Per esempio, chiamatelo come MyNormalizeDouble(Price+point,10) e catturate l'errore:
Il metodo di accelerare non controllando è accettabile, ma non nel nostro caso. Dobbiamo gestire qualsiasi inserimento di dati errati. - Aggiungiamo una semplice condizione sull'indice maggiore di 8. Per semplificare il codice, sostituiamo il tipo della variabile digits con uint, per fare un confronto per >8 invece della condizione aggiuntiva <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); }
- Questo è un errore standard dei tester di prestazioni.
Quando scriviamo i test dovremmo tenere a mente l'elenco completo delle ottimizzazioni che possono essere applicate dal compilatore. Dovete essere chiari su quali dati di input state usando e come saranno distrutti quando scrivete un test di esempio semplificato. - Quindi come dovreste testare le prestazioni?
Comprendendo come funziona il compilatore, dovete impedirgli di applicare preottimizzazioni e semplificazioni.
Per esempio, rendiamo variabile il parametro digits:
Questo è lo studio NormalizeDouble.
Il nostro compilatore MQL5 ha battuto la nostra funzione di sistema, il che dimostra la sua adeguatezza rispetto alla velocità del codice C++.
State confondendo l'accesso diretto indicizzato a un array statico tramite un indice costante (che degenera in una costante da un campo) e lo switch.
Switch non può davvero competere con un caso del genere. Switch ha alcune ottimizzazioni comunemente usate del genere:
- Il "valori volutamente ordinati e brevi sono messi in un array statico e indicizzati per switch" è il più semplice e veloce, e può competere con un array statico, ma non sempre.
Questo è proprio un caso di ordinazione.
Infatti, la migliore strategia per lo switch è quando lo sviluppatore ha deliberatamente cercato di fare un insieme compatto di valori nella serie inferiore di numeri.
Ecco un caso di ordine come questo.
L'ho provato su un sistema a 32 bit. Il cambio di interruttore nell'esempio precedente ha causato una grave frenata. Non ho controllato sulla nuova macchina.
Ci sono in realtà due programmi compilati in ogni MQL5: uno semplificato per 32 bit e uno ottimizzato al massimo per 64 bit. In MT5 a 32 bit il nuovo ottimizzatore non si applica affatto e il codice per le operazioni a 32 bit è semplice come MQL4 in MT4.
Tutta l'efficienza del compilatore che può generare codice dieci volte più veloce solo se eseguito nella versione a 64 bit di MT5: https://www.mql5.com/ru/forum/58241
Siamo completamente concentrati sulle versioni a 64 bit della piattaforma.
- recensioni: 8
- www.mql5.com
A proposito di NormalizeDouble c'è questa sciocchezza
Forum sul trading, sistemi di trading automatico e test di strategia
Come si fa a passare un'enumerazione in modo coerente?
fxsaber, 2016.08.26 16:08
C'è questa nota nella descrizione della funzione
Questo è vero solo per i simboli che hanno un passo di prezzo minimo 10^N, dove N è intero e non positivo. Se il livello di prezzo minimo ha un valore diverso, allora la normalizzazione dei livelli di prezzo prima di OrderSend è un'operazione senza senso che restituirà OrderSend falso nella maggior parte dei casi.
NormalizeDouble è completamente screditato. Non solo l'implementazione lenta, ma anche senza senso su più simboli di scambio (ad esempio RTS, MIX, ecc.).
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); }
Beh, non si può fare così maldestramente! Potrebbe essere molto più veloce, dimenticando 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); }
E per lo stesso volume poi fare
volume = NormalizePrice(volume, stepvol);
Per i prezzi fare
NormalizePrice(Price, TickSize)
Sembra corretto aggiungere qualcosa di simile per sovraccaricare lo standard NormalizeDouble. Dove il secondo parametro "digits" sarà un doppio invece di int.
Nel 2016, la maggior parte dei compilatori C++ sono arrivati agli stessi livelli di ottimizzazione.
MSVC fa dubitare dei miglioramenti ad ogni aggiornamento, e Intel C++ come compilatore si è fuso - mai veramente guarito dal suo "errore interno" su grandi progetti.
Un altro dei nostri miglioramenti nel compilatore nella build 1400 è che è più veloce nel compilare progetti complessi.
In tema. Dovete creare delle alternative alle funzioni standard, perché a volte vi danno l'output sbagliato. Ecco un esempio di alternativa 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); }
Potete chiamare SymbolInfoTick su ogni evento NewTick nel tester e sommare il campo volume per conoscere il fatturato delle azioni. Ma no, non si può! Dobbiamo fare un MySymbolInfoDouble molto più logico.
A proposito di NormalizeDouble c'è questa sciocchezza
Beh, non può essere così maldestro! Potete renderlo molte volte più veloce dimenticando NormalizeDouble.
E per lo stesso volume fare
Per i prezzi fare
Sembra corretto aggiungere qualcosa del genere come sovraccarico allo standard NormalizeDouble. Dove il secondo parametro "digits" sarà un doppio invece di int.
Si può ottimizzare tutto intorno ad esso.
Questo è un processo senza fine. Ma nel 99% dei casi non è economicamente redditizio.
- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso
NormalizeDouble
Il risultato è 1123275 e 1666643 a favore di MyNormalizeDouble (Optimize=1). Senza ottimizzazione, è quattro volte più veloce (in memoria).