Со сколькими копиями квика можно безболезненно запускать одного робота? Как происходит экспорт через DDE в Stock# - одинаковые данные, я так понимаю, фильтруются?
Вопрос возник не случайно - сейчас с 7ми квиками роботы съедают до 50-60% от нашего довольно мощного сервера (на каждом квике запущен 1-2 робота, каждый робот запускается 1 секунду). Стоит ли искать ошибку, пытаться оптимизировать самого робота или лучше закинуть часть квиков на другой сервер?
Comments (48)
Login or Create account, Log in or register to leave a comment
Михаил, небольшое уточнение. Предположим используется MultiTrader с двумя разными Квиками. В этих Квиках настроены «Все сделки» и «Стаканы» для одной бумаги, например Сбера.
Выше Вы написали, что данные поступают от двух Квиков одновременно. Дублирующие данные учитываются и отбрасываются.
Верно ли это для «Стаканов»?
Съедает сам робот. Он запущен только 1, просто там идёт работа со многими квиками. Что это за распределение и как его сделать? :)
Ещё я заметил следующее при работе с несколькими квиками - возьмём, к примеру, таблицу Всех сделок. Она передаётся насколько я понимаю из какого-то одного квика (к примеру, который был первым добавлен в AggregatedTrades), из остальных игнорируется. Как только теряется связь с сервером у первого квика (остальные при этом работают) - таблица всех сделок перестаёт экспортироваться (хотя могла бы из других квиков) - получается нельзя таким образом подстраховаться...
Можно через Process Explorer. Он покажет, какие потоки тормозят. А потокам можно понять, что в них делается. Все S# потоки именованы. Робота, если Вы этого не делали, не именованы.
Насчет прекращения экспорта. Схема не такая. Роботу льется все. А он уже смотрит на уникальность. Так что, если какой-то из Квиков лили дубли и он упал, то на экспорт это не должно отразиться. Тут случаем ReConnectionManager не вступает в работу? Вот он может все остальные Квики перезапускать.
Насколько я понял из документации, для MultiTrader отсутствует ReConnectionManager. То есть выше Вы имели ввиду, что вступает в работу ReConnectionManager для одного из Квика, который используется в MultiTrader.
Как может ReConnectionManager одного Квика перезапустить все остальные Квики? Или я что то не понял?
С тех пор много воды утекло.
То есть ситуация уже другая и ReConnectionManager пользовать можно?
А на предыдущий вопрос какой ответ? Насчет информации из стакана, если два Квика работают.
Лучше чтобы пересечение не было.
Скупы Вы, Михаил, на слова. 🙂
Но всеже повытягиваю информацию из Вас еще 🙂
Ситуация такая. Сейчас использую три разных Квика. По отдельности каждый из них работает не идеально. То задержки происходят, то обрыв соединения.
Необходимо использовать суммарную их информацию. Для этих целей, насколько я понял, подходит MultiTrader. Хотелось бы узнать подводные камни.
Какие еще тонкости в использовании MultiTrader с несколькими Квиками, настроенными на одну и ту же бумагу? В чем сложность со стаканом? Почему «лучше чтобы пересечение не было»?
Может быть Alexander поможет с ответом?
Я не скуп. Мне теперь приходится отвечать и новичкам и старичкам. Новичкам отвечаю больше. За счет ответов старичкам. Так что если старички хотят повысить качество ответов себе любимым, нужно повышать количество ответов новичкам.
Я несколько Квиков не использовал. Да, думаю Александр тут более компетентен.
ðÏËÁÚÁÌ, ÔÏÒÍÏÚÉÔ LogHelp, ×ÏÔ StackTrace: ntkrnlpa.exe!NtBuildNumber+0xe ntkrnlpa.exe!RtlUpcaseUnicodeString+0xa2 ntkrnlpa.exe!ZwYieldExecution+0x4f3 ntkrnlpa.exe!MmIsDriverVerifying+0xbb2 hal.dll+0x2ef2 clr.dll+0x1a5e clr.dll!CoUninitializeEE+0x378e clr.dll!CoUninitializeEE+0x7663 clr.dll!LogHelp_TerminateOnAssert+0x6a1fd clr.dll!LogHelp_TerminateOnAssert+0x3c642 clr.dll!LogHelp_TerminateOnAssert+0x3c560 clr.dll!LogHelp_TerminateOnAssert+0x3c6de clr.dll!LogHelp_TerminateOnAssert+0x40b22 clr.dll!LogHelp_TerminateOnAssert+0x3aab2 mscorlib.ni.dll+0x2aae5b mscorlib.ni.dll+0x237ff4 mscorlib.ni.dll+0x237f34 mscorlib.ni.dll+0x2aade8 clr.dll+0x21db clr.dll!CoUninitializeEE+0x6862 clr.dll!CoUninitializeEE+0x6a04 clr.dll!CoUninitializeEE+0x6a39 clr.dll!GetPrivateContextsPerfCounters+0x8a13 clr.dll!LogHelp_TerminateOnAssert+0x591ad clr.dll!LogHelp_TerminateOnAssert+0x5922f clr.dll!LogHelp_TerminateOnAssert+0x592ea clr.dll!LogHelp_TerminateOnAssert+0x59381 clr.dll!GetPrivateContextsPerfCounters+0x88e6 clr.dll!GetPrivateContextsPerfCounters+0x87e1 clr.dll!LogHelp_TerminateOnAssert+0x58fb0 KERNEL32.dll!GetModuleFileNameA+0x1ba
ReConnectionManager, ËÏÎÅÞÎÏ, ×ÓÔÕÐÁÅÔ × ÒÁÂÏÔÕ ÅÓÌÉ ÏÄÉÎ ÉÚ Ë×ÉËÏ× ÕÐÁÌ, Á ÄÒÕÇÏÊ - ÎÅÔ. ô.Å., ×ÉÄÉÍÏ, ÓÔÏÉÔ ReconnectionManager ÎÁÚÎÁÞÁÔØ ÏÔÄÅÌØÎÏ ÄÌÑ Ë×ÉËÏ×, Á ÎÅ ÄÌÑ ×ÓÅÇÏ MultiTrader?
Отключил логгирование для стратегий (убрал обработчик OnLog и не добавляю StrategyLogger) - теперь робот ест от 0 до 50% процессора. 50% - крайне редко, всплески. в основном висит на 0%. т.е. разница колоссальная.
Хм, или логов много или дисковая операция тяжелая. Есть вероятность что StrategyLogger глючит, он там так мало кода. Фактически, вот он весь (что используется при активизации события):
_writer = new StreamWriter(logDirectory.IsEmpty() ? this.FileName : Path.Combine(logDirectory, fileName)) ;
private void StrategyLog(Strategy strategy, StrategyErrorStates state, string message) { WriteMessage(strategy + " " + message);
///
/// Записать специальное сообщение, которое относится ко всем
статегиям .
///
/// Сообщение.
public void WriteMessage(string message)
{
if (message.IsEmpty())
throw new ArgumentNullException("message");
Про тяжёлую дисковую операцию не понял =) Дисковые операции едят время (iowait), а не CPU.
Про много лог-записей в точку -- операция конкатенации самая дорогая по CPU в .NET (из тех, что не ожидаешь). Единственный способ борьбы это писать сразу в файл. Если убрать конкатенацию в _writer.WriteLine(..), производительность логирования вырастет в полтора раза =)
То есть, оформить запись в виде трёх вызовов -- время, разделитель, сообщение.
Захотите проверить мою теорию, включите логирование, а все строки с конкатенацией замените на строки-константы примерно той же длины.
Ещё эффективнее по CPU будет, если событие лога генерировать не со строкой конкатенации стратегии и сообщения, а объектом с двумя полями или вызовом метода с двумя параметрами.
Единственный вариант привязать к CPU запись на диск это отключить DMA для ATA-дисков. Я никогда не сталкивался с необходимостью такое делать, но можно попровать начать с этого -- проверять надо в дисковом контроллере, на всех системах названия свои. Раньше было переключение между режимами (PIO/*DMA), в семёрке чекбокс выключения DMA.
логов много - каждая запущенная стратегия каждую секунду что-то да пишет. про тяжёлую дисковую операцию я тоже не понял =)
Хотелось бы ещё закрыть вопрос с прекращением экспорта - я так понимаю в случае с MultiTrader необходимо ReconnetionManager создавать для каждого из добавленных AggregatedTraders? В этом случае экспорт не прекратится если отвалится лишь один из квиков
У меня на процессоре Q6600 конкатенация съедала одно ядро примерно за 100 тысяч записей в секунду. Если у вас 100 стратегий с тайм-фреймом секунда, современный процессор не заметит этого -- тайм-фрейм должен быть 10 миллисекунд. У вас либо очень много стратегий (100 тысяч), либо они не все работают с тайм- фреймом, либо предположение о конкатенации неверно.
Посчитайте, сколько у вас записей в лог за секунду делается. Вместо записи в лог инкрементируйте счётчик через
Interlocked.Increment(ref _logCounter);
и пишите это значение куда-нибудь раз в секунду примерно вот так:
int count = Interlocked.Exchange(ref _logCounter, 0); Console.WriteLine("Log rps: {0}", count);
_logCounter -- глобальная переменная. Стратегии вроде сами по себе ничего не пишут в лог, поэтому вы можете получить точное значение.
private class MyStrategy : Strategy { public void DoLog(string message) { base.AddLog(StrategyErrorStates.None, message); }
var str = new MyStrategy();
var logger = new StrategyLogger("1.txt"); logger.Strategies.Add(str);
const int count = 100000;
var t1 = Watch.Do(() => { for (var i = 0; i < count; i++) { str.DoLog("111"); }
logger.Strategies.Remove(str);
var writer = new StreamWriter("2.txt") ;
str.Log += (s, e, m) => { writer.Write(DateTime.Now.TimeOfDay); writer.Write(" "); writer.Write(s); writer.Write(" "); writer.WriteLine(m);
var t2 = Watch.Do(() => { for (var i = 0; i < count; i++) { str.DoLog("111"); }
ňĹÚŐĚŘÔÁÔ ÎÁ ÍĎĹÍ ÍÁŰÉÎËĹ. t1 - 55 ÓĹËŐÎÄ. t2 - 1 ÓĹËŐÎÄÁ. îÉŢĹÇĎ ÓĹÂĹ! áÎÄŇĹĘ, ÖÄŐ ËĎÍÍĹÎÔÁŇÉĘ. đĎÔĎÍŐ ËÁË Ń ÄĎ ÓÉČ ĐĎŇ ÓŢÉÔÁĚ, ŢÔĎ ĎĐĹŇÁĂÉŃ ÚÁĐÉÓÉ × ĆÁĘĚ ÍÎĎÇĎ ÄĎŇĎÖĹ ËÁËĎĘ-ÔĎ ËĎÎËÁÔĹÎÁĂÉÉ ÓÔŇĎË.
Можете сделать проще. Отнаследоваться от QuikTrader, переопределить ReStartExport и там прописать свою логику проверки - нужно перезапускать экспорт или не нужно.
Комментарий о чём? ;) У вас синтетический случай, поэтому такой огромный прирост.
Если вопрос "почему?", то потому что когда пишете сразу в файл, копирование в памяти всего одно, а когда у вас несколько раз осуществляется операция конкатенации, вы копируете данные много раз. Из неочевидного строки создаются на хипе, а не на стеке, их создаётся много и GC освобождает занимаемую ими память. В случае 2 выделения/ освобождения памяти практически нет, все данные в кэше процессора, поэтому он работает на максимальной скорости, а не спотыкается об ожидание данных из медленной памяти.
Ну а запись в файл, как вы уже заметили, происходит очень быстро =) Потому что вывод буферизованный -- когда вы делаете 5 вызовов вместо одного, они все пишут в один буфер и оверхеада по сравнению с записью всей строки одним вызовом почти нет.
Если будете увлекаться файлами, столкнётесь с ещё одной неочевидной штукой. Называется IOPS. Пока читаете/пишете линейно, скорости обычного SATA будет достаточно -- придумана куча технологий для ускорения линейного доступа (кэши, буферы, группировка вызовов операционной системой, NCQ), но как только чтение станет случайным, пиковые 400-500 IOPS SAS-диска за тысячу долларов покажутся жалкой пародией на скорость =) Потому что в переводе на мегабайты получится чуть больше 10 мегабайтов в секунду, это в 10-15 раз меньше современного бытового SATA-диска на линейном доступе.
В том то и дело, что я устанавливаю AutoFlush = true. А это отключает всякую буферизацию. А если бы был буфер, я правильно понимаю, что сброс буфера в файл происходит асинхронно?
Насчет хипа - я так и думал. string - это ведь класс. Но ведь буферы у StreamWriter наверняка хранят не массив object, а массивы byte[]. Последние - это перевод переданных строк в файловой представление. Так вот, а массивы так же хранятся в хипе, и так же нужно время на перевод из строки в byte[]. Где же тогда экономия?
Про рандом и последовательный доступ понятно. Я читал об этом раньше. Надеюсь в S# не придется с этим столкнутся. Не хотелось бы погрязнуть в системных тонкостях =)
AutoFlush занимается тем, что сбрасывает буферы самого StreamWriter в подлежащий стрим. Использование этого флага с файлами ничего не меняет. Писать можно без буферов, потому что под StreamWriter должен быть какой-то FileStream, который сам будет буферизовать вывод, плюс есть буфер ядра.
Выключить буферизацию в ядре можно только ключом в реестре на всю систему или открывая файл. Отключить буфер после открытия файла, по- моему, невозможно -- необходимость буферизации известна до использования файла. По умолчанию буфер ядра в Windows Server несколько мегабайтов, сколько он в десктопах, не знаю. В десктопах и серверах разные схемы кэширования и буферов по умолчанию, в серверах сейчас (2008/2008R2) кэширование по умолчанию может съесть всю память и программам придётся свапиться =)
Сброс буфера ядра асинхронный. Сбросы прикладных буферов синхронные.
Массивы буферов не создаются и не удаляются 100 тысяч раз в секунду =) Они один раз создаются и живут какое-то продолжительное время. Когда вы делаете s1 += s2;, вы создаёте один объект (результат конкатенации s1 и s2) и отдаёте GC один объект (тот, на который до операции указывал s1). В случае с буферами все остаются при своих, происходит только одно копирование. Самый шик, когда строка не помещается в Gen 0 и отправляется в LOH -- в этом случае основную часть процессорного времени занимает удаление объектов =) Но это не наш случай, потому что строка должна быть больше 40 тысяч обычных символов (а необычных, типа индийских, ещё на треть меньше).
т.е. перезапускать экспорт только в том случае, если нет подключения:
public class OwnQuikTrader : QuikTrader { public OwnQuikTrader(string path, string ddeServer, string dllName) : base(path, ddeServer, dllName)
?
Да, так, если такого поведения достаточно.
Массив буферов создается один раз, согласен (возможно еще как-то увеличивается уменьшается, но это не так важно). Но сами то буферы нет. Они создаются так же часто, как мы вызываем Write. string в byte[] - создаем, int в byte[] - создаем, DateTime в byte[] - создаем... Тоесть, в любом случаем необходимо произвести конфертацию, а это создание массива байтов.
Копирование byte[] в массив буферов?
Не, массивы байтов не создаются, создаются строки. Много-много строк (иногда char[]).
Для начала форматная строка парсится (это TextWriter.Write).
public virtual void Write(string format, object arg0, object arg1) { this.Write(string.Format(this.FormatProvider, format, new object[] { arg0, arg1 }));
Из-за того, что выделенной памяти не хватило на создание конечной строки (размер форматной + 16 символов), происходит реаллок с выделением памяти в два раза больше и копированием. При парсинге форматной строки аллоки поменьше для внутреннего использования. Копирований, соответственно, пачка. Тут основная часть CPU и съедается.
Я писал сделки в файлы инструментов и каждый раз (запись сделки) считал путь к имени файла инструмента. Кэширование этой операции дало примерно пятикратный прирост скорости записи. Закэшировал остальные операции вычисления пути (тоже просто конкатенации и Path.Combine), прирост ещё раза в два.
Форматная строка вида
string dealStr = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}; {1:T};{2};{3};{4};{5}", _id, _timeStamp, _price, _count, _volume, _operation.HasValue ? _operation.ToString() : "0");
по скорости почти идентична просто конкатенации или StringBuilder. Предположу, что здесь хватило выделенного начального буфера (40 символов) и форматтер InvariantCulture работает быстрее CurrentCulture. Выяснять лениво, мне достаточно того, что так писать красивее, чем через + и работает некритично медленнее.
Писать напрямую в файл не стал, потому что после включения кэширования путей время полного копирования сделок за сутки составляет секунд 10. А если вместо форматной строки выше писать константу такой же длины, скорость пару секунд. 2 секунды или 10, роли не играет.
Опять проблема возникла с запуском нескольких стратегий на нескольких квиках. Сейчас на сервере запущено 7 квиков и суммарно должно было бы запуститься 13 стратегий.
Я запускаю следующим образом:
//цикл по всем стратегиям //для каждой из стратегий получаю список из счетов для которых данная стратегия не была запущена TimeFrameStrategy tfStrategy = null; //в зависимости от стратегии tfStrategy инициализирую конструктором нужного класса (наследуемого как раз от TimeFrameStrategy) //регистрирую ТФ
tfStrategy.Log += Strategy_OnLog; var newDateTimeDir = string.Format("Logs\{0}{1:00}{2:00}", DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day); if (!Directory.Exists(newDateTimeDir)) { Directory.CreateDirectory(newDateTimeDir);
var logger = new StrategyLogger("{0}\_{1} _{2}.txt".Put(newDateTimeDir, Enum.GetValues(typeof (StrategiesName)).GetValue(i), account.Account)); logger.Strategies.Add(tfStrategy);
_strategyManager.Register(tfStrategy, port, riFut); // где port - портфель нужный, riFut - фьюч из Securities. и то и то не null tfStrategy.Start();
В результате у меня для всех 13 стратегий создаются логи, но работают не все (в разные запуски разное число, от 4 до 8 было) - в логе неработающих стратегий лишь строчка 11:06:42.8281250 Volume_1_01:00:05 Volume_1 запущена. и ничего более, хотя при каждом вызове OnProcess в лог должно что-то писаться.
Вот сейчас в 8 стратегиях из 13 пишется, в 5 - лишь 1 эта строчка.
С чем это связано?
Попробовал запускать стратегии после того, как добавил их в StrategyManager - не помогло, последние 5 из добавленных стратегий всё равно не работают - в лог пишут лишь 1 строку.
Оказывается, это было связано с тем, что не хватало потоков для StrategyManager (я использовал конструктор без параметра для числа потоков) - получал на выходе лишь 8 потоков. Сегодня попытался создавать и указывать 15 потоков - всё заработало. Только непонятно насколько в таком случае он быстро будет работать =)
Есть небольшая просьба - можно ли в случае если используется конструктор StrategyManager по умолчанию (в который передаётся лишь ITrader) в случае, если число добавленных стратегий больше, чем число потоков, автоматически разбрасывать стратегии по потокам и запускать несколько стратегий на одном ядре? А то такой тихий незапуск стратегий как у меня очень смущает, проблема не очевидна первоначально.
Не, проблема в пуле потоков. Должно обрабатывать необработанные, а обрабатывает - обработанные. Ок, посмотрю.
Alexander, поделитесь опытом как работается с несколькими Квиками?
Интересует ситуация, когда в Квиках настроены одинаковые бумаги. Одна из задач использования MultiTrader - это минимизировать сделать поступление информации более стабильной. Что бы при задержках или падении части роботов программа все равно получала актуальные данные.
Реализовывать MultiTrader у себя в программе еще не начал. Возможно будут более конкретные вопросы. Пока могу задать такие:
Я использую MultiTrader лишь как коллекцию квиков. Фактически работа ведётся с каждым QuikTrader отдельно. ReConnectionManager устанавливается для каждого квика, поэтому если один падает - другие-то работают. Экспорт запускается для каждого квика отдельно. Опять же - сделано для того, чтоб при падении одного квика с данными, другие нормально работали. В стратегиях используются непосредственно нужные QuikTrader. MultiTrader используется для получения событий в основном о непрошедших сделках и т.д. и т.п. Может я неоптимально его использую, но пришёл именно к такому решению - как раз для того чтобы постоянно было дублирование данных для пущей стабильности.
Alexander, спасибо за ответ. Не совсем Вас понял. Еще немножко уточню.
Настроены ли у Вас во всех Квиках одинаковые бумаги? Например, во всех четырех Квиках идет вывод тикков и стаканов для Сбера.
Используете ли Вы свойство Trades из MultiTrader, или используете это свойство у каждого Квика?
3)Используете ли Вы метод GetMarketDepth из MultiTrader, или используете этот метод у каждого Квика?
Правильно я понимаю, что если один Квик упадет, то стратегия которая ему соответствует перестанет работать?
Да. Т.к. данные в него перестанут поступать. Но все остальные квики с этой стратегией будут работать. Опережая вопрос - как сделать автоматическое дублирование данных я не знаю :)
Вообще наверное MultiTrader как раз и должен это делать.
Я же в этом ключе и веду весь диалог :) Пытаюсь выяснить, как Вы используете MultiTrader для собирания одинаковых, дублирующихся данных со всех квиков, а потом использования этих данных из MultiTrader.
Вообщем, насколько я теперь понял, MultiTrader в этом ключе никто не использовал. Придется наступать на грабли самому.
Удалил предыдущий ответ, был неправ. Сейчас пересмотрел код - MultiTrader фильтрует сделки по уникальности trade.ID (если стоит флаг SupportTradesUnique). Если флаг не стоит - фильтрации не будет. Остальные данные суммируются. Подписка на события происходит для каждого из квика при добавлении его в AggregatedTraders.
Также не забывайте, что у каждого trade есть поле Security. А оно будет разным для разных квиков (из-за Security.Trader).
Добрый день, Работаю одним роботом с двумя квиками. И данные с ТВС первого квика приходят с запозданием. Например, сегодня на 17 часов последний тик был от 16 ч 11 мин. Во втором квике все тики прищли вовремя. Может проще запустить два робота отдельно на каждый квик? ЗЫ. наблюдал это на версиях 2.6.2 и 3.1.8
Как строится работа с двумя квиками? Как определяется задержка? У меня в настоящий момент работает 6 квиков, 1 робот, задержек не наблюдаю.
2 QuikTrader? DDE разные? Используйте MultiTrader, там всё просто, не надо его бояться. :)
Передавать в стратегии нужные Volume. Я, к примеру, меняю объём для каждой стратегии в каждом квике перед каждым входом - в зависимости от того сколько для данного счёта свободных средств. MultiTrader никак не влияет на то что на разных квиках будут разные стратегии, это очевидно что так будет.
Да я кажется понял. Попробую с Multitrader. Хотя всё равно не понятно почему с двумя QuikTrader сделки приходят не одинаково. Мне кажется проще иметь возможность самому задавать имя ДДЕ-сервера и запускать два отдельных робота на каждый квик. Хотя б для того чтобы можно было перезапускать один без ущерба для второго робота
что подразумевается под "два отдельных робота на каждый квик"? на один квик запускается один QuikTrader \ MultiTrader \ ..., в нём запускается 1 - тысячи Strategy, которые могут перезапускаться, останавливаться, как угодно взаимодействовать с квиком, без ущерба остальным стратегиям. 1 квик - 1 имя DDE. Иначе технология DDE не работает - лишь 1 вывод возможен.
Подразумевается полностью два отдельных робота на каждый квик. В каждом роботе у меня несколько стратегий. Имя ДДЕ -сервера - wrapper и его не изменить. Потому я и говорю, если б можно было менять: одному роботу задаю одно имя ДДЕ-сервера, другому другое и каждый работает со своим квиком не завися друг от друга.
А кто мешает это сделать? В любом случае имена DDE серверов обязаны быть разными для разных квиков. Отсюда и возникают проблемы. Пользуйтесь конструктором QuikTrader, передавая разные имена DDE для каждого Quik: дока
Спасибо. Не знал что есть возможность менять имя ДДе-сервера