Очень хочется увидеть ответ от разработчиков по поводу изучения данной проблемы, хотя бы после новогодних праздников. Ниже описывается пример, как большое количество таблиц может появляться в реальной программе.
Предположим, что скрипту нужно хранить в памяти для работы 3000 свечей (сколько отдаёт сервер при запросе данных по ликвидным инструментам) по 10 инструментам и 5 таймфреймам. Время свечи QLua отдаёт в виде таблицы
Код
datetime = { year = 2021, month = 12, day = 30, hour = 11, min = 0, sec = 0, ms = 0, mcs = 0, ... }
Соответственно, сразу же имеем 3000 * 10 * 5 = 150 000 таблиц. А если скриптов несколько, то можно ещё на порядок увеличить количество таблиц в памяти.
Конечно, конкретно здесь можно закодировать дату в виде строки "2021-12-30T11:00:00.000" или вообще числом 20211230110000000 для эффективности, но придётся писать код для выделения из этого числа отдельных полей, и арифметика даты/времени станет неудобной.
В общем, просьба к разработчикам дать обратную связь, а то уже нехорошо выглядит такая техподдержка. Хоть пообещайте что-нибудь, как обычно.
Поскольку пользователи жалуются, что терминал тормозит, а МосБиржа запрещает разработчикам терминала тестирование в реальных условиях, то предлагаю сделать специальный режим работы терминала, который можно включить по желанию пользователя для профилирования. Пусть в этом режиме собирается нужная статистика по времени работы, количеству вызовов функций и пр. внутри терминала, чтобы её можно было бы потом отправить разработчикам для анализа. Важно сделать вывод результата работы в понятном текстовом виде, чтобы пользователь понимал, что именно он отправляет разработчикам, и не переживал по поводу конфиденциальности. Думаю, что тогда получится выявить много узких мест.
Если уже сейчас имеется способ запуска терминала под какого-то рода отладчиком с профилированием, тогда дайте знать, как это сделать. Для java есть, к примеру visualvm с мониторингом кучи, тредов, сборщика мусора и профилированием.
Сделал такой тест на Windows и под эмулятором Wine:
Код
function main()
message("Started at " .. os.date(), 1)
local count = 0
for i = 0, getNumberOf("all_trades") - 1 do
local item = getItem("all_trades", i)
if item then
count = count + 1
end
end
message("Ended at " .. os.date(), 1)
message("Count = " .. tostring(count))
end
Получились такие результаты: при 1.55 млн. обезличенных сделок Windows отрабатывает за 9 секунд, а Linux за 22 секунды, что приемлемо для вызова getItem(). А вот почему в 10 раз медленнее исполняется полный скрипт -- пока непонятно.
Александр написал: я уже пытаюсь 7 недель выяснить
Это легко сделать самостоятельно, отправив рыночную заявку и проверив направление вашей сделки в ТОС.
Я вчера тоже хотел эту мысль в качестве ответа написать, а потом по предоставленным автором темы файлам понял, что расхождения весьма нечастые. Дорого будет так проверять.
Ради интереса запустил QUIK 9.3.3.3 под эмулятором Wine (winehq stable 6.0.2) в виртуалке Ubuntu 21.10. Увидел, что медленно работает скрипт экспорта биржевых данных, причём та его часть, которая из тиков, накопленных за несколько часов с начала торговой сессии, в оперативной памяти формирует свечные данные. Ввода-вывода в этом фрагменте кода вообще никакого нет. Скорость в одном из замеров примерно в 10 раз медленнее, чем в случае Windows на аналогичном по скорости процессоре. Как мне кажется, это указывает на какое-то совсем тормозное место внутри терминала, которое в эмуляторе совсем проседает.
В коде в цикле по всем обезличенным сделкам есть обращение к функции getItem:
Код
for i = 0, getNumberOf("all_trades") - 1 do
local item = getItem("all_trades", i)
редкое обращение к getSecurityInfo(classCode, secCode).lot_size, результаты которого кэшируются, а в остальном идёт работа с оперативной памятью (много таблиц, представляющих собой свечные данные 1-минутного таймфрейма по всем тикерам из классов акций (Т+2) и фьючерсов (без опционов)).
Код функции, которая работает медленно, ниже.
Скрытый текст
Код
local function getSecCodeMinutes(secCodesTable)
if next(secCodesTable) == nil then
secCodesTable = nil
end
local tradeDate = getInfoParam("TRADEDATE")
local day, month, year = string.match(tradeDate, "(%d+).(%d+).(%d+)")
local yyyymmddTradeDate = year * 10000 + month * 100 + day
local lotSizes = {}
local newSecCodeMinutes = {}
for i = 0, getNumberOf("all_trades") - 1 do
local item = getItem("all_trades", i)
if item == nil then
-- очистка данных
return nil
end
-- Использование данных только за текущий торговый день и для разрешённых кодов классов
local datetime = item.datetime
local classCode = item.class_code
if Misc.yyyymmdd(datetime) == yyyymmddTradeDate and classCodes[classCode] then
local secCode = item.sec_code
if secCodesTable == nil or secCodesTable[secCode] then
local lotSize = lotSizes[secCode]
if lotSize == nil then
lotSize = (classCode == "SPBFUT") and 1 or getSecurityInfo(classCode, secCode).lot_size
lotSizes[secCode] = lotSize
end
local dirtyMinutes = newSecCodeMinutes[secCode]
if dirtyMinutes == nil then
dirtyMinutes = { count = 0, classCode = classCode }
newSecCodeMinutes[secCode] = dirtyMinutes
end
local count = dirtyMinutes.count
if count == 0 then
dirtyMinutes[1] = newCandle(item, lotSize)
dirtyMinutes.count = 1
else
local candle = dirtyMinutes[count]
if isSamePeriod(candle.hour, candle.min, datetime.hour, datetime.min, 1) then
updateCandle(candle, item.price, item.qty * lotSize)
else
count = count + 1
dirtyMinutes[count] = newCandle(item, lotSize)
dirtyMinutes.count = count
end
end
end
end
end
return newSecCodeMinutes
end
Старатель написал: Демонстрационный скрипт: Скрытый текст
Код
local n = 50000
for i = 1 , n do
_G[ "f" .. i] = function () end
end
local class = "SPBFUT"
local sec = "SiZ1"
local param = {"BIDDEPTHT", "OFFERDEPTHT" }
local run = true
function OnStop ()
run = nil
end
function main ()
assert( Subscribe_Level_II_Quotes (class, sec))
for i = 1 , # param do
ParamRequest(class, sec, param[i])
end
while run do sleep ( 500 ) end
Unsubscribe_Level_II_Quotes (class, sec)
for i = 1 , # param do
CancelParamRequest(class, sec, param[i])
end
end
function OnQuote (class_code, sec_code) end
function OnParam (class_code, sec_code) end
function OnAllTrade (alltrade) end
QUIK 9.3.1.11, Lua 5.4 Открыто, как минимум одно окно: стакан ликвидного инструмента. Конечно, никто не запускает скрипты с тысячами функций, но при нескольких запущенных скриптах с десятками функций при высокой активности на бирже получаем нихилую загрузку CPU.
ЗЫ: У кого "один скрипт на все случаи жизни" с парой функций, может игнорировать эту тему. Без флуда!
Здравствуйте! Ваше письмо получено, проблема изучается. Постараемся в ближайшее время дать ответ.
Подскажите, занимаются ли разработчики терминала сейчас этой проблемой или пока есть более приоритетные задачи? Интересует, по крайней мере, диагноз: удалось ли уже увидеть проблему производительности в этом нагрузочном тесте?
Старатель написал: Имеет ли этот пункт отношение к обсуждаемой здесь теме?
Тест комментария 55 в QUIK 9.3.3.3 (QLua 5.4) выполняется столько же как и в QUIK 8.13.1.16 (QLua 5.4)
Если производительность вернулась, то это хорошо. Значит представители "Арки" в курсе проблемы, но не сознались.
Вспоминается шутка про проприетарные форматы файлов. Их спецификации не раскрывают не потому, что там есть какие-то секретные ноу-хау, а потому, что стыдно.
Медленная работа Рабочего места QUIK в некоторых случаях при запуске или во время работы. Некорректное построение таблицы котировок в некоторых случаях.
Интересно, почему до сих пор техподдержка никак себя не проявила в этой теме? В заголовке ведь явно указано [BUG]. Похоже, придётся специальную тему в "Пожеланиях..." создать для ускорения реакции.
Деградация производительности мешает утром, когда происходит подключение к срочному рынку и заново приходят все обезличенные сделки с вечерней сессии. Если скрипты реагируют на соответствующие коллбэки и скриптов много, то происходят жуткие тормоза даже на современном железе. Конкретно у меня, несмотря на то, что реакция на обезличенные сделки минимальная, обезличенные сделки прогружаются минут десять с включенными скриптами и раз в 10 быстрее с выключенными. Судя по загрузке процессора (Core i7 9700K), упираемся именно в него. Уверенность в том, что в терминале есть какая-то жуткая неэффективность в этом месте, почти стопроцентная.
Убедительная просьба к техподдержке донести до разработчиков информацию по данной проблеме. Старатель предоставил описание тестового стенда, который даже на QUIK Junior демонстрирует деградацию производительности терминала в 2 раза по сравнению в более ранними версиями.
Проверил у себя на компьютере. Вот 3 картинки: до запуска скрипта, после запуска скрипта и после запуска скрипта, где размер массива ещё увеличен в 10 раз. Видно, что загрузка процессора вырастает до максимума.
Просьба к техподдержке передать информацию разработчикам и написать соответствующее сообщение в этой теме.
У меня есть вот такое объяснение, почему описанная проблема возникает.
У терминала есть свечной график за прошлый день. При подключении к серверу с утра в терминал приходят данные по обезличенным сделкам вчерашней вечерней сессии на срочном рынке. При этом терминал начинает обновлять свечи, но делает это "интеллектуально": при поступлении очередной обезличенной сделки, если она не относится к последней свече, он пропускает её (считает, что уже не надо использовать эту информацию), а вот про последнюю свечу терминал не может сказать, учтены данные или нет. По этой причине последняя свеча, всё-таки, обновляется. На ценовой рейндж H - L это не влияет, значение C станет опять правильным, когда все обезличенные сделки пройдут. Страдает только объём, который, понятное дело, удваивается.
Если такое объяснение корректно, то долгие разборки разработчиков с данной проблемой означают, что в текущей архитектуре приложения нельзя устранить такую неполадку.
Как будто у вас файл, куда записываются настройки -- это не тот файл, откуда они потом читаются. Проверьте, что у вас прописано в Настройках клиентского места: Программа - Файлы настроек. Либо файл с настройками read only. Брокер ни при чём, поверьте.
Вот пример загрузки CPU, которую process explorer выводит. При этом процессор Intel Core i7-9700K слабым не назовёшь, а загрузка ядра близка к максимуму 12.5%
Ещё вижу повышенное использование процессора в QUIK 9.3. Не подскажет ли кто, как можно при работающем терминале понять, что именно забирает на себя ресурсы CPU? Типа какого-то профилировщика запустить?
Я могу, конечно, пытаться наугад действовать: закрыть наименее нужные окна (стаканы, ТТТ), снизить интенсивность вывода скриптами данных в таблицы, созданные QLua, но хотелось бы "не блуждать в потьмах".
Со своей стороны заметил, что есть "узкое место" с выводом данных в таблицы, созданные QLua. Как будто внутри терминала избыточная синхронизация в этом месте. Причём заголовок окна обновляется быстро, а содержимое таблиц -- медленно (при специальном нагрузочном тесте). Я уже снизил количество строк в таблицах и интенсивность вывода, но, похоже, что всё равно затык в этом месте. Для ориентира по нагрузке: в терминале открыто 45 окон (обычные и созданные QLua).
Всё-таки, хотелось бы и ssl добавить в эту сборку, т.к. он много для чего нужен (почта, телеграм, зашифрованная передача по сети). Если кто уже собрал или может собрать luasec для 5.4, поделитесь результатом.
Обновление свечей происходит с некоторой частотой, которая зависит от многих факторов. Если активность на рынке повышенная, то отдельные сделки "слипаются" в одно обновление свечи.
При большом количестве открытых окон и запущенных скриптов терминал через несколько дней после запуска потребляет много памяти и тормозит при прокачивании данных после смены даты торговой сессии. Сравниваю с 8.13, где были те же самые настройки и условия эксплуатации.
Попробовал скомпоновать всё, что было выше, в одну кучу. Добавил dkjson.lua для работы с JSON. Добавил исполняемые файлы из LuaBinaries версии 5.4.2 (считаем, что с квиковской 5.4.1 есть совместимость).
Предполагается такой способ установки на компьютер: 1) Распаковать содержимое архива в папку D:\LuaForQuik 2) Прописать в своих скриптах пути package.path и package.cpath (более подробно ближе к концу процесса напишу).
Использовать можно как из терминала, так и запуская lua54.exe.
Есть ли там файлы, из которых можно работающий SSL получить для QLua 5.4? Если можно, то какие файлы нужно брать? Для версии 5.3 у меня были файлы: ssl/https.lua, ssl.lua, ssl.dll, ssl.lib.
Судя по ответу разработчиков QUIK https://forum.quik.ru/messages/forum10/message59594/topic5823/#message59594 пользователям QLua имеет смысл перейти на версию 5.4.1, которая более корректно работает по сравнению с версией 5.3.5. При этом пользователи, использующие популярные внешние библиотеки, будут вынуждены как-то подправить код и найти новые версии этих библиотек (на языке Lua или скомпилированные под 64-битную версию Windows 10/11).
У меня есть просьба к тем представителям сообщества, кто уже успешно осуществил такой переход: давайте сделаем что-то типа небольшого дистрибутива, который будет в открытом доступе (GitHub ???), и откуда можно будет скачать эти библиотеки и относительно просто подключить для использования в терминале QUIK (скажем, положив внутрь папки с терминалом в подпапку типа lua54libs).
Судя по вопросам, обсуждавшимся на форуме, в состав такого дистрибутива имеет смысл включить: socket для работы с сокетами; luasec для ssl; какие-то библиотеки для работы с SQL; какие-то библиотеки для реализации графического интерфейса типа iup.
Если есть ещё какие-то полезные библиотеки, напишите в этой теме.
Ещё раз подчеркну, что этот дистрибутив был бы полезным не только для программистов, но и для рядовых пользователей, которые не владеют навыками сборки в Visual C++ из исходников.
Кто что думает? Реально ли такое сделать для всеобщего блага? Тем более, что особых усилий прилагать не надо, просто в одном месте собрать и "причесать".
С точки зрения пользователя, наверное, проще перейти на Lua 5.4 и успокоиться. Возможно, что ошибка не устраняется или устраняется очень трудоёмко в рамках того, что было сделано для встраивания Lua 5.3 в терминал. Надо только используемые внешние библиотеки под версию Lua 5.4 найти.
После первичной установки QUIK из дистрибутива и запуске LUA скриптов по умолчанию теперь используется версия LUA 5.4.1.
Ошибка в работе функции SetUpdateCallback QLUA, приводящая к чрезмерному потреблению памяти.
Некорректное отображение значений в подсказке параметров свечи в левом верхнем углу графика.
В некоторых случаях не отображалась подсказка параметров свечи на графике.
При замене заявки снимался признак «Заявка маркет-мейкера».
Аварийное завершение работы терминала при добавлении двух криптопровайдеров.
Некорректный расчет значения поля «max» на форме ввода заявки для маржинальных инструментов при установленном признаке «Исходя только из собственных средств»
В некоторых случаях в таблице «Клиентский портфель» некорректно рассчитывался параметр «Стоимость портфеля».
В таблице «Купить/Продать» некорректно отображались дисконты по фьючерсным контрактам.
Старатель написал: Абсолютно беспочвенное утверждение, говорящее об отсутствии понимания работы обсуждаемого кода.
Вы имеете ввиду, что для записи и чтения в очередь используются свои указатели и, я бы с вами согласился, если бы не существовал общий объект, в котором это происходит (таблица хранения очереди в которую вставляются и из которой вычеркиваются элементы очереди). Вы уверены, что работа с таблицей при изменении ее структуры в Lua реализована потокобезопасно? Где это написано?
Разработчики ARQA, как я помню, утверждали с самого начала, что примитивные переменные и таблицы Lua не портятся при параллельном их изменении из разных потоков с помощью атомарных операций присваивания. Другое дело, что для таблиц это на самом деле может быть не так. Функции типа table.sinsert, table.sremove и т.п. пришлось вводить, т.к. они не атомарные и занимаются сдвигом внутренних элементов. Все ли подобные неатомарные штуки были "выловлены" разработчиками ARQA -- неизвестно. Если не все, то единственный адекватный вариант -- это создавать структуру в потоке коллбэков, потом table.sinsert, а в main-потоке делать table.sremove.
Теперь про "пропажу" функции pop(). Не исключаю, что время от времени терминал QUIK или операционная система занимаются неким "переносом данных внутри своей памяти" (например, при сборке мусора) во время которого оказывается, что функция pop ещё не определена (не успели её перенести) в новом месте, а её вызывают из другого потока. Но это уже мои домыслы.
Просьба предоставить возможность скачивать обновления терминала не только с ftp://ftp.quik.ru/public/updates . Как минимум, это не будет заставлять пользователей искать обходные и небезопасные пути для решения проблемы по скачиванию дистрибутива терминала.
TGB написал: В вашем коде, по вашей ссылке, используется в двух потоках потоконебезопасная очередь OnAllTrades. В API QLua есть функции реализации потокобезопасной очереди, созданные разработчиком QUIK: table.sinsert, table.sremove. Пример использования потокобезопасной очереди: https://forum.quik.ru/messages/forum10/message56397/topic6356/#message56397
На самом деле, push выполняется только в потоке коллбэков, pop и size -- только в main-потоке. Очередь, реализованная Старателем, в этом случае корректно работает и без блокировок.
Ошибки типа os.date() == nil означают, что где-то глубоко в QLua что-то не так. У меня подобное было в 2014-2015 годах, потом прошло и уже давно не было. Возможно, в 9-й версии опять что-то не так.
Лучше при штатной работе скриптов вообще не использовать OnStop, которая возникает при нажатии кнопки "Остановить", слишком много проблем от этого может возникнуть. Считайте, что это -- аварийный способ завершения скрипта. У себя я сделал для каждого из скриптов с GUI специальное окошко, при закрытии которого происходит поднятие флага прерывания и скрипт, периодически проверяя его, должен в разумное время завершить работу и освободить ресурсы. Да, это костыль, но зато не прерывается выполнение потока коллбэков и всё ещё работает штатно.
Андрей написал: Действительно ли могут прийти колбеки заявок других клиентов?
Конечно нет, это исключено. Только брокер может видеть заявки других клиентов.
Представьте себе два терминала, где в каждом из них настроен доступ к нескольким счетам клиента/клиентов, и этот набор счетов одинаковый для обоих терминалов. Если в одном из терминалов отправляется заявка, то другой терминал увидит коллбэки, связанные с ней.
Есть следующий "хак", гарантирующий, что выполнение кода будет только в одном потоке.
Код
table.ssort({ 0, 1 }, function(a, b)
тут_ваш_код
return true
end)
Идея в том, что выполняется "бесполезный" ssort на таблице из двух элементов с указанным компаратором. Ваш код выполнится один раз внутри этого компаратора.
Только не надо злоупотреблять этим, иначе терминал начнёт тормозить.
Тем временем, в Lua 5.4 продолжают находить баги. Так что если разработчики, всё-таки, будут обновлять версию, то просьба до самой последней обновить. https://www.lua.org/bugs.html#5.4.3
Новая версия терминала 8.13 для отладки должна была появиться в этой папке: ftp://ftp.quik.ru/public/updates/8.13/ но там на момент написания этого сообщения пусто. Не повезло 13-й версии, к сожалению.
Это не утечка. Это Lua не делает уборку мусора, пока его не накопится "достаточно" для того, чтобы уборка имела смысл. Если постоянно дёргать сборщик мусора, то это будет замедлять исполнение программы. Возможно, что Lua имеет свои "представления" о том, когда надо запускать сборку мусора, и они не совпадают с Вашими. Можете, как уже советовали, запускать сборку мусора самостоятельно в подходящие на Ваш взгляд моменты.
Правильный вопрос. Можно попробовать вот такую конструкцию, где используется синхронизация через table.ssort (см. документацию для прояснения деталей; в потоке коллбэков эта конструкция не нужна, а в потоке main нужна):
Код
local paramTable = {}
table.ssort({ 0, 1 }, function(a, b)
-- Тут делаем нужные операции, которые, по идее, должны быть выполнены атомарно
paramTable["bid"] = GetParamEx(аргументы для получения bid).param_value
paramTable["offer"] = GetParamEx(аргументы для получения offer).param_value
-- Далее аналогично
return true
end)
Но вопрос к разработчикам терминала такой: этим мы достигнем требуемого (см. первое сообщение темы)?