Блог им. 3Qu

Победил подвисание Quik при включенной ТС. Это просто праздник какой-то.(с)

    • 30 марта 2021, 15:04
    • |
    • 3Qu
  • Еще

При включенной ТС при интенсивных торгах регулярно подвисал 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.
И теперь мы с этого имеем работающую ТС в терминале, никаких задержек, никаких подвисаний.
Победил подвисание Quik при включенной ТС. Это просто праздник какой-то.(с)

Однако, процесс доработки системы остановить невозможно, а лучшее враг хорошего. Проблемы еще есть и будут, но это по мере их поступления и решения.
  • обсудить на форуме:
  • QUIK
★23
18 комментариев
ничога не поняна, но оченя интересна!!!
Семён Семёныч! ©

Так в документации же это описано.
Тоже наступал на эти грабли.
Ещё было сказано, что, по возможности, table.insert следует избегать, т.к. он работает гораздо медленнее, чем присвоение через прямое индексирование.

то есть,
my_table[10] = {'new'}
работает реально в разы быстрее, чем
table.insert(my_table, {'new'})

Правда, что с sinsert, не знаю, но, логично, что он должен быть ещё тормознутее.

А в event/callback-функциях всё складывается в массивы, и только, а они уже обрабатываются, например, в main.
avatar
bascomo, конечно написано, но реализация че-то в голову не шла. Только когда решил, что 5 мс не время — вот тогда все сложилось.)
здесь без sinsert и sremove никак не обойтись, т.к. запись и чтение-удаление данных из таблиц осуществляется асинхронно, разными потоками. Как бы замена мьютексов.
avatar
Ну надо было просто спросить раньше :)

А так да, вроде и очевидно, и в доках описано жирными буквами:




avatar
bstone, разумеется, очевидно, даже изначально. Но решение каждой конкретной задачи очевидно далеко не всегда. По разным причинам.)
avatar
у меня вообще колбэки не используются, но подвисания случаются
правда серверное время уже давно не отстаёт от текущего
везде наставил пауз, где 0 мс, где целых 5 мс, кстати да, тоже 5 мс :)

кстати, если уж вы всё равно dll используете, так лучше реализовать OnQuote прямо в dll, и зарегать её через lua_pushcclosure, lua_setglobal. Спросите что это даст?
ну можно будет вообще таблицы lua не использовать, а писать сразу в свои,
и пользоваться штуками типа SetEvent, WaitForSingleObject.
avatar
ПBМ, имхо, это и есть единственно верный вариант.
avatar
ПBМ, я использую WaitForMultipleObjects вместо sleep в main.
avatar
Khan Tengri, у меня только одно событие, его и жду, а в промежутках когда его ожидать нет смысла, пользуюсь sleep

кмк всё равно в момент опроса квиковых функций можно квик немножко подвесить, если ему приходит большой поток данных, при переподключениях и резких движениях, грешу на синхронизацию во внутренних таблицах квик
avatar
ПBМ, коллбеки либо из потока main, либо из потоков DLL, и подвисаний по этой причине не будет.
avatar
ПBМ, использование Луа из С++ выигрыша не дает — работает та же виртуальная машина Луа + еще преобразования С++ ->Lua. А вот код такое использование усложняет.
Потому, делается и так и эдак, исходя из конкретики. Единообразия нет. Скажем, main(), частично реализован в Lua, частично в DLL. Раньше был полностью сделан в DLL. В этом случае хорошо еще и то, что при обрыве цикла main в Lua, автоматом вызывается деинициализация и уничтожение объектов ДЛЛ.
avatar
3Qu, я про выигрыш и виртуальную машину не говорил. Это вы сами придумали и сами опровергли. Зачем виртуальную машину в колбэках использовать, если они на C? Пишите тикер из С колбека сразу в таблицу на C, которая никак не связана с Lua. Вот и выигрыш, которого якобы нет.
avatar
ПBМ, видимо, неверно вас понял. Там речь об OnQuote в ДЛЛ шла. Но тогда и getQuoteLevel2 с sremove придется реализовать из ДЛЛ или уходить обратно в Луа, со всеми вытекающими… Как мы знаем, Луа не в курсе действий своего C-API.
avatar
3Qu, 
>> Как мы знаем, Луа не в курсе действий своего 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, совершенно согласен. но переключение контекстов наверное тоже «денег стоит», хотя надо измерять.
avatar
ПBМ, 
то что многие вещи удобнее сделать на Lua, совершенно согласен. но переключение контекстов наверное тоже «денег стоит», хотя надо измерять.
Потому вся передача данных в ДЛЛ сделана на Луа, и больше, за редкими исключениями, С++ в Луа не возвращается.
Ну, а что действия С-API неизвестны и никак не контролируются Луа, эт в книге написано. Оч аккуратно надо из С++ со стеком работать, особенно, если из других потоков — можно нарваться. Более ничего не имелось ввиду.
avatar

т. е. если было:
if #X >= 5 then table.remove(X, 1) end;
Можно переписать так?
if #X >= 5 then table.sremove(X, [1]) end;



avatar
Glago, да, но это нужно только тогда, когда запись, чтение и удаление данных из таблицы осуществляется разными потоками, и может случиться, что будет происходить одновременно. В остальных случаях оно не надо.
Только table.sremove(X, 1), видимо это очепатка.
avatar

теги блога 3Qu

....все тэги



UPDONW
Новый дизайн