Se você substituir
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};
para a variante switch, você pode ver a qualidade da implementação do switch em números.
Considere a versão limpa do roteiro com 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"); }
Resultados:
Custom: 1110255 msc Result: 1001.23456 Standard: 1684165 msc Result: 1001.23456
Observações e explicações imediatas:
- estática é necessária aqui para que o compilador leve esta matriz para fora da função e não a construa sobre a pilha toda vez que a função é chamada. O compilador C++ faz o mesmo.
static const double Points
- Para evitar que o compilador jogue o laço fora por ser inútil, devemos usar os resultados dos cálculos. Por exemplo, Imprimir a variável Preço.
- Há um erro em sua função - os limites dos dígitos não são verificados, o que pode facilmente levar a ultrapassagens de matriz.
Por exemplo, chame-o como MyNormalizeDouble(Preço+ponto,10) e pegue o erro:array out of range in 'BenchNormalizeDouble.mq5' (19,45)
O método de aceleração por não verificação é aceitável, mas não no nosso caso. Devemos lidar com qualquer entrada de dados errônea. - Vamos adicionar uma condição simples para um índice maior que 8. Para simplificar o código, substitua o tipo de dígitos variáveis por uint, para fazer uma comparação para >8 ao invés de uma condição adicional <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]); }
- Vamos executar o código e... Estamos surpresos!
Custom: 1099705 msc Result: 1001.23456 Standard: 1695662 msc Result: 1001.23456
Seu código ultrapassou ainda mais a função NormalizeDuplo padrão!
Além disso, a adição da condição reduz até mesmo o tempo (na verdade, ele está dentro da margem de erro). Por que há tanta diferença na velocidade? - Tudo isso tem a ver com um erro padrão dos testadores de desempenho.
Ao escrever testes, você deve ter em mente a lista completa de otimizações que podem ser aplicadas pelo compilador. Você precisa ser claro sobre quais dados de entrada você está usando e como eles serão destruídos quando você escrever um teste de amostra simplificado.
Vamos avaliar e aplicar todo o conjunto de otimizações que nosso compilador faz, passo a passo. - Vamos começar com a propagação constante - este é um dos erros importantes que você cometeu neste teste.
Você tem metade de seus dados de entrada como constantes. Vamos reescrever o exemplo levando em conta sua propagação.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); }
Após o seu lançamento, nada mudou - deve ser assim. - Continue - inline seu código (nosso NormalizeDouble não pode ser inlined)
Esta é a aparência de sua função na realidade, depois de inevitável em linha. A economia nas chamadas, a economia nas buscas de matriz, as verificações são removidas devido à análise 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); }
Não resumi constantes puras para economizar tempo. todas elas têm garantia de colapso em tempo de compilação.
Execute o código e obtenha o mesmo tempo que na versão original:Custom: 1149536 msc Standard: 1767592 msc
não se importe com a tagarelice dos números - ao nível de microssegundos, erro de temporizador e carga flutuante no computador, isto está dentro dos limites normais. a proporção é totalmente mantida. - Veja o código que você realmente começou a testar por causa dos dados da fonte fixa.
Como o compilador tem uma otimização muito poderosa, sua tarefa foi efetivamente simplificada. - Então, como você deve testar o desempenho?
Ao entender como o compilador funciona, você precisa impedir que ele aplique pré-optimizações e simplificações.
Por exemplo, vamos tornar o parâmetro de dígitos variável:#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"); }
Execute-o e... obtemos o mesmo resultado de velocidade de antes.
Seu código ganha cerca de 35%, como antes. - Então por que é assim?
Ainda não podemos nos salvar da otimização por causa da linha de costura. Economizar 100 000 000 de chamadas passando dados através da pilha em nossa função NormalizeDouble, que é semelhante na implementação, pode muito bem dar o mesmo aumento de velocidade.
Há outra suspeita de que nosso NormalizeDouble não tenha sido implementado no mecanismo de chamada direta ao carregar a tabela de realocação de funções no programa MQL5.
Vamos verificá-lo pela manhã e, se for o caso, vamos movê-lo para o direct_call e verificar novamente a velocidade.
Aqui está um estudo de NormalizeDouble.
Nosso compilador MQL5 venceu nossa função de sistema, o que mostra sua adequação quando comparado com a velocidade do código C++.
Se você substituir
para a variante switch, você pode ver a qualidade da implementação do switch em números.
Você está confundindo o acesso direto indexado a uma matriz estática por um índice constante (que degenera em uma constante de um campo) e muda.
Switch não pode realmente competir com um caso desses. Switch tem várias otimizações do formulário utilizadas com freqüência:
- "notoriamente ordenados e valores curtos são colocados em uma matriz estática e indexados" - os mais simples e rápidos, podem competir com a matriz estática, mas nem sempre
- "várias matrizes por ordem e fecham pedaços de valores com verificações de limites de zona" - isto já tem um freio
- "verificamos muito poucos valores através de se" - sem velocidade, mas é culpa do próprio programador, ele usa o interruptor inadequadamente
- "mesa muito esparsa com busca binária" - muito lenta para os piores casos
Na verdade, a melhor estratégia para a troca é quando o desenvolvedor tentou deliberadamente fazer um conjunto compacto de valores no conjunto inferior de números.
Considere a versão limpa do roteiro com NormalizeDouble:
Resultados:
Observações e explicações imediatas:
- é necessário aqui para que o compilador coloque esta matriz fora da função e não a construa sobre a pilha a cada chamada de função. O compilador C++ faz a mesma coisa.
- Para evitar que o compilador jogue fora o loop devido a sua inutilidade, devemos utilizar os resultados dos cálculos. Por exemplo, Imprimir a variável Preço.
- Há um erro em sua função que não verifica os limites dos dígitos, o que pode facilmente levar a ultrapassagens de matriz.
Por exemplo, chame-o como MyNormalizeDouble(Preço+ponto,10) e pegue o erro:
O método de aceleração por não verificação é aceitável, mas não no nosso caso. Devemos lidar com qualquer entrada de dados errônea. - Vamos adicionar uma condição simples sobre o índice maior que 8. Para simplificar o código, vamos substituir o tipo de dígitos variáveis por uint, para fazer uma comparação para >8 ao invés de uma condição adicional <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); }
- Este é um erro padrão dos testadores de desempenho.
Ao escrever testes, devemos ter em mente a lista completa de otimizações que podem ser aplicadas pelo compilador. Você precisa ser claro sobre quais dados de entrada você está usando e como eles serão destruídos quando você escrever um teste de amostra simplificado. - Então, como você deve testar o desempenho?
Ao entender como o compilador funciona, você precisa impedir que ele aplique pré-optimizações e simplificações.
Por exemplo, vamos tornar o parâmetro de dígitos variável:
Este é o estudo NormalizeDouble.
Nosso compilador MQL5 superou nossa função de sistema, o que mostra sua adequação quando comparado com a velocidade do código C++.
Você está confundindo o acesso direto indexado a uma matriz estática por um índice constante (que degenera em uma constante de um campo) e muda.
Switch não pode realmente competir com um caso desses. O Switch tem algumas otimizações do tipo comumente utilizadas:
- Os "valores deliberadamente ordenados e curtos são colocados em uma matriz estática e indexados por interruptor" é o mais simples e rápido, e pode competir com uma matriz estática, mas nem sempre.
Este é exatamente um caso de encomenda.
Na verdade, a melhor estratégia para a troca é quando o desenvolvedor tentou deliberadamente fazer um conjunto compacto de valores no conjunto inferior de números.
Este é apenas um caso de ordenação.
Experimentei-o em um sistema de 32 bits. Ali, a substituição do interruptor no exemplo acima causou uma frenagem séria. Ainda não chequei na nova máquina.
Na verdade, há dois programas compilados em cada MQL5: um simplificado para 32 bits e um maximamente otimizado para 64 bits. Em 32 bit MT5 o novo otimizador não se aplica e o código para operações de 32 bit é tão simples quanto o MQL4 em MT4.
Toda a eficiência do compilador que pode gerar código dez vezes mais rápido apenas quando executado na versão de 64 bits do MT5: https://www.mql5.com/ru/forum/58241
Estamos totalmente concentrados nas versões de 64 bits da plataforma.

- comentários: 8
- www.mql5.com
Sobre o assunto de NormalizeDouble há este absurdo
Fórum sobre comércio, sistemas automatizados de comércio e testes estratégicos
Como faço para passar por uma enumeração de forma consistente?
fxsaber, 2016.08.26 16:08
Há esta nota na descrição da função
Isto só é verdade para símbolos que têm uma etapa de preço mínimo de 10^N, onde N é um número inteiro e não positivo. Se a etapa de preço mínimo tem um valor diferente, então a normalização dos níveis de preço antes da OrderSend é uma operação sem sentido que devolverá a OrderSend falsa na maioria dos casos.
NormalizeDouble está completamente desacreditado. Não apenas a implementação lenta, mas também sem sentido em múltiplos símbolos de troca (por exemplo, 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); }
Bem, você não pode fazer isso desajeitadamente! Poderia ser muitas vezes mais rápido, esquecendo a 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 para o mesmo volume, então faça
volume = NormalizePrice(volume, stepvol);
Para preços fazem
NormalizePrice(Price, TickSize)
Parece correto acrescentar algo semelhante à sobrecarga do padrão NormalizeDouble. Onde o segundo parâmetro "dígitos" será um duplo em vez de int.
Até 2016, a maioria dos compiladores C++ chegou aos mesmos níveis de otimização.
A MSVC faz uma maravilha sobre as melhorias a cada atualização, e a Intel C++ como compilador se fundiu - nunca realmente curado de seu "erro interno" em grandes projetos.
Outra de nossas melhorias no compilador na construção do 1400 é que ele é mais rápido na compilação de projetos complexos.
Sobre o assunto. Você tem que criar alternativas para as funções padrão, porque elas às vezes lhe dão o resultado errado. Aqui está um exemplo de 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); }
Você pode chamar a SymbolInfoTick em cada evento NewTick no campo de teste e resumir o volume do campo de teste para saber a rotatividade do estoque. Mas não, você não pode! Temos que fazer um MySymbolInfoDouble muito mais lógico.
Sobre o assunto de NormalizeDouble há este absurdo
Bem, não pode ser tão desajeitado! Você pode fazer isso muitas vezes mais rápido esquecendo a NormalizeDouble.
E para o mesmo volume fazem
Para preços fazem
Parece correto acrescentar algo como isto como uma sobrecarga ao padrão NormalizeDouble. Onde o segundo parâmetro "dígitos" será um duplo em vez de int.
Você pode otimizar tudo ao seu redor.
Este é um processo interminável. Mas em 99% dos casos é economicamente não rentável.

- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
NormalizeDuplo
O resultado é 1123275 e 1666643 em favor do MyNormalizeDouble (Optimize=1). Sem otimização, é quatro vezes mais rápido (em memória).