Библиотека Generic классов - ошибки, описание, вопросы, особенности использования и предложения - страница 12

 
Sergey Dzyublik:

Вы понимаете что делает код, если отсутствует явная реализация функции GetHashCode для типа T?
Ответ: пакость, поскольку замалчивается проблема отсутствия реализации. Все объекты одного и того же класса будут возвращать одинаковое значение хеша.

Да причем тут реализация (тело)?! Вот это добавил

int GetHashCode(T &value)

тело вставил от балды.

 
fxsaber:

Да причем тут реализация (тело)?! Вот это добавил

тело вставил от балды.


Вам рассказывают о мухах (что так делать не стоит, код заболеть может в будущем), а вы дальше о котлетах.
Ладно, хорошего аппетита.

 

Решил посмотреть скоростные характеристики предложенного решения. Советник для тестера

#include <MT4Orders.mqh>
#include <Generic\HashMap.mqh>

CHashMap<ulong, double> DealsProfit;

// Создаем историю из Amount сделок в тестере
void CreateHistory( const int Amount, const double Lots = 0.1 )
{
  MqlTick Tick;
  
  if (SymbolInfoTick(_Symbol, Tick) && Tick.ask && Tick.bid)
    for (int i = (Amount >> 1) - 1; i >= 0; i--)
      OrderClose(OrderSend(_Symbol, OP_BUY, Lots, Tick.ask, 0, 0, 0), Lots, Tick.bid, 0);
}

// Заполняем массив случайно выбранными сделками
void GetDeals( const int Amount, const int MaxDealTicket, ulong &Deals[] )
{
  for (int i = ArrayResize(Deals, Amount) - 1; i >= 0; i--)  
    Deals[i] = MathRand() * MaxDealTicket / SHORT_MAX;
}

// Заполнили HashMap
void SetHashMap()
{
  if (HistorySelect(0, INT_MAX))
    for (int i = HistoryDealsTotal() - 1; i >= 0; i--)
    {
      const ulong DealTicket = HistoryDealGetTicket(i);
      
      DealsProfit.Add(DealTicket, HistoryDealGetDouble(DealTicket, DEAL_PROFIT));
    }
}

double GetDealProfitHashClear( const ulong Deal )
{
  static double Profit = 0;
  
  return(DealsProfit.TryGetValue(Deal, Profit) ? Profit : 0);
}

double GetDealProfitFull( const ulong Deal )
{
  return(HistoryDealSelect(Deal) ? HistoryDealGetDouble(Deal, DEAL_PROFIT) : 0);
}

double GetDealProfitClear( const ulong Deal )
{
  return(HistoryDealGetDouble(Deal, DEAL_PROFIT));
}

typedef double (*GetDealProfit)( const ulong );

// Находим суммарный профит сделок из массива
double SumProfit( const ulong &Deals[], GetDealProfit DealProfit )
{
  double Profit = 0;
  
  for (int i = ArraySize(Deals) - 1; i >= 0; i--)
    Profit += DealProfit(Deals[i]);
    
  return(Profit);
}

#define BENCH(A)                                                              \
{                                                                             \
  const ulong StartTime = GetMicrosecondCount();                              \
  A;                                                                          \
  Print("Time[" + #A + "] = " + (string)(GetMicrosecondCount() - StartTime)); \
} 

int OnInit()
{
  const int Amount = 100000;  
  CreateHistory(Amount); // Создаем историю из Amount сделок в тестере
  
  ulong Deals[];
  GetDeals(Amount, Amount, Deals); // Заполняем массив случайно выбранными сделками

  // Находим суммарный профит сделок из массива
  
  BENCH(Print(SumProfit(Deals, GetDealProfitFull))); // Полноценная классическая реализация
  
  BENCH(SetHashMap()); // Заполнили HashMap
  BENCH(Print(SumProfit(Deals, GetDealProfitHashClear))); // Реализация через HashMap
  
  BENCH(HistorySelect(0, INT_MAX));
  BENCH(Print(SumProfit(Deals, GetDealProfitClear))); // Реализация с предварительно загруженной историей
  
  return(INIT_FAILED);
}

Советник открывает 100 000 сделок, затем ищет суммарный профит случайных сделок разными методами (см. комментарии). Результат

2017.12.05 00:00:00   -13133.19999999244
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitFull))] = 38082
2017.12.05 00:00:00   Time[SetHashMap()] = 57849
2017.12.05 00:00:00   -13133.19999999244
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitHashClear))] = 7437
2017.12.05 00:00:00   Time[HistorySelect(0,INT_MAX)] = 1
2017.12.05 00:00:00   -13133.19999999244
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitClear))] = 31669

Ну вот сравниваем два выделенных показателя. Получается, что HashMap-доступ в 4 раза быстрее того, что у разработчиков. Но у разработчиков он уже включает историю...

В 4 раза - много это или мало для данной ситуации? Ну здесь это 24 миллсекунды. Если очень много раз обращаться к истории, то, наверное, получится заметно сэкономить. Но не уверен.


Для более реального тестерного случая (2000 сделок и 1 000 000 единичных обращений к истории) результат выглядит так

2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitFull))] = 122969
2017.12.05 00:00:00   Time[SetHashMap()] = 816
2017.12.05 00:00:00   4829800340.792288
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitHashClear))] = 23852
2017.12.05 00:00:00   Time[HistorySelect(0,INT_MAX)] = 1
2017.12.05 00:00:00   4829800340.792288
2017.12.05 00:00:00   Time[Print(SumProfit(Deals,GetDealProfitClear))] = 114427

Почти 100 мс экономии на проход! Если, допустим, делаем Оптимизацию на 10 000 полноценных проходов, то Hash-вариант закончится на 15 минут быстрее.

Разработчикам за реализацию работы с Историей твердую пятерку ставить пока рано. Видно, что могут ускорить, раз даже MQL-решение уделывает.

 
fxsaber:

Решил посмотреть скоростные характеристики предложенного решения. Советник для тестера

Советник открывает 100 000 сделок, затем ищет суммарный профит случайных сделок разными методами (см. комментарии). Результат

Ну вот сравниваем два выделенных показателя. Получается, что HashMap-доступ в 4 раза быстрее того, что у разработчиков. Но у разработчиков он уже включает историю...

В 4 раза - много это или мало для данной ситуации? Ну здесь это 24 миллсекунды. Если очень много раз обращаться к истории, то, наверное, получится заметно сэкономить. Но не уверен.

В вызовах через платформу вы дважды в GetDealProfitFull и однократно в GetDealProfitClear проходите через объекты синхронизации и массу обязательных проверок на каждой итерации.

Поэтому скорость заведомо ниже, чем в чистой и оптимизированной с кучей инлайнов работе по заранее приготовленному локальному хешмапу.

 
Renat Fatkhullin:

В вызовах через платформу вы дважды в GetDealProfitFull и однократно в GetDealProfitClear проходите через объекты синхронизации и массу обязательных проверок на каждой итерации.

Поэтому скорость заведомо ниже, чем в чистой и оптимизированной с кучей инлайнов работе по заранее приготовленному локальному хешмапу.

Поправил свой предыдущий пост. Дважды проверка поэтому и называется Full.

Не совсем понимаю, о каких дорогих объектах синхронизации и массе проверок идет речь в Тестере для HistoryDealGetDouble?
 
fxsaber:

Поправил свой предыдущий пост. Дважды проверка поэтому и называется Full.

Не совсем понимаю, о каких дорогих объектах синхронизации и массе проверок идет речь в Тестере для HistoryDealGetDouble?

В чем разница:

  1. для хешмап теста вы предварительно переложили данные в локальное хранилище с быстрым несинхронизированным доступом, а в платформенных вызовах надо проваливаться внутрь хранилища
  2. в платформе хранилище другого вида, не хешмап
  3. в платформе при извлечении одиночного значения нужно к запросу относиться "как в первый раз", перепроверяя корректность и наличие всех данных. мало ли что изменилось между вызовами


Я посмотрел наш код - есть возможность оптимизировать вызовы к базе торговых операций. Попробуем к релизу на следующей неделе реализовать.

 

Renat Fatkhullin:

в платформе при извлечении одиночного значения нужно к запросу относиться "как в первый раз", перепроверяя корректность и наличие всех данных

А разве при вызове TryGetValue не делается проверка на корректность? По логам видно, что HistorySelect  в тестере бесплатная.

Вот чего совсем не понимаю, почему, чтобы получить все данные по сделке, нужно вызвать кучу дорогих HistoryDealGet*-функций? Ведь напрашивается всего один вызов с заполнением MqlDeal-структуры.

Очевидно, что пользователь, когда захочет через HashMap работать с историей, заполнит именно CHashMap<ulong, MqlDeal>.

Может, сделать MqlDealInteger, MqlDealDouble, MqlDealString или что-то подобное, чтобы не плодить дорогие юнитные вызовы, раз уж в тестере все равно вся историческая таблица находится? И проверять корректность тогда DealTicket достаточно один раз, а не каждый.

 
fxsaber:

А разве при вызове TryGetValue не делается проверка на корректность? По логам видно, что HistorySelect  в тестере бесплатная.

Как это бесплатная? Совсем не бесплатная.


Вот чего совсем не понимаю, почему, чтобы получить все данные по сделке, нужно вызвать кучу дорогих HistoryDealGet*-функций? Ведь напрашивается всего один вызов с заполнением MqlDeal-структуры.

Очевидно, что пользователь, когда захочет через HashMap работать с историей, заполнит именно CHashMap<ulong, MqlDeal>.

У нас нет структуры MqlDeal, так как форматы торговых записей плавающие и периодически расширяющиеся. Без этого нельзя расширять функционал платформы.

Поэтому единственный вариант, это доступаться к ним через Get функции. Причем доступ к остальным полям ранее затронутой записи в разы быстрее первого обращения, так как запись ложится в кеш.

И проверять корректность тогда DealTicket достаточно один раз, а не каждый.

В тесте выше номера сделок каждый раз новые, что постоянно сбивает кеш ранее выбранной сделки. А также нет гарантии что между вызовами что-то не поменялось. Вы ведь еще торгануть можете между запросами к истории.

 
Renat Fatkhullin:
Как это бесплатная? Совсем не бесплатная.

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Библиотека Generic классов - ошибки, описание, вопросы, особенности использования и предложения

fxsaber, 2017.12.08 22:46

Советник открывает 100 000 сделок, затем ищет суммарный профит случайных сделок разными методами (см. комментарии). Результат

2017.12.05 00:00:00   Time[HistorySelect(0,INT_MAX)] = 1

На 100 000 сделок (и столько же ордеров) 1 микросекунда - бесплатно же. Речь все время про тестер.

В тесте выше номера сделок каждый раз новые, что постоянно сбивает кеш ранее выбранной сделки. А также нет гарантии что между вызовами что-то не поменялось. Вы ведь еще торгануть можете между запросами к истории.

Так история (особенно в тестере) только дополняется, старые записи не изменяются. Речь про Clear-вариант.


На реале, вроде, даже когда ордер исполняется частично и порождает несколько сделок, он не попадет в историю, пока полностью не зафиллится или не будет отменен. Т.е. сохраняется правило замороженной истории.

 
fxsaber:

На 100 000 сделок (и столько же ордеров) 1 микросекунда - бесплатно же. Речь все время про тестер.

HistorySelect в тестере абсолютно виртуален/фейковый, тем более с параметрами 0, INT_MAX. Это давным давно оптимизировали.

Нельзя сопоставлять HistorySelect(ставит range доступа в тестере) и HistoryDealSelect(ticket), который реально ищет конкретный тикет и кеширует его.

Причина обращения: