Stock# с несколькими квиками

Stock# с несколькими квиками
Atom
8/3/2010
Alexander


Со сколькими копиями квика можно безболезненно запускать одного
робота?
Как происходит экспорт через DDE в Stock# - одинаковые данные, я так
понимаю, фильтруются?

Вопрос возник не случайно - сейчас с 7ми квиками роботы съедают до
50-60% от нашего довольно мощного сервера (на каждом квике запущен 1-2
робота, каждый робот запускается 1 секунду). Стоит ли искать ошибку,
пытаться оптимизировать самого робота или лучше закинуть часть квиков
на другой сервер?

Tags:


Thanks:


< 1 2 3 4  > >>
Mikhail Sukhov

Avatar
Date: 8/11/2010
Reply


Можете сделать проще. Отнаследоваться от QuikTrader, переопределить
ReStartExport и там прописать свою логику проверки - нужно
перезапускать экспорт или не нужно.

Thanks:

Иванов Андрей

Avatar
Date: 8/12/2010
Reply


Комментарий о чём? ;) У вас синтетический случай, поэтому такой
огромный прирост.

Если вопрос "почему?", то потому что когда пишете сразу в файл,
копирование в памяти всего одно, а когда у вас несколько раз
осуществляется операция конкатенации, вы копируете данные много раз.
Из неочевидного строки создаются на хипе, а не на стеке, их создаётся
много и GC освобождает занимаемую ими память. В случае 2 выделения/
освобождения памяти практически нет, все данные в кэше процессора,
поэтому он работает на максимальной скорости, а не спотыкается об
ожидание данных из медленной памяти.

Ну а запись в файл, как вы уже заметили, происходит очень быстро =)
Потому что вывод буферизованный -- когда вы делаете 5 вызовов вместо
одного, они все пишут в один буфер и оверхеада по сравнению с записью
всей строки одним вызовом почти нет.

Если будете увлекаться файлами, столкнётесь с ещё одной неочевидной
штукой. Называется IOPS. Пока читаете/пишете линейно, скорости
обычного SATA будет достаточно -- придумана куча технологий для
ускорения линейного доступа (кэши, буферы, группировка вызовов
операционной системой, NCQ), но как только чтение станет случайным,
пиковые 400-500 IOPS SAS-диска за тысячу долларов покажутся жалкой
пародией на скорость =) Потому что в переводе на мегабайты получится
чуть больше 10 мегабайтов в секунду, это в 10-15 раз меньше
современного бытового SATA-диска на линейном доступе.

Thanks:

Mikhail Sukhov

Avatar
Date: 8/12/2010
Reply


В том то и дело, что я устанавливаю AutoFlush = true. А это отключает
всякую буферизацию. А если бы был буфер, я правильно понимаю, что
сброс буфера в файл происходит асинхронно?

Насчет хипа - я так и думал. string - это ведь класс. Но ведь буферы у
StreamWriter наверняка хранят не массив object, а массивы byte[].
Последние - это перевод переданных строк в файловой представление. Так
вот, а массивы так же хранятся в хипе, и так же нужно время на перевод
из строки в byte[]. Где же тогда экономия?

Про рандом и последовательный доступ понятно. Я читал об этом раньше.
Надеюсь в S# не придется с этим столкнутся. Не хотелось бы погрязнуть
в системных тонкостях =)

Thanks:

Иванов Андрей

Avatar
Date: 8/13/2010
Reply


AutoFlush занимается тем, что сбрасывает буферы самого StreamWriter в
подлежащий стрим. Использование этого флага с файлами ничего не
меняет. Писать можно без буферов, потому что под StreamWriter должен
быть какой-то FileStream, который сам будет буферизовать вывод, плюс
есть буфер ядра.

Выключить буферизацию в ядре можно только ключом в реестре на всю
систему или открывая файл. Отключить буфер после открытия файла, по-
моему, невозможно -- необходимость буферизации известна до
использования файла. По умолчанию буфер ядра в Windows Server
несколько мегабайтов, сколько он в десктопах, не знаю. В десктопах и
серверах разные схемы кэширования и буферов по умолчанию, в серверах
сейчас (2008/2008R2) кэширование по умолчанию может съесть всю память
и программам придётся свапиться =)

Сброс буфера ядра асинхронный. Сбросы прикладных буферов синхронные.

Массивы буферов не создаются и не удаляются 100 тысяч раз в секунду =)
Они один раз создаются и живут какое-то продолжительное время.
Когда вы делаете s1 += s2;, вы создаёте один объект (результат
конкатенации s1 и s2) и отдаёте GC один объект (тот, на который до
операции указывал s1). В случае с буферами все остаются при своих,
происходит только одно копирование. Самый шик, когда строка не
помещается в Gen 0 и отправляется в LOH -- в этом случае основную
часть процессорного времени занимает удаление объектов =) Но это не
наш случай, потому что строка должна быть больше 40 тысяч обычных
символов (а необычных, типа индийских, ещё на треть меньше).

Thanks:

Alexander

Avatar
Date: 8/15/2010
Reply


т.е. перезапускать экспорт только в том случае, если нет подключения:

public class OwnQuikTrader : QuikTrader
{
public OwnQuikTrader(string path, string ddeServer, string
dllName) :
base(path, ddeServer, dllName)
{
}

public override void ReStartExport()
{
if (!IsConnected)
base.ReStartExport();
}
}

?

Thanks:

Mikhail Sukhov

Avatar
Date: 8/16/2010
Reply


Да, так, если такого поведения достаточно.

Thanks:

Mikhail Sukhov

Avatar
Date: 8/16/2010
Reply


Массив буферов создается один раз, согласен (возможно еще как-то
увеличивается уменьшается, но это не так важно). Но сами то буферы
нет. Они создаются так же часто, как мы вызываем Write. string в
byte[] - создаем, int в byte[] - создаем, DateTime в byte[] -
создаем... Тоесть, в любом случаем необходимо произвести конфертацию,
а это создание массива байтов.

Копирование byte[] в массив буферов?

Thanks:

Иванов Андрей

Avatar
Date: 8/17/2010
Reply


Не, массивы байтов не создаются, создаются строки. Много-много строк
(иногда 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, роли не играет.

Thanks:

Alexander

Avatar
Date: 8/23/2010
Reply


Опять проблема возникла с запуском нескольких стратегий на нескольких
квиках.
Сейчас на сервере запущено 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 эта строчка.

С чем это связано?

Thanks:

Alexander

Avatar
Date: 8/23/2010
Reply


Попробовал запускать стратегии после того, как добавил их в
StrategyManager - не помогло, последние 5 из добавленных стратегий всё
равно не работают - в лог пишут лишь 1 строку.

Thanks:
< 1 2 3 4  > >>

Attach files by dragging & dropping, , or pasting from the clipboard.

loading
clippy