MQL4, MQL2, EasyLanguage, Wealth-Lab 3.0 и VC++: сравнение скорости

 
Мы провели тесты скорости исполнения простейших математических операций в MQL4, MQL2, Wealth-Lab 3.0 и VC++ 6.0

Идея - в 10 миллионном цикле добавляем в целочисленную переменную квадрат переменной цикла с последующим увеличением на единицу и в тоже самое в переменную с плавающей запятой.
В качестве замера времени используем штатную Windows функцию GetTickCount(), которая возвращает количество миллисекунд с момента запуска сессии Windows. Точности счетчика (миллисекунды) достаточно для замеров.

Тесты для общей информации о производительности языков.

Код на VC++ 6.0 (максимальная оптимизация, однопотоковая библиотека)
#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void main()
  {
   int    res_int=0,i,start_time;
   double res_double=0;
//----
   start_time=GetTickCount();
   for(i=0;i<=10000000;i++)
     {
      res_int+=i*i;
      res_int++;
      res_double+=i*i;
      res_double++;
     }
   start_time=GetTickCount()-start_time;
//---- вывод результатов
   printf("Time: %d ms, ResInt=%d ResDouble=%.0lf\n",start_time,res_int,res_double);
//----
  }


Код на MQL4 (практически 100% совпадает с кодом VC С++)

//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
void start()
  {
   int    res_int=0,i,start_time;
   double res_double=0;
//----
   start_time=GetTickCount();
   for(i=0;i<=10000000;i++)
     {
      res_int+=i*i;
      res_int++;
      res_double+=i*i;
      res_double++;
     }
   start_time=GetTickCount()-start_time;
//---- вывод результатов
   Print("Time: ",start_time," ms, ResInt=",res_int," ResDouble=",res_double);
//----
  }


Код на MQL2 (при тестировании был отключен 1 секундный контроль)

Variable : res_int(0),i(0),start_time(0),res_double(0);

res_int=0;
res_double=0;
start_time=GetTickCount();
for i=0 to 10000000
begin
  res_int+=i*i;
  res_int++;
  res_double+=i*i;
  res_double++;
end;
start_time=GetTickCount()-start_time;
//---- вывод результатов
Print("Time: ",start_time," ms, ResInt=",res_int," ResDouble=",res_double);



Код на Wealth-Lab Script 3.0:

var res_int,i,start_time : integer;
var res_double : float;
var results : string;

res_int:=0;
res_double:=0;

start_time:=GetTickCount();
for i:=0 to 10000000 do
begin
  res_int:=res_int+i*i;
  res_int:=res_int+1;
  res_double:=res_double+i*i;
  res_double:=res_double+1;
end;
start_time:=GetTickCount()-start_time;
{ вывод результатов - сначала соберем в строку результаты }
results:='Time: '+IntToStr(start_time)+' ms, ResInt='+FloatToStr(res_int)+
' ResDouble='+FloatToStr(res_double);
ShowMessage(results);


Тестирование проводилось на компьютере Intel P4, 2.8Ghz(HT), RAM 1Gb.
Результаты:

VC++: Time: 47 ms, ResInt=-752584127 ResDouble=17247836076609
MQL4: Time: 797 ms, ResInt=-752584127 ResDouble=17247836076609
MQL2: Time: 22156 ms, ResInt=333333383333717340000 ResDouble=333333383333717340000
WL3 : Time: 32422 ms, ResInt=3.33333383333717E20 ResDouble=3.33333383333717E20




На этих тестах видно, что MQL4 отстает от чистого кода VC++ в 17 раз.
MQL2 отстает от MQL4 в 27 раз и в 471 раз от VC++.
WL3 отстает от MQL4 в 40 раз и в 690 раз от VC++.

MQL4 и VC++ выдают один результат вычислений (правильный, с учетом типов переменных int и double).
MQL2 и WL3 выдают другой результат, так как у них все переменные всегда с плавающей запятой.

В расчетах индикаторов и экспертов очень часто используются циклы и другие целочисленные операции. Автоматическое использование всех переменных как double обходится очень дорого если думать о производительности.

Но протестируем производительность MQL4, когда все переменные с плавающей запятой? Вот код:

void start()
  {
   double res_int=0,i;
   int    start_time;
   double res_double=0;
//----
   start_time=GetTickCount();
   for(i=0;i<=10000000;i++)
     {
      res_int+=i*i;
      res_int++;
      res_double+=i*i;
      res_double++;
     }
   start_time=GetTickCount()-start_time;
//---- вывод результатов
   Print("Time: ",start_time," ms, ResInt=",res_int," ResDouble=",res_double);
//----
  }


И результат:

MQL4: Time: 906 ms, ResInt=333333383333717340000 ResDouble=333333383333717340000


Результаты совпадают с результатами MQL2 и WL3, а замедление исполнения по сравнению
с целочисленными счетчиками составило 13%.

Любой может повторить тесты (только в MQL2 есть ограничение в максимальное время 1 сек).

 
Всего то в 17 раз? Фантастика какая-то.
Может что-то добавить в тест, например вызов функций или еще чего :)
 
Всего то в 17 раз? Фантастика какая-то.


В MQL4 очень эффективный компилятор в p-код и еще более эффективная виртуальная
машина исполненения(написана на ассемблере).

Может что-то добавить в тест, например вызов функций или еще чего :)

Упор делался на проверку базовой математики - циклов и простейших вычислений.
Вызов функций в MQL4 тоже очень быстрый.
 
Вот тот же самый тест в Omega Research Prosuite 2000i:
DefineDLLfunc:"Kernel32.dll",INT, "GetTickCount"; 
Variable : res_int(0),i(0),start_time(0),res_double(0);

res_int=0;
res_double=0;
start_time=GetTickCount();

for i=0 to 10000000
begin
  res_int=res_int+i*i;
  res_int=res_int+1;
  res_double=res_double+i*i;
  res_double=res_double+1;
end;

start_time=GetTickCount()-start_time;
Alert("Time: "+NumToStr(start_time,0)+" ms, ResInt="+NumToStr(res_int,0)+" ResDouble="+
        NumToStr(res_double,0));


Для замеров времени используется таже самая стандартная функция GetTickCount(), импортируемая из kernel32.dll . И результат:

Easy: Time: 875 ms, ResInt=3369844457615319400 ResDouble=3369844457615319400


Поместим в общую таблицу:

VC++: Time: 47 ms, ResInt=-752584127 ResDouble=17247836076609
MQL4: Time: 797 ms, ResInt=-752584127 ResDouble=17247836076609
Easy: Time: 875 ms, ResInt=3369844457615319400 ResDouble=3369844457615319400
MQL2: Time: 22156 ms, ResInt=333333383333717340000 ResDouble=333333383333717340000
WL3 : Time: 32422 ms, ResInt=3.33333383333717E20 ResDouble=3.33333383333717E20



EasyLanguage показал достойный результат по скорости, но вот что-то у него не сложилось с точностью вычислений. Похоже что на каком-то этапе у него происходит переполнение. Если же уменьшить количество циклов (например, до 10000), то все расчеты у всех 5ти языков сходятся. Если кто-то найдет ошибку в коде - укажите, пожалуйста.

Через некоторое время мы представим обзорную статью с тестами языков еще нескольких систем. Естественно, тестовые примеры будут расширены. Если есть предложения - что именно (какие операции и функции) нужно протестировать, то пишите - согласуем тестовые примеры. Желательно чтобы код был универсальным и реализуемым в разных системах.

 
Сравнение скорости лучше проводить используя функцию clock, которая calculates the processor time used by the calling process, а не GetTickCount, которая retrieves the number of milliseconds that have elapsed since the system was started.

Приведённые результаты можно считать верными только в том случае, когда на тестовой машине не исполнялась ни одна другая задача и приоритет выполняемого процесса был поднят до time critical. Будем надеятся, что так оно и было, однако, Ренат, если не трудно, приведи результаты тестов с использованием функции clock для замера времени.
 
Приведённые результаты можно считать верными только в том случае, когда на тестовой машине не исполнялась ни одна другая задача и приоритет выполняемого процесса был поднят до time critical

Я все таки знаю условия соблюдения чистоты экспериментов. Замеры были проведены многократно и с практически одинаковыми результатами (погрешность на уровне 10-15 ms, чем можно явно пренебречь когда итоговые цифры 800, 22000 и 32000). В тестах важно было сделать общий обзор скорости исполнения. Тиковый счетчик можно использовать разве что для замера скорости выполнения VC++ кода, но с VC++ кодом все понятно.

Результатом стало:
1) MQL4 и Easy показали практически один результат, что говорит об одинаковом качестве скомпилированного кода и виртуальной машине исполнения. Это достаточно эффективные компиляторы.
2) MQL2 и WL3 по сути являются интерпретаторами, что сильно сказывается на скорости выполнения
3) Easy выдал ошибочный результат из-за переполнений (при меньшем счетчике все выдавал правильно)

если не трудно, приведи результаты тестов с использованием функции clock

Именно трудно - откуда ее взять в этих языках? К счастью, clock совсем не нужен - достаточно поднять объем вычислений чтобы воспользоваться более грубым счетчиком.
 
Тест в AmiBroker 4.40:

res_int=0;
start_time=0;
res_double=0;
//----
start_time=GetTickCount();
for(i=0;i<=10000000;i++)
  {
   res_int=res_int+i*i;
   res_int++;
   res_double=res_double+i*i;
   res_double++;
  }
start_time=GetTickCount()-start_time;
//---- вывод результатов в окно Interpretation
str="Time: "+start_time+" ms, ResInt="+res_int+" ResDouble="+res_double;


Так как AmiBroker не содержит в себе функций замера скорости, то пришлось написать простую DLL c функцией, выдающей результат от штатной GetTickCount.

Вот результаты:

Ami : Time: 12626 ms, ResInt=3.36984e+020 ResDouble=3.36984e+020


Результаты математических вычислений опять отличаются - даже странно.
Кто-нибудь сможет найти ошибку?

Эталоном расчетов является следующий код на VC++ (за правильный результат ручаюсь):

void main()
  {
   double res_int=0,i;
   int    start_time;
   double res_double=0;
//----
   start_time=GetTickCount();
   for(i=0;i<=10000000;i++)
     {
      res_int+=i*i;
      res_int++;
      res_double+=i*i;
      res_double++;
     }
   start_time=GetTickCount()-start_time;
//----
   printf("Time: %d ms, ResInt=%lf ResDouble=%lf\n",start_time,res_int,res_double);
  }


с результатом вычислений: ResDouble=333333383333717340000

И итоговое распределение мест:

VC++: Time: 47 ms, ResInt=-752584127 ResDouble=17247836076609
MQL4: Time: 797 ms, ResInt=-752584127 ResDouble=17247836076609
Easy: Time: 875 ms, ResInt=3369844457615319400 ResDouble=3369844457615319400
Ami : Time: 12626 ms, ResInt=3.36984e+020 ResDouble=3.36984e+020
MQL2: Time: 22156 ms, ResInt=333333383333717340000 ResDouble=333333383333717340000
WL3 : Time: 32422 ms, ResInt=3.33333383333717E20 ResDouble=3.33333383333717E20
 

Результаты математических вычислений опять отличаются - даже странно.
Кто-нибудь сможет найти ошибку?

Overflow in conversion or arithmetic operation
 
Я все таки знаю условия соблюдения чистоты экспериментов.

Ренат, я не отрицал твоей компетентности, более того, считаю, что человек занятый в таком серьёзном проекте не может этого не знать. Просто я сам не раз сталкивался с не совсем адекватным поведением GetTickCount(); Хотя, если результаты множества замеров были устойчивыми +/- некоторая маленькая дельта, то это подтверждает верность тестов.
Именно трудно - откуда ее взять в этих языках?

Либо сразу импортировать из msvcrt.dll либо написать свою dll - wrapper.
Ну да ладно, я согласен, что результаты достаточно точные, а главное показательные.
 
Overflow in conversion or arithmetic operation


Вообще-то переполнения должны быть арифметически "правильными/предсказуемыми", но не
"результат неопределен". Да, зачастую в документации именно так и пишется "результат неопределен", но результат предсказуем.

VC++(счетчик в double), MQL2, MQL4(счетчика в double) дают правильный результат
ResDouble=333333383333717340000

WL3 - правильно с маленькой погрешностью ResDouble=3.33333383333717E20

А EasyLanguage и AmiBroker - совершенно левый результат.

Пример для тестов был взят совершенно простой, но вылезшие расхождения оказались интересными - поэтому я не стал переделывать тестовый код. В следующих тестах будет подобран код с результатами без переполнений. Планируется сделать серию тестов по работе с различными данными: обычные мат операции, циклы, работа с массивами, доступ к рабочим графикам, вызов функций.
 
Просто я сам не раз сталкивался с не совсем адекватным поведением GetTickCount(); Хотя, если результаты множества замеров были устойчивыми +/- некоторая маленькая дельта, то это подтверждает верность тестов.


Да, точность GetTickCount() прыгает от 10 до 16 мс и им нельзя мерить мелкие периоды, но в нашем случае его использование обосновано. Особенно, когда к нему так легко достучаться из почти любого кода.