При включенной ТС при интенсивных торгах регулярно подвисал Quik. Мало того, серверное время Quik, отставало от московского иногда до 30 с. Естественно, все это приводило к ошибкам ТС.
ТС представляет собой программу Lua, связанную с DLL. DLL, собственно, и является самой ТС, a Lua только реал-тайм получает данные из терминала (таблицу обезличенных сделок, стаканы, котировки, реал-тайм свечи — OHLCV, и передает эти данные в DLL, т.е., является интерфейсом между терминалом и ТС. В Lua на сегодня всего ~250 строк кода.
Деле в том, что все события QLua (обновление стакана, таблицы обезличенных сделок и пр.) происходят в одном потоке, потоке терминала, и наша задача, как можно быстрее считать данные о событии, и освободить функцию получения события (см. мануал QLua). Если мы начнем обрабатывать данные в функции события, то все остальные события просто будут ждать своей очереди, а терминал встанет, и тоже будет ждать своей очереди на этом празднике жизни.
Однако, хотя в Lua и DLL реализовалось всего лишь чтение данных событий и преобразование их к формату C++, и далее обрабатывалось уже другими потоками DLL, тем не менее, при высокой интенсивности торгов терминал начинал заметно подвисать. И, хотя DLL и так уже занималось только чтением данных, при высокой интенсивности торгов даже и это занимало в потоке терминала слишком много времени.
Уже пару месяцев назад с этим геморроем надо было что-то делать, но ничего разумного в голову не приходило.
И, вот, вчера пришло (тугодум, блин).
Данные событий пишем в таблицы Lua, а DLL, уже в своих потоках в цикле, определяет обновились ли таблицы, и читает данные из этих таблиц. Цена вопроса — задержка получения данных не более 5 мс. Помогают нам в этом специфические потоконезависимые функции QLua sinsert и sremove.
Посмотрим что у нас получилось на примере стакана
Было:
-- получение стакана
function OnQuote(class, sec)
if class == EX_CODE and sec == FUT_S then
QL_s = getQuoteLevel2(EX_CODE, FUT_S)
dt = getInfoParam ('TRADEDATE') .. " " ..getInfoParam ('SERVERTIME')
ret =CCS.GlassPrice(FUT_S, dt, QL_s) -- вызов функции ДЛЛ
--message("Запись в ДБ S " .. tostring(ret))
elseif class == EX_CODE and sec == FUT_L then
--message("стакан FUT_L изменился.")
QL_l = getQuoteLevel2(EX_CODE,FUT_L)
dt = getInfoParam ('TRADEDATE') .. " " ..getInfoParam ('SERVERTIME')
ret =CCS.GlassPrice(FUT_L, dt, QL_l) -- вызов функции ДЛЛ
--message("Запись в ДБ L" .. tostring(ret))
end
end
Стало:
-- получение стакана
function OnQuote(class, sec)
if class == EX_CODE and sec == FUT_S and #quote_iv_S == 0 then
table.sinsert(quote_iv_S,{sec})
elseif class == EX_CODE and sec == FUT_L and #quote_iv_L == 0 then
table.sinsert(quote_iv_L,{sec})
end<br />end
Здесь мы даже саму функцию получения данных стакана getQuoteLevel2 вынесли за пределы потока события OnQuote.
И теперь мы с этого имеем работающую ТС в терминале, никаких задержек, никаких подвисаний.
Однако, процесс доработки системы остановить невозможно, а лучшее враг хорошего. Проблемы еще есть и будут, но это по мере их поступления и решения.
Так в документации же это описано.
Тоже наступал на эти грабли.
Ещё было сказано, что, по возможности, table.insert следует избегать, т.к. он работает гораздо медленнее, чем присвоение через прямое индексирование.
то есть,
my_table[10] = {'new'}
работает реально в разы быстрее, чем
table.insert(my_table, {'new'})
Правда, что с sinsert, не знаю, но, логично, что он должен быть ещё тормознутее.
А в event/callback-функциях всё складывается в массивы, и только, а они уже обрабатываются, например, в main.
здесь без sinsert и sremove никак не обойтись, т.к. запись и чтение-удаление данных из таблиц осуществляется асинхронно, разными потоками. Как бы замена мьютексов.
А так да, вроде и очевидно, и в доках описано жирными буквами:
правда серверное время уже давно не отстаёт от текущего
везде наставил пауз, где 0 мс, где целых 5 мс, кстати да, тоже 5 мс :)
кстати, если уж вы всё равно dll используете, так лучше реализовать OnQuote прямо в dll, и зарегать её через lua_pushcclosure, lua_setglobal. Спросите что это даст?
ну можно будет вообще таблицы lua не использовать, а писать сразу в свои,
и пользоваться штуками типа SetEvent, WaitForSingleObject.
кмк всё равно в момент опроса квиковых функций можно квик немножко подвесить, если ему приходит большой поток данных, при переподключениях и резких движениях, грешу на синхронизацию во внутренних таблицах квик
Потому, делается и так и эдак, исходя из конкретики. Единообразия нет. Скажем, main(), частично реализован в Lua, частично в DLL. Раньше был полностью сделан в DLL. В этом случае хорошо еще и то, что при обрыве цикла main в Lua, автоматом вызывается деинициализация и уничтожение объектов ДЛЛ.
>> Как мы знаем, Луа не в курсе действий своего C-API.
не понимаю это предложение. что-то мы наверно действительно не понимаем друг друга
еще одна попытка:
я имел в виду OnQuote сделать на C. там вкладывать в обычную C таблицу (а не в lua) приходящий seccode, и делать SetEvent.
в другом потоке в dll делать WaitForSingleObject и по сигналу SetEvent уже считывать из таблицы на C seccode, без sremove, но тем не менее с синхронизацией (уже на C), и дальше конечно обращаться к Lua за getQuoteLevel2, ну или читать из CreateDataSource/ ds::o,h,l,c[i]
то что многие вещи удобнее сделать на Lua, совершенно согласен. но переключение контекстов наверное тоже «денег стоит», хотя надо измерять.
Ну, а что действия С-API неизвестны и никак не контролируются Луа, эт в книге написано. Оч аккуратно надо из С++ со стеком работать, особенно, если из других потоков — можно нарваться. Более ничего не имелось ввиду.
т. е. если было:
if #X >= 5 then table.remove(X, 1) end;
Можно переписать так?
if #X >= 5 then table.sremove(X, [1]) end;
Только table.sremove(X, 1), видимо это очепатка.