Работа колбеков

Страницы: 1
RSS
Работа колбеков
 
Просто наблюдение. Пока не могу подтвердить реальным тестом.

В последних версиях терминала, по ощущениям начиная с 12-ой, наличие колбека в скрипте стало ощутимо "тормозить" терминал.
Естественно и раннее колбек использовался только для передачи в очередь. Но теперь даже это приводит к периодическим замираниям терминала.
Особенно если у скрипта есть и окно со своим колбеком на обновление.

Наблюдаю у разных брокеров. Есть те, у кого это, можно сказать, постоянно. А есть те, у кого это только в периоды повышенной ликвидности.

Сильнее это проявляется если запустить несколько скриптов и даже в спокойное время. При этом на демо Quik Junior, где поток данных очевидно низкий, это малозаметно.

Хотелось бы понять - это у меня что-то вылезло и надо глубоко копать или такое есть и других.

Примечательно, что есть скрипт из одной строки запускающий dll реализующий jRPC сервер из Квика на плюсах. И если подписаться в нём на OnQuote, то происходит то же самое. Казалось бы - библиотека на ++, нет окон - а терминал не даёт переключить закладки, думает.
 
Ок. Сделал нагрузочный тест https://disk.360.yandex.ru/d/P7xB_poL0QlKfAВерсия терминала 13.02.20. Реальный брокер.

Создаётся простая таблица. По списку (всего 7) инструментов выводятся данные.

Чтобы была какая-то нагрузка, то берем два колбека OnParam и OnQuote. Данные в таблицу выводятся только из main. В колбеке OnParam вызывается getParamEx несколько раз (нагрузка же).

OnQuote только для записи флага. Чтение DOM тоже в main.

Чтобы хоть как-то смоделировать реальный скрипт, созданы библиотеки заглушки с большим число функций. В main вызывается по одной функции из них.

В итоге скрипт занимает примерно 1 мб. Что не так и много.

Запускаю на реальном брокере  и терминал "умирает". Не совсем, но уже ощутимо видно как таблица текущих торгов, да и весь интерфейс, перестаёт работать "плавно".
В таблице данные выводятся с задержками, приходится "пинать окно" (вспоминаем тему https://forum.quik.ru/messages/forum10/message49120/topic3777/#message49120)

Также на этом примере можно увидеть (по крайней мере у меня), что если не подключать большие библиотеки, то уже всё заметно живее. Т.е. банальный размер скрипта влияет.

При этом в более старых версиях терминала не замечалось особого влияния размера скрипта на работу.
 
можете сделать вывод времени входа и выхода для колбеков и для  main  c помощью точного счетчик Windows?
 
Давно эта тема обсуждалась, ещё в 9-й версии.
Оценить оверхэд от колбэков можно на следующих скриптах:
Код
local run = true
function OnStop()
  run = nil
end

function main()
  local n = getNumberOf("all_trades")
  local m = n
  message("Num all trades: " .. n)
  repeat
    sleep(1)
    m = n; n = getNumberOf("all_trades")
    if run == nil then return end
  until n < m
  local t = os.clock()
  message("Num all trades: " .. m)
  repeat
    sleep(1)
    n = getNumberOf("all_trades")
    if run == nil then return end
  until n >= m         -- ожидание пока прокачаются все сделки
  t = os.clock() - t
  message(string.format("%.1f s; num: %u; avg: %.3f ms", t, n, t / n * 1000))
end
Код
local run = true
function OnStop()
  run = nil
end

function main()
  local n = getNumberOf("all_trades")
  local m = n
  message("Num all trades: " .. n)
  repeat
    sleep(1)
    m = n; n = getNumberOf("all_trades")
    if run == nil then return end
  until n < m
  local t = os.clock()
  message("Num all trades: " .. m)
  repeat
    sleep(1)
    n = getNumberOf("all_trades")
    if run == nil then return end
  until n >= m         -- ожидание пока прокачаются все сделки
  t = os.clock() - t
  message(string.format("with OnAllTrade:  %.1f s; num: %u; avg: %.3f ms", t, n, t / n * 1000))
end

function OnAllTrade(alltrade) end
Перезаказываете обезличенные сделки и замерете время прокачки всех сделок в обоих случаях.
Важно: в поле «Выбрать классы инструментов» указать все инструменты без фильтров для более точных измерений!
У меня результаты такие:
Код
40.2 s; num: 5719275; avg: 0.007 ms
with OnAllTrade:  85.7 s; num: 5731311; avg: 0.015 ms
Скрытый текст

Видно, что в первом случае упор идёт на пропускную способность канала, т.е. гипотетически сделки могли загрузиться быстрее при более широком канале.
Во втором случае скрипт не успевает обрабатывать колбэки с той скоростью, с которой поступают сделки. А мы помним, что колбэки работают в том же потоке, что и GUI.
Если у вас разницы нет, что с колбэком, что без него, значит узким местом является не процессор, а например, пропускная способность сети (подключение по Wi-Fi) или сервера. И тогда таким образом оценить оверхэд не получится.

Цитата
Nikolay написал:
При этом на демо Quik Junior, где поток данных очевидно низкий, это малозаметно.
Поэтому у разработчиков всё отлично и "не воспроизводится".
Надо делать так, как надо. А как не надо - делать не надо.
 
Цитата
Старатель написал:
Оценить оверхэд от колбэков можно на следующих скриптах:
Как использовать:
  1. Настройка получения данных выполняется через пункт меню Система / Настройки / Основные настройки..., раздел «Программа» / «Получение данных» / «Обезличенные сделки»
    В поле «Выбрать классы инструментов» выбрать все без фильтров! Это важно для корректных измерений.
    Если у вас уже выбраны все классы без фильтров этот шаг можно пропустить. Если нет, - то нажать кнопку «Перезаказать данные».
     
  2. Подождать пока прокачаются все сделки и запустить первый скрипт.
     
  3. Снова перезаказать обезличенные сделки и дождаться, когда скрипт выведет итоговый результат и остановится.
     
  4. Повторить 3-й пункт со вторым скриптом.
Надо делать так, как надо. А как не надо - делать не надо.
 
Цитата
nikolz написал:
можете сделать вывод времени входа и выхода для колбеков и для  main  c помощью точного счетчик Windows?
Если без излишеств, просто через os.clock, то сами колбеки работаю микросекунды. main уже зависит о того, что делает, но там тоже не что-то существенное.

Я пока наблюдаю странное - подключаешь несколько библиотек на 1000 функций - и привет. Собственно это в тесте и сделано, чтобы проверить влияние этого.
Я, конечно, понимаю, что Lua - не тот язык в котором обычно пишутся модули на тысячи строк, но как-то я не встречал упоминаний о влиянии этого на производительность. Хотя если вспомнить World of Warcraft, то ему не мешает.

Пришлось разбираться, т.к. в последних версиях терминала стало всё очень медленно если запущены "большие" скрипты, банально вкладки переключаются со скрипом.
Пальцем в небо - что-то сделали с GC. У меня функции использую локальные переменные, а функций много. В итоге GC надо работать. Как он работает в Квике не знаю, но надеюсь, что независимо от основного потока.

Впрочем, приведённый нагрузочный тест не делает ничего такого, чтобы сильно потреблять память, а падение производительности есть.
 
Цитата
Nikolay написал:
Я пока наблюдаю странное - подключаешь несколько библиотек на 1000 функций - и привет.
    Странно. Я ваш тест проверял в QUIK 13.0.1.2 брокер ВТБ. Никаких особенностей не заметил (с подключением пакетов и без). В вашем тесте мной был выполнен замер Время выполнения всех коллбеков / Общее время выполнения скрипта: ~0,0004.
  Вы выполняли ваш тест на другом ПК?
 
Цитата
TGB написал:
Никаких особенностей не заметил (с подключением пакетов и без).
Сколько у вас колбэков в секунду?
Надо делать так, как надо. А как не надо - делать не надо.
 
Думаю, что всё зависит от времени, потока данных, да и брокера. На ВТБ позже проверю.
Я проверяю на одном небольшом брокере, у которого сервера не особо "широкие".

Вот в 9 утра на всплеске ликвидности запустил. Плюс, что важно, запущены и другие скрипты, но можно и этот дублировать и запускать, в реальности у нас же не один скрипт работает.

Вот участок , всего 125 млс, 9 колбеков

Enter main loop Wed Jul 22 09:03:18 2026
Enter OnQuote SPBFUT RIU6
Exit OnQuote 0.0 SPBFUT RIU6
---------------------------------------------------------------------------
Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 90318 2089.0
---------------------------------------------------------------------------
Enter OnParam SPBFUT RIU6
Exit OnParam 0.0 SPBFUT RIU6 90318 82730.0
---------------------------------------------------------------------------
Enter OnQuote SPBFUT MMU6
Exit OnQuote 0.0 SPBFUT MMU6
---------------------------------------------------------------------------
Enter OnParam TQBR SBER
Exit OnParam 0.0 TQBR SBER 90318 260.58
---------------------------------------------------------------------------
Enter OnParam TQBR GAZP
Exit OnParam 0.0 TQBR GAZP 90318 85.22
---------------------------------------------------------------------------
Enter OnQuote TQBR GAZP
Exit OnQuote 0.0 TQBR GAZP
---------------------------------------------------------------------------
Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 90318 2089.0
---------------------------------------------------------------------------
Enter OnParam SPBFUT RIU6
Exit OnParam 0.0010000000002037 SPBFUT RIU6 90318 82740.0
---------------------------------------------------------------------------
Exit main loop 0.125

А вот участок уже 1.36 секунды, но уже 222 колбека.

Enter main loop Wed Jul 22 09:03:19 2026

Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 90319 2089.0
---------------------------------------------------------------------------
Enter OnParam SPBFUT USDRUBF
Exit OnParam 0.0 SPBFUT USDRUBF 90319 78.6
---------------------------------------------------------------------------
Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 90319 2089.0
---------------------------------------------------------------------------
Enter OnParam SPBFUT SiU6
Exit OnParam 0.00099999999974898 SPBFUT SiU6 90319 79637.0
---------------------------------------------------------------------------
Enter OnParam SPBFUT RIU6
Exit OnParam 0.0 SPBFUT RIU6 90319 82730.0
---------------------------------------------------------------------------
Enter OnQuote SPBFUT SiU6
Exit OnQuote 0.0 SPBFUT SiU6
---------------------------------------------------------------------------
Enter OnQuote SPBFUT USDRUBF
Exit OnQuote 0.0 SPBFUT USDRUBF
---------------------------------------------------------------------------
Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 90319 2089.0
---------------------------------------------------------------------------
Enter OnParam SPBFUT RIU6
Exit OnParam 0.0 SPBFUT RIU6 90319 82730.0
---------------------------------------------------------------------------
...............................................................................................................
чтобы не делать сообщение очень длинным убрал часть
...............................................................................................................
---------------------------------------------------------------------------
Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 90320 2088.85
---------------------------------------------------------------------------
Enter OnParam SPBFUT SiU6
Exit OnParam 0.0 SPBFUT SiU6 90320 79634.0
---------------------------------------------------------------------------
Enter OnParam SPBFUT USDRUBF
Exit OnParam 0.0 SPBFUT USDRUBF 90320 78.6
---------------------------------------------------------------------------
Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 90320 2088.85
---------------------------------------------------------------------------
Exit main loop 1.3679999999999
 
Цитата
Старатель написал:
Сколько у вас колбэков в секунду?
   На тесте Nikolay:
1) OnQuote всего (в сек.) = ~68
2) OnParam всего (в сек.) = ~482
3) OnParam обработано отфильтрованных (в сек.) = ~16
   В моих скриптах эти колбэки не отрабатываются.
 
Ок. Запустил на VTB  в 10 утра. И это просто финиш. В основном была обработка main за 0-0.5 секунды.

А вот этот кусок за 593.92. Записал даже видео работы, где видно что происходит если запустить скрипт. Редактирование поля в таблице обновляется только после пинка. Цена не соответствует данным в ТТТ.
Как только скрипт остановлен, то ТТТ сразу оживает.

Видео https://disk.360.yandex.ru/i/VcfnlUv3JoFizQ


Enter main loop Wed Jul 22 10:01:25 2026
Enter OnParam SPBFUT USDRUBF
Exit OnParam 0.0 SPBFUT USDRUBF 100102 78.52
---------------------------------------------------------------------------
Enter OnParam SPBFUT CNYRUBF
Exit OnParam 0.0 SPBFUT CNYRUBF 100102 11.587
---------------------------------------------------------------------------
Enter OnParam SPBFUT SiU6
Exit OnParam 0.0 SPBFUT SiU6 100102 79532.0
---------------------------------------------------------------------------
Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 100102 2095.95
---------------------------------------------------------------------------

колбеки, колбеки ....


---------------------------------------------------------------------------
Enter OnParam SPBFUT CNYRUBF
Exit OnParam 0.0 SPBFUT CNYRUBF 100754 11.603
---------------------------------------------------------------------------
Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 100754 2087.15
---------------------------------------------------------------------------
Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 100754 2087.15
---------------------------------------------------------------------------
Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 100754 2087.15
---------------------------------------------------------------------------
Enter OnParam SPBFUT SiU6
Exit OnParam 0.0 SPBFUT SiU6 100754 79628.0
---------------------------------------------------------------------------
Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 100754 2087.15
---------------------------------------------------------------------------
Enter OnParam SPBFUT MMU6
Exit OnParam 0.0 SPBFUT MMU6 100754 2087.15
---------------------------------------------------------------------------
Exit main loop 593.92
---------------------------------------------------------------------------
 
Еще раз тест, что запустил на VTB
Код
local path = _G.getScriptPath()
package.path = path.."/?.lua;"

local bl1 = require('big_lib')
local bl2 = require('big_lib2')
local bl3 = require('big_lib3')
local bl4 = require('big_lib4')

local logFile

local function log_tostring(...)
    local n = select('#', ...)
    if n == 1 then
    return tostring(select(1, ...))
    end
    local t = {}
    for i = 1, n do
    t[#t + 1] = tostring((select(i, ...)))
    end
    return table.concat(t, " ")
end

local function log(...)
    if logFile==nil then return end
    logFile:write(log_tostring(...).."\n");
    logFile:flush();
end


local sec_list      = {
    {'SPBFUT', 'MMU6'},
    {'SPBFUT', 'SiU6'},
    {'SPBFUT', 'RIU6'},
    {'SPBFUT', 'USDRUBF'},
    {'SPBFUT', 'CNYRUBF'},
    {'TQBR', 'SBER'},
    {'TQBR', 'GAZP'}
}

local rev_lis = {}
for i = 1, #sec_list do
    rev_lis[sec_list[i][2]] = i
end

local sleep         = _G.sleep
local isRun         = true

local t_id          = nil

-- local SeaGreen      = 12713921  --  RGB(193, 255, 193) нежно-зеленый
-- local RosyBrown     = 12698111  --  RGB(255, 193, 193) нежно-розовый
-- local GetCell       = _G.GetCell
-- local Highlight     = _G.Highlight
local SetCell           = _G.SetCell
local getParamEx        = _G.getParamEx
local getInfoParam      = _G.getInfoParam
local getQuoteLevel2    = _G.getQuoteLevel2
local IsWindowClosed    = _G.IsWindowClosed
local message           = _G.message
local table_sremove     = _G.table.sremove
local queue             = {}
local get_dom           = {}

function _G.OnParam(class_code,  sec_code)
    local s = os.clock()
    if t_id and rev_lis[sec_code] then
        log('Enter OnParam', class_code,  sec_code)
        local last_price = tonumber((getParamEx(class_code,  sec_code, 'LAST') or {}).param_value) or nil
        local res = {}
        res.last_time  = tonumber((getParamEx(class_code,  sec_code, 'TIME') or {}).param_value) or 0
        if res.last_time == 0 then return end
        res.last_price = last_price
        if not res.last_price then return end
        local bid_depth  = tonumber((getParamEx(class_code,  sec_code, 'BIDDEPTHT') or {}).param_value) or 0
        res.best_bid   = bid_depth > 0 and tonumber((getParamEx(class_code,  sec_code, 'BID') or {}).param_value) or nil
        local ask_depth  = tonumber((getParamEx(class_code,  sec_code, 'OFFERDEPTHT') or {}).param_value) or 0
        res.best_ask   = ask_depth > 0 and tonumber((getParamEx(class_code,  sec_code, 'OFFER') or {}).param_value) or nil
        res.last_rec   = tonumber((tostring(getInfoParam('LASTRECORDTIME') or ''):gsub(':', ''))) or 0
        res.local_time = (tonumber((tostring(getInfoParam('LOCALTIME') or ''):gsub(':', ''))) or 0)
        res.time_diff  = math.abs(res.local_time - res.last_rec) + 10

        queue[#queue+1] = {class_code,  sec_code, last_price}
        log('Exit OnParam', os.clock() - s, class_code,  sec_code, res.last_rec, last_price)
        log('---------------------------------------------------------------------------')
    end
end

function _G.OnQuote(class_code,  sec_code)
    local s = os.clock()
    if t_id and rev_lis[sec_code] and not get_dom[sec_code] then
        log('Enter OnQuote', class_code,  sec_code)
        get_dom[sec_code] = {class_code,  sec_code}
        log('Exit OnQuote', os.clock() - s, class_code,  sec_code)
        log('---------------------------------------------------------------------------')
   end
end

local function CreateTable()

    t_id = _G.AllocTable()

    _G.AddColumn(t_id, 0, "sec", true, _G.QTABLE_STRING_TYPE, 15)
    _G.AddColumn(t_id, 1, "price", true, _G.QTABLE_DOUBLE_TYPE, 15)
    _G.AddColumn(t_id, 2, "bid", true, _G.QTABLE_DOUBLE_TYPE, 15)
    _G.AddColumn(t_id, 3, "ask", true, _G.QTABLE_DOUBLE_TYPE, 15)
    _G.AddColumn(t_id, 4, "edit", true, _G.QTABLE_DOUBLE_TYPE, 15)
    _G.CreateWindow(t_id)
    _G.SetWindowPos(t_id, 90, 120, 470, 300)
    for i = 1, #sec_list do
        _G.InsertRow(t_id, i)
        SetCell(t_id, i, 0, sec_list[i][2])
    end
end

local function event_callback(_, msg, par1, par2)
    log('Enter TableNotificationCallback')
    if (msg == _G.QTABLE_CLOSE) then
        isRun = false
    end
    if msg == _G.QTABLE_CHAR then
        SetCell(t_id, par1, 4, tostring(par2), par2)
    end
    log('Exit TableNotificationCallback')
    log('---------------------------------------------------------------------------')
end

local function process_queue()
    if #queue == 0 then return end
    while #queue > 0 do
        local last_price = queue[1][3]
        -- local lp = GetCell(t_id, 1, 0).value or last_price
        -- if lp < last_price then
        --     Highlight(t_id, 1, 0, SeaGreen, 0, 500)
        -- elseif lp > last_price then
        --     Highlight(t_id, 1, 0, RosyBrown, 0, 500)
        -- end
        SetCell(t_id, rev_lis[queue[1][2]], 1, tostring(last_price), last_price)
        table_sremove(queue, 1)
    end
end

local function GetQuotes(class_code, sec_code)

    local q             = getQuoteLevel2(class_code, sec_code)
    if not q then return end

    local qbid          = q["bid"]
    local qask          = q["offer"]
    if not qbid and not qask then
        return
    end

    local best_bid = tonumber(qbid[#qbid]["price"])
    local best_ask = tonumber(qask[1]["price"])

    SetCell(t_id, rev_lis[sec_code], 2, tostring(best_bid), best_bid)
    SetCell(t_id, rev_lis[sec_code], 3, tostring(best_ask), best_ask)
    get_dom[sec_code] = nil

end

function _G.OnInit()
    CreateTable()
    _G.SetTableNotificationCallback(t_id, event_callback)
end

function _G.main()

    for i = 1, #sec_list do
        if not _G.Subscribe_Level_II_Quotes(sec_list[i][1], sec_list[i][2]) then
            return message('Не удалось подписаться на получение данных стакана по инструменту '..tostring(sec_list[i][1])..'|'..tostring(sec_list[i][2]))
        end
    end

    logFile = io.open(path.."\\load_test.txt", "w")

    local lt = os.time()

    while isRun do
        local s = os.clock()
        log('Enter main loop', os.date())
      IsWindowClosed(t_id)
        local ct = os.time()
        local u
        -- if ct-lt >= 2 then
            -- for i = 1, #sec_list do
                -- queue[#queue+1] = {sec_list[i][1], sec_list[i][2], tonumber(getParamEx(sec_list[i][1], sec_list[i][2], 'LAST').param_value)}
                -- get_dom[sec_list[i][2]] = {sec_list[i][1], sec_list[i][2]}
                -- u = true
            -- end
            -- lt = ct
        -- end
        process_queue()
        for i = 1, #sec_list do
            if get_dom[sec_list[i][2]] then
                GetQuotes(sec_list[i][1], sec_list[i][2])
            end
        end
        bl1.func1()
        bl2.func100()
        bl3.func200()
        bl4.func300()
        log('Exit main loop', os.clock()-s, u and 'update' or '')
        log('---------------------------------------------------------------------------')
        sleep(60)
    end
end

function _G.OnStop()
    isRun = false
    if t_id and not IsWindowClosed(t_id) then
        _G.DestroyTable(t_id)
    end
end

 
Цитата
Nikolay написал:
Запустил на VTB  в 10 утра. И это просто финиш.
   А если запустить только ваш тест без остальных ваших скритов?
 
Правда здесь уже, видимо, проявилась нагрузка на запись лога внутри колбеков, т.к. запись в файл внутри колбека -  это очень плохая идея.
Поэтому лучше заменить логирование на PrintDbgStr.
Код
local function log(...)
    PrintDbgStr(log_tostring(...))
end

В первой версии теста лога не было. Но, с другой стороны, это тоже такой тест, явно  показывающий как main просто перестает вызываться.

Цитата
TGB написал:
В моих скриптах эти колбэки не отрабатываются.
Тоже, особенно OnQuote. Но бывают задачи где это необходимо и я стал замечать проблемы. Пришлось смотреть, что происходит.
 
Цитата
Nikolay написал:
log('Exit OnParam', os.clock() - s, class_code,  sec_code, res.last_rec, last_price)
Вы так ничего не измерите. Обработка кода внутри колбэка идёт на ура. Оверхэд возникает из-за самого вызова колбэка, даже пустого. Смотрите мой пример выше.[QUOTE]
Надо делать так, как надо. А как не надо - делать не надо.
 
Цитата
Старатель написал:
Вы так ничего не измерите. Обработка кода  внутри  колбэка идёт на ура. Оверхэд возникает из-за самого вызова колбэка, даже пустого. Смотрите мой пример  выше
Возможно, но моя задача понять что вызывает проблемы. Пока я натолкнулся на влияние на работу main при странном сочетание наличия таблицы и обработки её реакций, библиотек и колбеков. Сами колбеки, судя по всему, работают быстро.
 
Цитата
TGB написал:
А если запустить только ваш тест без остальных ваших скритов?
Выключил все скрипты, только этот. Лог в PrintDbgStr. Вот участок с странным временем. Для начала, есть OnParam с 244 млс. Общее время 1.193 сек для 55 отфильтрованных колбеков.

[17220] ---------------------------------------------------------------------------
[17220] Enter main loop Wed Jul 22 11:05:47 2026
17220] Enter OnParam SPBFUT MMU6
[17220] Exit OnParam 0.2440000000006 SPBFUT MMU6 110547 2110.85
[17220] ---------------------------------------------------------------------------
[17220] Enter OnQuote SPBFUT MMU6
[17220] Exit OnQuote 0.0019999999985885 SPBFUT MMU6
[17220] ---------------------------------------------------------------------------
[17220] Enter OnQuote TQBR SBER
[17220] Exit OnQuote 0.0 TQBR SBER
................................................................................................................
[17220] ---------------------------------------------------------------------------
[17220] Enter OnParam SPBFUT MMU6
[17220] Exit OnParam 0.0 SPBFUT MMU6 110548 2110.65
[17220] ---------------------------------------------------------------------------
[17220] Enter OnParam SPBFUT SiU6
[17220] Exit OnParam 0.0 SPBFUT SiU6 110548 79593.0
[17220] ---------------------------------------------------------------------------
[17220] Enter OnParam SPBFUT MMU6
[17220] Exit OnParam 0.0 SPBFUT MMU6 110548 2110.65
[17220] ---------------------------------------------------------------------------
[17220] Exit main loop 1.1930000000011
[17220] ---------------------------------------------------------------------------

И вот такие моменты проявляются. Естественно, чем больше скриптов запущено, тем, возможно, больше влияние.
 
Цитата
Nikolay написал:
Выключил все скрипты, только этот. Лог в PrintDbgStr
  А если отключить запись в пользовательскую таблицу?
 
Цитата
Nikolay написал:
Код
if t_id and rev_lis[sec_code] then
        log('Enter OnParam', class_code,  sec_code)
        local last_price = tonumber((getParamEx(class_code,  sec_code, 'LAST') or {}).param_value) or nil
        local res = {}
        res.last_time  = tonumber((getParamEx(class_code,  sec_code, 'TIME') or {}).param_value) or 0
        if res.last_time == 0 then return end
        res.last_price = last_price
        if not res.last_price then return end
        local bid_depth  = tonumber((getParamEx(class_code,  sec_code, 'BIDDEPTHT') or {}).param_value) or 0
        res.best_bid   = bid_depth > 0 and tonumber((getParamEx(class_code,  sec_code, 'BID') or {}).param_value) or nil
        local ask_depth  = tonumber((getParamEx(class_code,  sec_code, 'OFFERDEPTHT') or {}).param_value) or 0
        res.best_ask   = ask_depth > 0 and tonumber((getParamEx(class_code,  sec_code, 'OFFER') or {}).param_value) or nil
        res.last_rec   = tonumber((tostring(getInfoParam('LASTRECORDTIME') or ''):gsub(':', ''))) or 0
        res.local_time = (tonumber((tostring(getInfoParam('LOCALTIME') or ''):gsub(':', ''))) or 0)
        res.time_diff  = math.abs(res.local_time - res.last_rec) + 10

        queue[#queue+1] = {class_code,  sec_code, last_price}
        log('Exit OnParam', os.clock() - s, class_code,  sec_code, res.last_rec, last_price)
   Из выше приведенного, где может быть блокировка или зацикливание, "темный" фрагмент ?:
Код
res.last_rec   = tonumber((tostring(getInfoParam('LASTRECORDTIME') or ''):gsub(':', ''))) or 0
res.local_time = (tonumber((tostring(getInfoParam('LOCALTIME') or ''):gsub(':', ''))) or 0)

 Формально, почему бы нет, но функция  getInfoParam в высокочастотном  OnParam подозрительна.
 
Цитата
TGB написал:
функция  getInfoParam в высокочастотном  OnParam подозрительна
Ничего особенного по ней в описании нет. Впрочем, документация очень шаткая на данный момент.

Проблема OnParam в том, что он очень убог. Этот колбек должен быть реализован в виде:

OnParam(STRING class_code, STRING sec_code, TABLE chng_params)

где chng_params - это таблица с изменившимися ключами и их значениями (и еще лучше добавить время пакета в котором это приехало). А подписка на такой колбек должна быть адресной - указать инструмент и набор полей для отслеживания.

В текущей реализации приходится DDOS-ить API чтобы получить данные. Сделки могут быть раз в час, цена посл. сделки может не изменятся, а будет постоянно менять лучший спрос, предложение (есть такие инструменты где роботы постоянно меняют стакан) - постоянный холостой вызов колбека.

Впрочем, этот тест просто для нагрузки, так что можно и запрашивать данные. Тот же LASTRECORDTIME важен, т.к. иногда приходят опоздавшие данные и их лучше пропускать.

Цитата
TGB написал:
А если отключить запись в пользовательскую таблицу?
Тогда это будет скрипт без таблицы, по факту. Хотелось бы решить вопрос при наличии таблицы. Я специально добавил постоянный опрос IsWindowClosed.
 
Цитата
Nikolay написал:
Тот же LASTRECORDTIME важен, т.к. иногда приходят опоздавшие данные и их лучше пропускать.
В LASTRECORDTIME проставляется время сервера или клиента?
Надо делать так, как надо. А как не надо - делать не надо.
 
Цитата
Старатель написал:
В LASTRECORDTIME проставляется время сервера или клиента?
По моим наблюдениям - сервера.
 
Цитата
Nikolay написал:
Ничего особенного по ней в описании нет.

 Все таки предлагается протестировать измененный вариант:
Код
res.last_rec    = 0  ---   tonumber((tostring(getInfoParam('LASTRECORDTIME') or ''):gsub(':', ''))) or 0
res.local_time = 0 --- (tonumber((tostring(getInfoParam('LOCALTIME') or ''):gsub(':', ''))) or 0)
 
Цитата
Nikolay написал:
Ничего особенного по ней в описании нет.

 Может  быть протестировать измененный вариант:
Код
res.last_rec    = 0  ---   tonumber((tostring(getInfoParam('LASTRECORDTIME') or ''):gsub(':', ''))) or 0
res.local_time = 0 --- (tonumber((tostring(getInfoParam('LOCALTIME') or ''):gsub(':', ''))) or 0)
 
Цитата
Старатель написал:
Цитата
Старатель написал:
Оценить оверхэд от колбэков можно на следующих скриптах:
Как использовать:
 Настройка получения данных выполняется через пункт меню Система / Настройки / Основные настройки..., раздел «Программа» / «Получение данных» / «Обезличенные сделки»
В поле «Выбрать классы инструментов» выбрать  все без фильтров!  Это важно для корректных измерений.
Если у вас уже выбраны все классы без фильтров этот шаг можно пропустить. Если нет, - то нажать кнопку «Перезаказать данные».
   Подождать пока прокачаются все сделки и запустить первый скрипт.
   Снова перезаказать обезличенные сделки и дождаться, когда скрипт выведет итоговый результат и остановится.
   Повторить 3-й пункт со вторым скриптом.
122.6 s; num: 7088692; avg: 0.017 ms
with OnAllTrade:  378.9 s; num: 7125638; avg: 0.053 ms

13.0.1.2
 
Цитата
TGB написал:
Может  быть протестировать измененный вариант:
Вот убрал запрос getInfoParam, запустил. Всего 86 колбеков. Все они в 1 мкс. Но main выполнялся 1.37 сек. Как-то не верится, что банальный разбор такой небольшой очереди будет столько, хоть и через очень неэффективную table.sremove. Возможна и она проблема. Позже сделаю тест с другой двойной очередью без table.sremove.

[17220] Enter main loop Wed Jul 22 17:27:34 2026
[17220] Enter OnQuote SPBFUT MMU6
[17220] Exit OnQuote 0.0 SPBFUT MMU6
[17220] ---------------------------------------------------------------------------
[17220] Enter OnQuote SPBFUT SiU6
[17220] Exit OnQuote 0.0 SPBFUT SiU6
[17220] ---------------------------------------------------------------------------
[17220] Enter OnQuote SPBFUT RIU6
[17220] Exit OnQuote 0.0 SPBFUT RIU6
[17220] ---------------------------------------------------------------------------
[17220] Enter OnQuote TQBR SBER
[17220] Exit OnQuote 0.0 TQBR SBER
[17220] ---------------------------------------------------------------------------
[17220] Enter OnQuote TQBR GAZP
[17220] Exit OnQuote 0.0 TQBR GAZP
[17220] ---------------------------------------------------------------------------
[17220] Enter OnParam SPBFUT SiU6
[17220] Exit OnParam 0.0 SPBFUT SiU6 nil 79273.0
[17220] ---------------------------------------------------------------------------
[17220] Enter OnParam SPBFUT MMU6
[17220] Exit OnParam 0.0010000000038417 SPBFUT MMU6 nil 2180.4
[17220] ---------------------------------------------------------------------------
[17220] Enter OnParam SPBFUT SiU6
[17220] Exit OnParam 0.0010000000038417 SPBFUT SiU6 nil 79273.0
[17220] ---------------------------------------------------------------------------

...................................................................................................................................
[17220] ---------------------------------------------------------------------------
[17220] Enter OnParam SPBFUT USDRUBF
[17220] Exit OnParam 0.0 SPBFUT USDRUBF nil 78.34
[17220] ---------------------------------------------------------------------------
[17220] Enter OnParam SPBFUT MMU6
[17220] Exit OnParam 0.0 SPBFUT MMU6 nil 2180.4
[17220] ---------------------------------------------------------------------------
[17220] Enter OnParam SPBFUT SiU6
[17220] Exit OnParam 0.0 SPBFUT SiU6 nil 79275.0
[17220] ---------------------------------------------------------------------------
[17220] Exit main loop 1.3700000000026  
 
Цитата
Станислав написал:
122.6 s; num: 7088692; avg: 0.017 ms
with OnAllTrade:  378.9 s; num: 7125638; avg: 0.053 ms
Оверхэд на колбэк не менее 0.036 мс, а то и больше. Не знаю, будут ли эти значения такими же для OnParam, OnQuote и др. Но если да, то умножте эту величину на количество всех колбэков в секунду, которые получает рабочий скрипт, и получите время, затрачиваемое только на вызов колбэков в течение 1 секунды. И это без их фактической обработки. Особенно это актуально в активные часы торгов, когда событий больше
Надо делать так, как надо. А как не надо - делать не надо.
 
Цитата
Nikolay написал:
Как-то не верится, что банальный разбор такой небольшой очереди будет столько, хоть и через очень неэффективную table.sremove. Возможна и она проблема. Позже сделаю тест с другой двойной очередью без table.sremove.
    Очередь, реализованная таким образом, что в ней невозможны блокировки, если она используется только между двумя потоками (main и потоком коллбеков). Варианты реализации такой очереди на форуме выкладывались не один раз (в том числе Старателем), но так как реализация короткая, то я повторюсь:
Код:
Код
----   Функции работы с очередями --
--- При использовании между потоком колбеков и потоком main очереди потокобезопасные. ---
--- Создать очередь 
function QueueNew()
   return  {first = 1, last = 0 }
end
---  Записать в очередь --
function QueuePush(self, v)
   if v == nil then error('!!! Ошибка. Параметр v функции QueuePush nil') end
   local last = self.last + 1 
   self[last] = v
   self.last = last
   return last - self.first + 1    -- количество элементов в очереди ---
end 
---  Читать очередь  --
function QueuePop(self)
   local  first =  self.first
   if  first > self.last then return nil   end 
   local v, v1  =  self[first], self[first + 1]
   self.first = first + 1 
   self[first] =  nil 
   v1 = type (v1) == 'string' and #v1 or 0
   return  v, v1   -- v1 -длина следующего элемента (если строка) ---
end
  
-- Текущий размер очереди ---
function QueueSize(self)
   return self.last - self.first + 1 
end 
Страницы: 1
Читают тему
Наверх