Утечка памяти в EmulationTrader + LocalMarketDataDrive
Atom Ответить
15.10.2013


Гоняю 1000 итераций, где в каждой создаю EmulationTrader и все необходимое, он отрабатывает, уничтожаю объекты. Память накапливается, и программа заканчивается с OutOfMemory Exception.
Вызываю для всех объектов где надо Dispose(), где надо null, отписываюсь от событий, но память все равно течет.

Профайлером ANTS Memory Profiler я увидел, что продолжают висеть в памяти EmulationTrader-ы и LocalMarketDateDrive-ы. На них ссылаются делегаты (видимо, их же обработчики, подписавшиеся на события извне). Вот последовательность взаимных ссылок, которые держат объекты (читать лучше снизу вверх: удерживаемый объект - внизу, корневая удерживающая ссылка - вверху):

1. Делегат LocalMarketDataDrive-а
Код
static LazyHelpers _delegates (references to ->)
   Ecng.Collections.SyncrhonizedDictionary<Object, Delegate> _inner ->
     Dictionary<Object, Delegate>  entries ->
       it's [0].Value  ->
         (delegate) System.Func<CachedSyncronizedOrderDictionary<DateTime, DateTime>> (inside LocalMarketDataDrive) ->
           LocalMarketDataDrive


2. Делегат EmulationTrader-а
Код
 static List<Ecng.ComponentModel.EventsContainer> _containers ->
   it's _items[0] -> 
     EventsContainer<Security> _processDataError ->
       (delegate) System.Action<Exception> (inside EmulationTrader) ->
         EmulationTrader (internal field #=q.....GZQ==)->
           LocalMarketDataDrive 


Поскольку у меня нет исходников S#, то догадки свои проверить не могу. Могу прислать дампы программы для анализа. Ну, и привести кусочки кода создания/освобождения объектов.
Буду очень благодарен за исправления!

UPD. Версия последняя, 4.1.19.1



Спасибо: Bond Mikhail Sukhov




12 Ответов
Bond

Фотография
Курсы
Дата: 16.10.2013
Ответить


pafnuty Перейти
Гоняю 1000 итераций, где в каждой создаю EmulationTrader и все необходимое, он отрабатывает, уничтожаю объекты. Память накапливается, и программа заканчивается с OutOfMemory Exception.
Вызываю для всех объектов где надо Dispose(), где надо null, отписываюсь от событий, но память все равно течет.

Профайлером ANTS Memory Profiler я увидел, что продолжают висеть в памяти EmulationTrader-ы и LocalMarketDateDrive-ы. На них ссылаются делегаты (видимо, их же обработчики, подписавшиеся на события извне). Вот последовательность взаимных ссылок, которые держат объекты (читать лучше снизу вверх: удерживаемый объект - внизу, корневая удерживающая ссылка - вверху):

1. Делегат LocalMarketDataDrive-а
Код
static LazyHelpers _delegates (references to ->)
   Ecng.Collections.SyncrhonizedDictionary<Object, Delegate> _inner ->
     Dictionary<Object, Delegate>  entries ->
       it's [0].Value  ->
         (delegate) System.Func<CachedSyncronizedOrderDictionary<DateTime, DateTime>> (inside LocalMarketDataDrive) ->
           LocalMarketDataDrive


2. Делегат EmulationTrader-а
Код
 static List<Ecng.ComponentModel.EventsContainer> _containers ->
   it's _items[0] -> 
     EventsContainer<Security> _processDataError ->
       (delegate) System.Action<Exception> (inside EmulationTrader) ->
         EmulationTrader (internal field #=q.....GZQ==)->
           LocalMarketDataDrive 


Поскольку у меня нет исходников S#, то догадки свои проверить не могу. Могу прислать дампы программы для анализа. Ну, и привести кусочки кода создания/освобождения объектов.
Буду очень благодарен за исправления!

UPD. Версия последняя, 4.1.19.1


Добрый день, pafnuty!
Я тоже в одно время интересовался утечками памяти. Обещали исправить, но пока видимо не успешно.
Позвольте поинтересоваться с какой целью вы проводите так много итераций? Если вы пишите оптимизитор для тестирования стратегий, то было бы здорово, если вы поделитесь своими мыслями по этому поводу. Просто мы сейчас тоже работаем над этим.
Спасибо:

Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 16.10.2013
Ответить


Спасибо за фидбэк. А возможно ли сделать некий мини код (желательно с теми маркет-данными, что идут в нашем дистрибутиве), который бы демонстрировал утечку на итерациях?

Сейчас мы переделываем эмулятор на новый подход, и было бы очень кстати до нового релиза получить такой код. Возможно, мы могли бы его добавить как юнит тест для предотвращения в будущем таких ситуаций.
Спасибо:

pafnuty

Фотография
Программист
Дата: 16.10.2013
Ответить


Mikhail Sukhov
1. Утечку я устранил пока такими заплатками:
Код
      var value = typeof(LazyHelper).GetField("_delegates", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
      value.GetType().GetMethod("Clear").Invoke(value, null);

      value = typeof(EventsContainer).GetField("_containers", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
      value.GetType().GetMethod("Clear").Invoke(value, null);

Утечка оказалась существенной - на каждой итерации ~ +1Мб.
Незначительные лики в StockSharp зацепились здесь (возможно, что и не лики вовсе - буду еще гонять):
Код
ExchangeBoard k__BackingField (удерживает под собой много чего по мелочи)

(Еще какие-то String-и висят и массивы... но это уже что-то мое, а не StockSharp.)

2. Мини-код для воспроизведения я постараюсь подготовить к следующей неделе (время терпит?)

Bond
Да, делаю оптимизатор. Мысли у меня такие: у стратегии есть набор параметров, от которых она зависит. Оптимизатор перебирает их в заданном диапазоне (включая диапазон дат), для каждой уникальной комбинации параметров запускает тест (по схеме, предложенной здесь: Тестирование на истории (оптимизация)). Набор параметров и результаты теста (все данные из StatisticManager, а также накопленную историю Orders, PnLs, Positions) он накапливает в базу данных. Для распределенного тестирования на нескольких машинах планирую использовать либо удаленный сервер БД, либо репликацию между локальными серверами (если медленное соединение, либо сервер не всегда доступен).
Автор топика
Спасибо: Mikhail Sukhov Bond

Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 16.10.2013
Ответить


pafnuty Перейти
Mikhail Sukhov
1. Утечку я устранил пока такими заплатками:
Код
      var value = typeof(LazyHelper).GetField("_delegates", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
      value.GetType().GetMethod("Clear").Invoke(value, null);


У вас Security для одного и того же тикера используется один и тот же или на каждой итерации свой объект?
Спасибо:

Bond

Фотография
Курсы
Дата: 16.10.2013
Ответить


pafnuty Перейти

Bond
Да, делаю оптимизатор. Мысли у меня такие: у стратегии есть набор параметров, от которых она зависит. Оптимизатор перебирает их в заданном диапазоне (включая диапазон дат), для каждой уникальной комбинации параметров запускает тест (по схеме, предложенной здесь: Тестирование на истории (оптимизация)). Набор параметров и результаты теста (все данные из StatisticManager, а также накопленную историю Orders, PnLs, Positions) он накапливает в базу данных. Для распределенного тестирования на нескольких машинах планирую использовать либо удаленный сервер БД, либо репликацию между локальными серверами (если медленное соединение, либо сервер не всегда доступен).


Доброго времени суток!
Мы тоже сейчас пытаемся определиться с архитектурой оптимизатора.
С количеством оптимизируемых параметров:
- таймфрейм,
- веса индикаторов,
- время торговли,
- объемы вхождения в сделки и т.д.
При этом для первых проверок реализовать графический интерфейс (см. приложение) для визуальной оценки работы стратегии, анализа, корректировки. Для более тонкой и производительной настройки делать уже без визуализации для производительности.
Кстати, я тоже начал делать оптимизатор на базе примера из Sample с BasketTrader.
Если интересно могу сбросить код. Пример работает на исторических данных отсюда Исторические данные. А вообще присоединяйтесь в наш чат TFS. Там все. Одна голова хорошо, а десять лучше!) Может сделаем общими усилиями нормальный производительный оптимизатор.
В процессе работы вы как планируете хранить промежуточные данные? Собирать в коллекцию и в конце итерации выводить или выгружать данные в файл?
Можно как образец отчета стащить из Велс Лаба? (см. приложение)
Какие варианты оптимизации вычислений лучше применить? Вложенные циклы? Parallel Extensions?
Введение в технику оптимизации циклов
Parallel Extensions
Как вы обращаетесь к историческим данным? К тикам или уже готовым свечам?
Работа со StorageRegistry
В общем есть о чем поговорить, добавляйтесь в чаты)))
Спасибо:

pafnuty

Фотография
Программист
Дата: 17.10.2013
Ответить


Михаил Сухов Перейти
pafnuty Перейти
Mikhail Sukhov
1. Утечку я устранил пока такими заплатками:
Код
      var value = typeof(LazyHelper).GetField("_delegates", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
      value.GetType().GetMethod("Clear").Invoke(value, null);


У вас Security для одного и того же тикера используется один и тот же или на каждой итерации свой объект?


На каждый тест - новый Trader, Security (получаю копию исходного экземпляра из Clone() перед запуском теста), Strategy(тоже Clone()) и сейчас даже StorageRegistry. Вот тут, кстати, у меня вопрос - а StorageRegistry можно не плодить? Я предполагал оставить один экземпляр для всех Trader-ов.

Bond

Спасибо за приглашение, как будет время - обязательно загляну!

У меня оптимизатор работает с переменным количеством параметров. Стратегия сама выдает список оптимизируемых параметров и принимает на вход очередную комбинацию. Задаются диапазоны каждого параметра (From, To, Step) с помощью простенького ГУИ. (см. приложенную картинку) Есть класс-итератор, он выдает очередную комбинацию параметров из этого диапазона.

Для возможного ускорения работы я оставил в качестве параметра значение "сколько EmulationTrader-ов одновременно запускать в BasketTrader-е". Если памяти и ядер много, так можно и по 4 штуки за раз пустить - это уже хорошо. :) Один трейдер отработал, вышел из BasketTrader-а, вместо себя запустил нового.

Вообще я сейчас не заморачиваюсь с увеличением производительности. Я вообще все делаю по возможности максимально простым способом. :) Для меня сейчас важно, чтобы просто работало, собирало данные, желательно быстро и чтобы не падало, но при этом с минимальными трудозатратами с моей стороны. А дальше уже, может быть, займусь ускорением работы.

Хотя, займусь этим вряд ли. Я использую событийную модель - в ней и так уже куча потоков запускается. Перенести на GPU считаю невозможным - 1) имея поверхностные познания в этой области, я не совсем представляю, как там правильно все связать с ООП, 2) если обрабатывать большие массивы юзер-данных по стандартной модели - так это уже и не событийная модель получается (или не совсем она). Значит, надо изобретать, надо писать свой StockSharp заново под GPU. Смысла в этом я пока не вижу. Хотя, для общего развития можно было бы убить пару месяцев. (у меня их, к сож, пока нет) Я верю, что скорее NVidia сделает свои GPU совместимыми с x86/64, вот тогда мы похохочем. :)

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

Конечно, построение графиков со свершенными сделками может понадобится для анализа, и поэтому после каждой итерации я просто выгружаю данные из коллекций в базу - это быстро и эффективно. (Тут один нюанс: я решил, что пока мне нужен робот, ограниченный одним днем, и поэтому каждая итерация ограничена одним днем, т.е. в конце итерации получается не очень крупный набор данных.) Потом, если надо, графику можно подгрузить в другом приложении. Т.е. оптимизация мне видится просто процессом сбора данных. Для анализа сделок и корректировки стратегий у меня отдельные два приложения - вот они как раз с графиками, которые можно строить как с нуля, так и по собранным данным оптимизатора.

Стратегия, которую я сейчас оптимизирую, работает с тиками (получаю просто BestPair из стаканов).
1.JPG 29,2KB (0) 2.JPG 24,1KB (0)
Автор топика
Спасибо:

Bond

Фотография
Курсы
Дата: 18.10.2013
Ответить


pafnuty Перейти
Михаил Сухов Перейти
pafnuty Перейти
Mikhail Sukhov
1. Утечку я устранил пока такими заплатками:
Код
      var value = typeof(LazyHelper).GetField("_delegates", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
      value.GetType().GetMethod("Clear").Invoke(value, null);


У вас Security для одного и того же тикера используется один и тот же или на каждой итерации свой объект?


На каждый тест - новый Trader, Security (получаю копию исходного экземпляра из Clone() перед запуском теста), Strategy(тоже Clone()) и сейчас даже StorageRegistry. Вот тут, кстати, у меня вопрос - а StorageRegistry можно не плодить? Я предполагал оставить один экземпляр для всех Trader-ов.

Bond

Спасибо за приглашение, как будет время - обязательно загляну!

У меня оптимизатор работает с переменным количеством параметров. Стратегия сама выдает список оптимизируемых параметров и принимает на вход очередную комбинацию. Задаются диапазоны каждого параметра (From, To, Step) с помощью простенького ГУИ. (см. приложенную картинку) Есть класс-итератор, он выдает очередную комбинацию параметров из этого диапазона.

Для возможного ускорения работы я оставил в качестве параметра значение "сколько EmulationTrader-ов одновременно запускать в BasketTrader-е". Если памяти и ядер много, так можно и по 4 штуки за раз пустить - это уже хорошо. :) Один трейдер отработал, вышел из BasketTrader-а, вместо себя запустил нового.

Вообще я сейчас не заморачиваюсь с увеличением производительности. Я вообще все делаю по возможности максимально простым способом. :) Для меня сейчас важно, чтобы просто работало, собирало данные, желательно быстро и чтобы не падало, но при этом с минимальными трудозатратами с моей стороны. А дальше уже, может быть, займусь ускорением работы.

Хотя, займусь этим вряд ли. Я использую событийную модель - в ней и так уже куча потоков запускается. Перенести на GPU считаю невозможным - 1) имея поверхностные познания в этой области, я не совсем представляю, как там правильно все связать с ООП, 2) если обрабатывать большие массивы юзер-данных по стандартной модели - так это уже и не событийная модель получается (или не совсем она). Значит, надо изобретать, надо писать свой StockSharp заново под GPU. Смысла в этом я пока не вижу. Хотя, для общего развития можно было бы убить пару месяцев. (у меня их, к сож, пока нет) Я верю, что скорее NVidia сделает свои GPU совместимыми с x86/64, вот тогда мы похохочем. :)

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

Конечно, построение графиков со свершенными сделками может понадобится для анализа, и поэтому после каждой итерации я просто выгружаю данные из коллекций в базу - это быстро и эффективно. (Тут один нюанс: я решил, что пока мне нужен робот, ограниченный одним днем, и поэтому каждая итерация ограничена одним днем, т.е. в конце итерации получается не очень крупный набор данных.) Потом, если надо, графику можно подгрузить в другом приложении. Т.е. оптимизация мне видится просто процессом сбора данных. Для анализа сделок и корректировки стратегий у меня отдельные два приложения - вот они как раз с графиками, которые можно строить как с нуля, так и по собранным данным оптимизатора.

Стратегия, которую я сейчас оптимизирую, работает с тиками (получаю просто BestPair из стаканов).


Добрый день!
У Вас совершенно правильный подход. Одно приложение должно торговать на готовых стратегиях. Другое для анализа и пробы стратегий. Третье для оптимизации стратегий. У каждого свои задачи, своя производительность. Плюс разделение рисков от возможных поломок.
Кстати, я тоже придерживаюсь интрадея. Маленькие таймфреймы без всяких переносов через ночь.
Возможно стоит сделать один StorageRegistry, Portfolio и Security. А итерировать только стратегию. Здесь, конечно, нужно еще подумать.
Будет здорово, если вы сможете поделиться вашими наработками оптимизатора, мы в свою очередь постараемся его усовершенствовать, в перспективе довести до ума и добавить в саму платформу S#. У нас сейчас активно накапливаются идеи в репозитарии, некоторые товарищи даже работают над нейроснтями так, что присоединяйтесь!
Спасибо:

pafnuty

Фотография
Программист
Дата: 18.10.2013
Ответить


Оффтоп:
Bond
А давайте вынесем наш с вами разговор в отдельный топик, чтобы не засорять главную тему. Отношения к утечкам памяти он не имеет.
В будущем будем стараться делать это сразу. Я вот что-то не уследил. :)
Автор топика
Спасибо:

pafnuty

Фотография
Программист
Дата: 18.10.2013
Ответить


Михаил Сухов Перейти
У вас Security для одного и того же тикера используется один и тот же или на каждой итерации свой объект?

Забыл сказать, что и Portfolio тоже для каждого теста новый создается.
Автор топика
Спасибо:

Mikhail Sukhov

Фотография
Автор статей Программист Трейдер
Дата: 18.10.2013
Ответить


pafnuty Перейти
Михаил Сухов Перейти
У вас Security для одного и того же тикера используется один и тот же или на каждой итерации свой объект?

Забыл сказать, что и Portfolio тоже для каждого теста новый создается.


Да, спасибо, уже примерно понятно из-за чего.
Спасибо:

pafnuty

Фотография
Программист
Дата: 20.10.2013
Ответить


Михаил Сухов Перейти
Спасибо за фидбэк. А возможно ли сделать некий мини код (желательно с теми маркет-данными, что идут в нашем дистрибутиве), который бы демонстрировал утечку на итерациях?

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


1. Вот тестовый проект: http://yadi.sk/d/omfHs1P0BFfZq

Не очень мини получилось - я просто выцепил кусок своей программы, выкинул все ненужное. Стратегию взял из SampleHistoryTesting, но переделал ее на свой лад, как я работал с тиками (чтобы можно было увидеть мои утечки, а заодно и средства борьбы с ними). Я думаю, что можно также прозрачно подставить туда и исходную SmaStrategy, только переопределить в ней Clone() и добавить конструктор по умолчанию. Ну, и CandleManager подцепить, сейчас я работал без него.

Сразу отмечу, что пока я не гонял этот код на свои собственные утечки. За основу брал свою последнюю версию, а что получилось после изменений, не проверил.

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


2. Попутно я боролся с утечками. После тех брутальных Clear() я смог еще кое-что обнаружить и убить (наверное, неаккуратно). Удалось сбросить до ~60Мб на 1000 итераций.

  • Течет немножко MarketEmulator. Сделал ему Dispose(). После Dispose() все равно какие-то ссылки на SyncrhonizedList<Order>, SyncrhonizedDictionary<MyTrade>, Dictionary<Security, MarketEmulator+#какая-то хрень#>, там же где-то MarketDepth и с ним Quote[]. Где они там, и как их победить, я не разобрался.

  • У EmulationTrader после Dispose() тоже что-то висит: SyncrhonizedDictionary<MyTrade>, BlockingQueue<Message>, SyncrhonizedDictionary<Order, SyncrhonizedList<MyTrade> >. От него остался висеть BaseTrader, в котором висят CachedSynchronizedList<MyTrade>. И т.д.

  • Есть еще какие-то два static SyncrhonizedDictionary<Order, #какая-то хрень#>, в которых накапливаются Order и DateTime, у которых в свою очередь _trader держит уже отDisposeнный EmulationTrader.

Возможно, брутальные Clear() что-то неаккуратно чистят. Я не пробовал разные схемы.
Автор топика
Спасибо: Bond

pafnuty

Фотография
Программист
Дата: 28.10.2013
Ответить


Обнаружил тут, что в своем исходнике я 2 раза подписывался/отписывался на стаканы для одного и того же Security (в обоих случаях один и тот же объект - клон):

  • в OptimizerTest.CreateTrader()/ReleaseTrader()
  • и в MarketDephtSmaStrategy.OnStarted()/OnStopping()


Возможно, это тоже приводит к утечке (хотя, я так не думаю).
Автор топика
Спасибо:


Добавить файлы через драг-н-дроп, , или вставить из буфера обмена.

loading
clippy