Si sustituye
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};
a la variante del interruptor, se puede ver la calidad de la implementación del interruptor en números.
Considere la versión limpia del 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"); }
Resultados:
Custom: 1110255 msc Result: 1001.23456 Standard: 1684165 msc Result: 1001.23456
Observaciones y explicaciones inmediatas:
- static es necesario aquí para que el compilador tome este array fuera de la función y no lo construya en la pila cada vez que se llame a la función. El compilador de C++ hace lo mismo.
static const double Points
- Para evitar que el compilador deseche el bucle por ser inútil, debemos utilizar los resultados de los cálculos. Por ejemplo, imprime la variable Precio.
- Hay un error en su función: no se comprueban los límites de los dígitos, lo que puede llevar fácilmente a desbordamientos de la matriz.
Por ejemplo, llámelo como MyNormalizeDouble(Price+point,10) y capture el error:array out of range in 'BenchNormalizeDouble.mq5' (19,45)
El método de acelerar no verificando es aceptable, pero no en nuestro caso. Debemos manejar cualquier entrada de datos errónea. - Añadamos una condición simple para un índice mayor que 8. Para simplificar el código, sustituya el tipo de la variable dígitos por uint, para hacer una comparación para >8 en lugar de una condición 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 a ejecutar el código y... Estamos sorprendidos.
Custom: 1099705 msc Result: 1001.23456 Standard: 1695662 msc Result: 1001.23456
Su código ha superado aún más la función estándar NormalizeDouble.
Además, la adición de la condición incluso reduce el tiempo (en realidad está dentro del margen de error). ¿Por qué hay tanta diferencia de velocidad? - Todo esto tiene que ver con un error estándar de los probadores de rendimiento.
Al escribir las pruebas hay que tener en cuenta la lista completa de optimizaciones que puede aplicar el compilador. Debe tener claro qué datos de entrada va a utilizar y cómo se van a destruir cuando escriba una prueba de muestra simplificada.
Evaluemos y apliquemos todo el conjunto de optimizaciones que realiza nuestro compilador, paso a paso. - Empecemos por la propagación constante: es uno de los errores importantes que ha cometido en esta prueba.
Tienes la mitad de tus datos de entrada como constantes. Reescribamos el ejemplo teniendo en cuenta su propagación.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); }
Después de lanzarlo, nada ha cambiado - debe ser así. - Adelante - inline su código (nuestro NormalizeDouble no puede ser inline)
Esto es lo que su función se convertirá en realidad después de inelining. El ahorro en las llamadas, el ahorro en la obtención de matrices, las comprobaciones se eliminan debido al análisis 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); }
No he resumido las constantes puras para ahorrar tiempo. todas están garantizadas para colapsar en tiempo de compilación.
Ejecuta el código y obtén el mismo tiempo que en la versión original:Custom: 1149536 msc Standard: 1767592 msc
no te preocupes por el parloteo de los números - a nivel de microsegundos, error del temporizador y carga flotante en el ordenador, esto está dentro de los límites normales. la proporción se mantiene completamente. - Mira el código que realmente comenzó a probar debido a los datos de origen fijo.
Como el compilador tiene una optimización muy potente, su tarea se ha simplificado de forma efectiva. - Entonces, ¿cómo se debe comprobar el rendimiento?
Al entender cómo funciona el compilador, hay que evitar que aplique preoptimizaciones y simplificaciones.
Por ejemplo, hagamos variable el parámetro dígitos:#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"); }
Ejecútalo y... obtenemos el mismo resultado de velocidad que antes.
Su código gana un 35% como antes. - ¿Y por qué es así?
Todavía no podemos salvarnos de la optimización debido al inlining. Ahorrar 100 000 000 de llamadas pasando los datos a través de la pila a nuestra función NormalizarDoble, que es similar en la implementación, bien podría dar el mismo aumento de velocidad.
Hay otra sospecha de que nuestro NormalizeDouble no ha sido implementado en el mecanismo direct_call cuando se carga la tabla de reubicación de funciones en el programa MQL5.
Lo comprobaremos por la mañana y si es así, lo pasaremos a direct_call y comprobaremos de nuevo la velocidad.
Aquí hay un estudio de NormalizeDouble.
Nuestro compilador MQL5 ha superado la función de nuestro sistema, lo que demuestra su idoneidad cuando se compara con la velocidad del código C++.
Si sustituye
a la variante del interruptor, se puede ver la calidad de la implementación del interruptor en números.
Estás confundiendo el acceso indexado directo a un array estático mediante un índice constante (que degenera en una constante de un campo) y el switch.
Switch no puede competir con un caso así. Switch tiene varias optimizaciones de uso frecuente de la forma:
- "los valores notoriamente ordenados y cortos se ponen en un array estático y se indexan" - el más sencillo y rápido, puede competir con el array estático, pero no siempre
- "varias matrices por trozos de valores ordenados y cerrados con comprobaciones de límites de zona" - esto ya tiene un freno
- "comprobamos muy pocos valores a través de if" - no hay velocidad, pero es culpa del propio programador, que utiliza el switch de forma inapropiada
- "tabla ordenada muy dispersa con búsqueda binaria" - muy lento para los peores casos
De hecho, la mejor estrategia para el cambio es cuando el desarrollador trató deliberadamente de hacer un conjunto compacto de valores en el conjunto inferior de números.
Considere la versión limpiada del script con NormalizeDouble:
Resultados:
Observaciones y explicaciones inmediatas:
- static es necesario aquí para que el compilador ponga este array fuera de la función y no lo construya en la pila en cada llamada a la función. El compilador de C++ hace lo mismo.
- Para evitar que el compilador deseche el bucle por su inutilidad, debemos utilizar los resultados de los cálculos. Por ejemplo, imprime la variable Precio.
- Hay un error en su función que no comprueba los límites de los dígitos, lo que puede llevar fácilmente a desbordamientos del array.
Por ejemplo, llámelo como MyNormalizeDouble(Price+point,10) y capture el error:
El método de acelerar no verificando es aceptable, pero no en nuestro caso. Debemos manejar cualquier entrada de datos errónea. - Añadamos una condición simple sobre el índice mayor que 8. Para simplificar el código, sustituyamos el tipo de la variable dígitos por uint, para hacer una comparación para >8 en lugar de la condición 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 es un error habitual de los probadores de rendimiento.
Al escribir las pruebas debemos tener en cuenta la lista completa de optimizaciones que puede aplicar el compilador. Debe tener claro qué datos de entrada va a utilizar y cómo se van a destruir cuando escriba una prueba de muestra simplificada. - Entonces, ¿cómo se debe comprobar el rendimiento?
Al entender cómo funciona el compilador, hay que evitar que aplique preoptimizaciones y simplificaciones.
Por ejemplo, hagamos variable el parámetro dígitos:
Este es el estudio NormalizeDouble.
Nuestro compilador MQL5 superó la función de nuestro sistema, lo que demuestra su idoneidad cuando se compara con la velocidad del código C++.
Estás confundiendo el acceso indexado directo a un array estático mediante un índice constante (que degenera en una constante de un campo) y el switch.
Switch no puede competir con un caso así. Switch cuenta con algunas optimizaciones de uso común del tipo:
- El "ordenado deliberadamente y los valores cortos se ponen en un array estático y se indexan por interruptor" es el más simple y rápido, y puede competir con un array estático, pero no siempre.
Este es precisamente un caso de pedido.
De hecho, la mejor estrategia para el cambio es cuando el desarrollador ha intentado deliberadamente hacer un conjunto compacto de valores en el conjunto inferior de números.
Este es un caso de orden.
Lo he probado en un sistema de 32 bits. En este caso, la sustitución del interruptor en el ejemplo anterior provocó una frenada grave. No lo he comprobado en la nueva máquina.
En realidad hay dos programas compilados en cada MQL5: uno simplificado para 32 bits y otro optimizado al máximo para 64 bits. En MT5 de 32 bits el nuevo optimizador no se aplica en absoluto y el código para las operaciones de 32 bits es tan simple como MQL4 en MT4.
Toda la eficiencia del compilador que puede generar código diez veces más rápido sólo cuando se ejecuta en la versión de 64 bits de MT5: https://www.mql5.com/ru/forum/58241
Estamos totalmente centrados en las versiones de 64 bits de la plataforma.
- comentarios: 8
- www.mql5.com
Sobre el tema de NormalizeDouble existe esta tontería
Foro sobre comercio, sistemas de comercio automatizados y pruebas de estrategias
¿Cómo puedo pasar por una enumeración de forma coherente?
fxsaber, 2016.08.26 16:08
Hay esta nota en la descripción de la función
Esto sólo es cierto para los símbolos que tienen un paso de precio mínimo de 10^N, donde N es un número entero y no positivo. Si el paso de precio mínimo tiene un valor diferente, entonces la normalización de los niveles de precio antes de OrderSend es una operación sin sentido que devolverá un falso OrderSend en la mayoría de los casos.
NormalizeDouble está completamente desacreditado. No sólo se trata de una implementación lenta, sino que además carece de sentido en múltiples símbolos de intercambio (por ejemplo, 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); }
Bueno, ¡no se puede hacer tan torpemente! Podría ser muchas veces más rápido, olvidándose de 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); }
Y para el mismo volumen entonces hacer
volume = NormalizePrice(volume, stepvol);
Para los precios sí
NormalizePrice(Price, TickSize)
Parece correcto añadir algo similar para sobrecargar el estándar NormalizeDouble. Donde el segundo parámetro "dígitos" será un doble en lugar de un int.
En 2016, la mayoría de los compiladores de C++ han llegado a los mismos niveles de optimización.
MSVC hace que uno se pregunte sobre las mejoras con cada actualización, e Intel C++ como compilador se ha fusionado - nunca se ha curado realmente de su "error interno" en proyectos grandes.
Otra de nuestras mejoras en el compilador en la compilación 1400 es que es más rápido en la compilación de proyectos complejos.
Sobre el tema. Hay que crear alternativas a las funciones estándar, porque a veces dan una salida equivocada. Este es un ejemplo de la 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); }
Puede llamar a SymbolInfoTick en cada evento NewTick en el probador y sumar el campo de volumen para conocer la rotación de las acciones. Pero no, no se puede. Tenemos que hacer un MySymbolInfoDouble mucho más lógico.
Sobre el tema de NormalizeDouble existe esta tontería
Bueno, ¡no puede ser tan torpe! Puede ser muchas veces más rápido olvidándose de NormalizeDouble.
Y por el mismo volumen hacer
Para los precios sí
Parece correcto añadir algo así como una sobrecarga al estándar NormalizeDouble. Donde el segundo parámetro "dígitos" será un doble en lugar de un int.
Puedes optimizar todo a su alrededor.
Este es un proceso interminable. Pero en el 99% de los casos no es económicamente rentable.
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
NormalizarDoble
El resultado es 1123275 y 1666643 a favor de MyNormalizeDouble (Optimize=1). Sin optimización, es cuatro veces más rápido (en memoria).