Секреты TSLab | Торговые роботы | События
26 Апр

Скоростная обработка тиков. Рекомендации.

Последнее время приходится много работать с тиковыми данными и, естественно, приходится их обрабатывать в TSLab. Так как свечей много и в каждой  свече еще больше тиков, то процесс обработки оных может занимать длительное время. Очень хочется чтобы работало все быстрее и как итог, было решено протестировать основные паттерны работы с тиками. В результате сделаем вывод о том, как реализуется скоростная обработка тиков чтобы было быстро и хорошо. И да, картинок не будет :).

Скоростная обработка тиков. Постановка задачи.

Схема обработки тиков весьма однообразна и содержит пару базовых паттернов.

  • прямой обсчет тиков текущей свечи. Мы работаем прямо с тиками без создание промежуточных списков.
  • обсчет тиков свечи через промежуточные коллекции и списки.

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

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

Для измерения времени работы, будет использовать специальный класс System.Diagnostics.Stopwatch. Он позволит измерить время с высоким уровнем точности. Каждый проход по свечам будем замерять как единую операцию. В итоге получим список измерений для разной длины массива тиков.

Обработка коллекций может происходить с использованием трех типичных инструментов:

  • стандартные циклы (for, while, do)
  • цикл foreach
  • linq команды

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

Тестовый скрипт

Скрипт получился не очень большой но покрывающий все наши исходные задачи.

            // чтобы набор чисел всегда был одинаков, используем seed
            var rnd = new Random(100);
            
            // стартовый размер массива данных и делитель
            var arrSize = 100000000;
            var div = 1;

            var currSize = arrSize;
            while (currSize > 10)
            {
                div *= 10;
                currSize = arrSize / div;

                // якобы массив тиков
                var arr = new double[currSize];
                
                for (int i = 0; i < arr.Length; i++)
                    arr[i] = rnd.Next(1, 1000);

                var watch = new Stopwatch();
                watch.Start();
                var count = 0.0;

                // якобы перебор свечек  
                for (var i = 0; i < div; i++)
                {
                    count = 0;
                    // сюда будем вставлять команды работы с тиками
                }


                var msg = string.Format("ArrLength: {0}, Loop: {1}, Time: {2}, count: {3}", currSize, div, watch.Elapsed, count);
                Debug.WriteLine(msg);
            }

 

Прямой обсчет тиков

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

  • подсчет числа сделок на покупку в свече.
  • расчет объема свечи из тиковых сделок.
  • подсчет объема всех покупок в свече.
  • подсчет сделок больше заданного объема в свече.

В общем, примеры показывают направление нашей мысли. Для теста будем рассчитывать объем всех сделок с объемом больше 500. Максимальный объем тика у нас 1000, как следует из тестового скрипта. Примеры кода для каждого варианта:

    // linq
    count = arr.Sum(v => v > 500 ? v: 0);

    // for
   for (int k = 0; k < arr.Length; k++)
       count += arr[k] > 500 ? arr[k] : 0;
   
    // foreach
    foreach (var item in arr)
        count += item > 500 ? item : 0;

Результаты следующие:

for ArrLength: 3000000, Loop: 100, Time: 00:00:03.9433100, count: 1122845420
ArrLength: 300000, Loop: 1000, Time: 00:00:03.9302639, count: 112374876
ArrLength: 30000, Loop: 10000, Time: 00:00:03.9892026, count: 11198670
ArrLength: 3000, Loop: 100000, Time: 00:00:03.8094046, count: 1111242
ArrLength: 300, Loop: 1000000, Time: 00:00:03.7044354, count: 116344
ArrLength: 30, Loop: 10000000, Time: 00:00:03.5904666, count: 12634
ArrLength: 3, Loop: 100000000, Time: 00:00:02.6527128, count: 2169
foreach ArrLength: 30000000, Loop: 10, Time: 00:00:04.2863756, count: 11237564998
ArrLength: 3000000, Loop: 100, Time: 00:00:04.2342477, count: 1122845420
ArrLength: 300000, Loop: 1000, Time: 00:00:04.2351377, count: 112374876
ArrLength: 30000, Loop: 10000, Time: 00:00:04.1641656, count: 11198670
ArrLength: 3000, Loop: 100000, Time: 00:00:03.7539018, count: 1111242
ArrLength: 300, Loop: 1000000, Time: 00:00:03.6106301, count: 116344
ArrLength: 30, Loop: 10000000, Time: 00:00:03.3811633, count: 12634
ArrLength: 3, Loop: 100000000, Time: 00:00:02.4263865, count: 2169
linq ArrLength: 30000000, Loop: 10, Time: 00:00:10.2037127, count: 11237564998
ArrLength: 3000000, Loop: 100, Time: 00:00:10.1790545, count: 1122845420
ArrLength: 300000, Loop: 1000, Time: 00:00:10.6790103, count: 112374876
ArrLength: 30000, Loop: 10000, Time: 00:00:10.1260764, count: 11198670
ArrLength: 3000, Loop: 100000, Time: 00:00:10.1368777, count: 1111242
ArrLength: 300, Loop: 1000000, Time: 00:00:09.6797452, count: 116344
ArrLength: 30, Loop: 10000000, Time: 00:00:09.5827869, count: 12634
ArrLength: 3, Loop: 100000000, Time: 00:00:16.0744673, count: 2169

Как видно, linq остает почти в 3 раза от других вариантов цикла. При этом у linq какое-то неадекватное увеличение времени выполнения при размере массива 3. Это не ошибка, результат повторяется стабильно. Видимо накладные расходы на использование linq здесь вылезают по полной программе и время резко увеличивается. Так что, хоть и красиво и удобно, но для обработки тиков linq не подходит при прямой работе с тиками. Увы и ах.

for и foreach идут ноздря в ноздрю и между ними выбирать не стоит. Какой вариант удобнее такой и используйте, но если вы патологический кодер, то используйте всегда базовые циклы, ведь они немного быстрее :).

Обсчет тиков через промежуточные коллекции.

Данный паттерн встречается не менее часто чем предыдущий. Примеры реализации:

  • график footprint который можно увидеть в TSLab
  • неоднократное обращение к определенной выборке тиков, например к тикам покупок.
  • любой другой случай где мы часть тиков помещаем в отдельную коллекцию.

Так как выше выяснили что foreach так же быстр как for, то не будем задействовать for в тестах. Примеры кода для каждого варианта:

    // linq без конвертации в другой вид коллекции
    var result = arr.Where(v => v > 500);
    
    // linq с корвертацией в массив
    var result = arr.Where(v => v > 500).ToArray();

    // foreach с созданием отдельного списка
    var result = new List<double>();
    foreach (var item in arr)
    {
        if (item > 500)
            result.Add(item);
    }

После получения коллекции, будем производить с ней какую либо операцию, например, подсчет объема тиков.

    // проводим операцию над коллекцией полученной выше одним из способов
    foreach (var item in result)
       count += item;

Результаты следующие:

foreach ArrLength: 10000000, Loop: 10, Time: 00:00:02.4438366, count: 3746495393
ArrLength: 1000000, Loop: 100, Time: 00:00:02.3367783, count: 374321244
ArrLength: 100000, Loop: 1000, Time: 00:00:02.4252638, count: 37297133
ArrLength: 10000, Loop: 10000, Time: 00:00:02.4478711, count: 3756152
ArrLength: 1000, Loop: 100000, Time: 00:00:01.4827645, count: 355438
ArrLength: 100, Loop: 1000000, Time: 00:00:01.7289573, count: 39551
ArrLength: 10, Loop: 10000000, Time: 00:00:01.7987754, count: 2111
linq ArrLength: 10000000, Loop: 10, Time: 00:00:02.9777427, res.count: 4995004
ArrLength: 1000000, Loop: 100, Time: 00:00:02.9503232, res.count: 499069
ArrLength: 100000, Loop: 1000, Time: 00:00:02.9179539, res.count: 49788
ArrLength: 10000, Loop: 10000, Time: 00:00:02.9169527, res.count: 5013
ArrLength: 1000, Loop: 100000, Time: 00:00:02.7192137, res.count: 478
ArrLength: 100, Loop: 1000000, Time: 00:00:02.5827635, res.count: 50
ArrLength: 10, Loop: 10000000, Time: 00:00:02.8403376, res.count: 3
linq array
ArrLength: 10000000, Loop: 10, Time: 00:00:03.8770327, count: 3746495393
ArrLength: 1000000, Loop: 100, Time: 00:00:03.8029816, count: 374321244
ArrLength: 100000, Loop: 1000, Time: 00:00:03.8641673, count: 37297133
ArrLength: 10000, Loop: 10000, Time: 00:00:03.9113998, count: 3756152
ArrLength: 1000, Loop: 100000, Time: 00:00:03.2722251, count: 355438
ArrLength: 100, Loop: 1000000, Time: 00:00:03.9661614, count: 39551
ArrLength: 10, Loop: 10000000, Time: 00:00:04.2493606, count: 2111

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

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

Тяжело в учении – легко в бою

В завершение, хотелось проверить как будет работать скоростная обработка тиков в реальных условиях, то есть, в TSLab. Тиковых данных у меня достаточно, засим переходим в реальные полевые условия (можно накачать тики с http://ftp.moex.ru или достать где-то еще). Тестировать буду на простом примере из библиотеки индикаторов RusAlgo. По умолчанию, там используется linq в виду его красоты и удобства написания. Кубик расчета объема всех сделок на покупку и продажу, подойдет как нельзя кстати. Создадим новую версию кубика, с применением стандартных циклов и будем сравнивать скорость обработки всех баров. Для того чтобы время было ощутимое, в коде скрипта завернем цикл, в котором будем многократно повторять обсчет тиков, что даст нам более адекватные цифры для сравнения.

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

Приводить код тестового скрипта не буду, сразу переходим к итогам:

13:39:49.98 : Direct: for: 00:00:14.5599726
 13:40:13.65 : Direct: linq: 00:00:23.6646459

Как видим, linq оказался в 2 раза тормознее (ну не совсем в 2, но разница серьезная). Ранее, мы получали еще большую разницу. Почему? Очевидно, что работа с объектами сделок и запрос свойств объектов, дает затраты и эти затраты одинаковы в обоих случаях, но сильно влияют на общую  скорость работы.

Давайте для окончательной проверки, возьмем кубик суммирующий объемы всех сделок на покупку/продажу. Его особенность в том, что linq используется правильно, а именно в виде связки

value = trades.Where(trd => trd.Direction == Direction).Sum(trd => trd.Quantity);

Такая связка позволяет избежать создания промежуточных списков и отработать достаточно быстро и удобно. Посмотрим что у нас получится для обоих вариантов:

13:53:32.71 : Direct: linq: 00:00:24.4801047
13:53:08.23 : Direct: for: 00:00:17.3492000

Разрыв стал меньше, но все равно существенный. Если скрипт работает 17 минут вместо 24-х, это заметно.

Выводы

Статья была написана не с целью написания, а в процессе проведения тестов и оптимизации своих кодов работы с тиками. Это отразилось и на сборке индикаторов RusAlgo, в виде ускорения работы некоторых индикаторов. Ну, и память не вечна, лучше всегда иметь шпаргалку на будущее :). Собственно итоги в куче:

  • Везде где можно используем стандартные циклы (for,while,do) или foreach. Ускорение до двух раз.
  • Если нужна промежуточная коллекция, лучше создать ее в виде списка или массива чем использовать linq. Памяти сожрет больше, скорость будет тоже больше. Ускорение небольшое, но есть, особенно если запрос к коллекции будет не один а много.
  • Скоростная обработка тиков это не миф, а реальность :).

 

PS: Всем добра :). И не стоит заниматься оптимизацией скорости, если не возникла проблема выполнения скриптов в течение минут десяти и более :).

 

 


comments powered by HyperComments

Павел Дуков
2015-12-02 20:44:16
спасибо
Жора
2017-01-01 07:18:37
Казино Вулкан раздают деньги сегодня http://cenforce100.ru/casino-vulkan.php
8
Сен
2017

Доверительное управление. Результаты в августе 2017 года.

Доверительное управление. Результаты в августе 2017 года. Стратегия “Опционы” принесла в августе прибыль в размере… »

6
Авг
2017

Доверительное управление. Результаты в июле 2017 года.

Доверительное управление. Результаты в июле 2017 года. В июле индекс РТС вновь колебался в достаточно узком… »

14
Июл
2017

Доверительное управление. Результаты в июне 2017 года.

Доверительное управление. Результаты в июне 2017 года. Июнь индекс РТС вновь провел преимущественно в боковых движениях, а… »

11
Июн
2017

Доверительное управление. Результаты в мае 2017 года.

Доверительное управление. Результаты в мае 2017 года. В мае “болтанка” индекса РТС продолжилась, на паре… »

7
Май
2017

Доверительное управление. Результаты в апреле 2017 года.

Доверительное управление. Результаты в апреле 2017 года. В апреле мы наблюдали очередной месяц “боковика” по… »