Проблемы с производительностью в методе Connector.GetSecurity()


Проблемы с производительностью в методе Connector.GetSecurity()
Atom
09.06.2020


В последних коммитах была внесена серьезная проблема с производительностью в методе Connector.GetSecurity(). Ранее там использовался entityCache, который позволял быстро получить Security по идентификатору. Теперь там идет использование цепочки методов: TraderHelper.GetOrCreate() -> TraderHelper.LookupById() -> InMemorySecurityStorage.Lookup() -> TraderHelper.Filter() -> Messages.Extensions.Filter()

Метод TraderHelper.LookupById() хоть и должен вернуть только один единственный объект, вызывает все равно метод InMemorySecurityStorage.Lookup(), который работает с коллекцией Security.
Метод InMemorySecurityStorage.Lookup() является более универсальным и работает с коллекцией, он всегда вынужден перебирать коллекцию, проверяя критерий на каждом Security. Он вызывает метод TraderHelper.Filter() на всей коллекции сохраненных Security.
Метод TraderHelper.Filter() вызывает сначала преобразование коллекции securities в Dictionary с ключами типа SecurityMessage, что вызывает перебор всей коллекции с достаточно тяжелым кодом конверсии Security -> SecurityMessage, а затем вызывает на ключах этого словаря метод Messages.Extensions.Filter().
Метод Messages.Extensions.Filter() снова перебирает всю коллекцию, но на этот раз сообщений SecurityMessage, только что созданных из Security.

Изначальный метод Connector.GetSecurity() вызывается на каждом входящем сообщении из коннектора.

Таким образом имеем следующее.
Допустим, что у нас есть очередь из 1000000 входящих сообщений (загрузка тиковой истории) и всего в коннектор загружено у нас 100 инструментов. На каждом входящем сообщении мы имеем два перебора коллекции securities с достаточно тяжелым кодом, выполняющемся на каждом переборе, т.е. у нас получается 1000000 * 200 переборов элементов securities. Каждый перебор - это достаточно тяжелый код по созданию словаря и конверсии всех элементов, а также по вызову сборки мусора, т.к. создется очень много временных объектов нулевого поколения. Чем больше занружено инструментов, тем медленнее (мультипликативно!) это будет работать. ВременнАя сложность текущего кода получается: 1000000 * 200 * X, где X - время, которое тратится на один элемент.

Совершенно очевидно, что это должно работать с константной сложностью с использованием кэша, как и было ранее. ВременнА сложность такого кода будет 1000000 * Y, где Y - это очень маленькое константное время выборки из кэша. Величина Y много меньше, чем X. По опыту могу казать, что разница тут будет в разы.

Т.е. текущий код работает минимум в 200 раз медленнее, но более вероятно, что где-то в 1000-2000 раз медленнее, чем должен, на 100 инструментах. При этом чем больше инструментов будет, тем более медленно он будет работать, т.е. имеем полиномиальную сложность времени исполнения.

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



Спасибо:


Alexander

Фотография
Дата: 10.06.2020
Ответить


Спасибо за фикс. Однако, если я правильно понимаю, проблема все еще сохранилась (хоть и в более мягком виде) в случае использования HistoryEmulationConnector, т.к. он использует CollectionSecurityProvider, в котором метод LookupById() использует перебор коллекции. В случае одновременного тестирования на истории нескольких инструментов это все так же будет приводить к перебору коллекции инструментов на каждом входящем тике. Это будет работать существенно медленнее, чем было раньше.
Спасибо:


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

loading
clippy