Итераторы
После того, как написал несколько раз такой код:
function(command, ...) ... local combos = command.combos if combos then local deviceCombos = combos[deviceName] if deviceCombos then for i, combo in ipairs(deviceCombos) do -- что-то сделать с combo ... end end end ... end
мне стало понятно, что нужно придумать что-нибудь поизящнее, чем эти двойные проверки. И тут я вспомнил про итераторы! Programming in Lua говорит нам, что итератором может быть функция, которая при каждом вызове возвращает следующий элемент коллекции. Поскольку deviceCombos это массив, а мне при переборе индекс элемента не нужен, то задача упрощается:
function commandCombos(command, deviceName) local pos = 0 local combos local deviceCombos if command then combos = command.combos if combos then deviceCombos = combos[deviceName] end end return function() if deviceCombos then pos = pos + 1 return deviceCombos[pos] end end end
Обратите внимание, что переменная pos — это upvalue.
Теперь проверим получившуюся функцию:
local commandNil = nil local commandNoCombos = {} local commandEmptyCombos = { combos = {}, } local command = { combos = { mouse = { {key = 'MOUSE1', action = 1}, {key = 'MOUSE2', action = 2}, }, keyboard = { {key = 'A', action = 11}, {key = 'B', action = 22}, }, } } for combo in commandCombos(commandNil) do assert(false) end for combo in commandCombos(commandNoCombos) do assert(false) end for combo in commandCombos(commandNoCombos) do assert(false) end for combo in commandCombos(commandEmptyCombos) do assert(false) end for combo in commandCombos(command, 'mouse') do print('~~~mouse', combo.key, combo.action) end for combo in commandCombos(command, 'keyboard') do print('~~~keyboard', combo.key, combo.action) end
На выходе ожидаемо получаем:
~~~mouse MOUSE1 1
~~~mouse MOUSE2 2
~~~keyboard A 11
~~~keyboard B 22
Upvalues и вопросы производительности
Дано: список событий(массив таблиц).
Задача: с делать фильтр по полям событий.
Решение 1(прямое, как палка):
function getEventVisible(event, filter) if filter.initiatorId then if filter.initiatorId ~= event.initiatorMissionID then return false end end if filter.targetId then if filter.targetId ~= event.targetMissionID then return false end end if filter.objectId then if event.initiatorMissionID ~= filter.objectId and event.targetMissionID ~= filter.objectId then return false end end if filter.weaponId then if filter.weaponId ~= event.weapon then return false end end if filter.eventType then if filter.eventType ~= eventEventType then return false end end if filter.initiatorCoalition then if filter.initiatorCoalition ~= DebriefingMissionData.getUnitCoalitionName(event.initiatorMissionID) then return false end end if filter.targetCoalition then if filter.targetCoalition ~= DebriefingMissionData.getUnitCoalitionName(event.targetMissionID) then return false end end return true end
Тут мы видим, что некоторые поля используются несколько раз. Это срочно нужно оптимизировать:
function getEventVisible2(event, filter) local initiatorId = filter.initiatorId local targetId = filter.targetId local objectId = filter.objectId local weaponId = filter.weaponId local eventType = filter.eventType local initiatorCoalition = filter.initiatorCoalition local targetCoalition = filter.targetCoalition local getUnitCoalitionNameFunc = DebriefingMissionData.getUnitCoalitionName local eventInitiatorId = event.initiatorMissionID local eventTargetId = event.targetMissionID local eventWeaponId = event.weapon local eventEventType = event.type local eventInitiatorCoalition = getUnitCoalitionNameFunc(eventInitiatorId) local eventTargetCoalition = getUnitCoalitionNameFunc(eventTargetId) if initiatorId then if initiatorId ~= eventInitiatorId then return false end end if targetId then if targetId ~= eventTargetId then return false end end if objectId then if eventInitiatorId ~= objectId and eventTargetId ~= objectId then return false end end if weaponId then if weaponId ~= eventWeaponId then return false end end if eventType then if eventType ~= eventEventType then return false end end if initiatorCoalition then if initiatorCoalition ~= eventInitiatorCoalition then return false end end if targetCoalition then if targetCoalition ~= eventTargetCoalition then return false end end return true end
Тут можно было бы откинуться на кресле и сделать глоток крепкого виски… Но чу! Это что же такое получается? Для фильтра, который один для всех событий, значения полей каждый раз извлекаются из таблицы filter. Непорядок. А ну-ка, где тут у нас upvalues?
function getFilterFunc(filter) local initiatorId = filter.initiatorId local targetId = filter.targetId local objectId = filter.objectId local weaponId = filter.weaponId local eventType = filter.eventType local initiatorCoalition = filter.initiatorCoalition local targetCoalition = filter.targetCoalition local getUnitCoalitionNameFunc = DebriefingMissionData.getUnitCoalitionName return function(event) local eventInitiatorId = event.initiatorMissionID local eventTargetId = event.targetMissionID local eventWeaponId = event.weapon local eventEventType = event.type local eventInitiatorCoalition = getUnitCoalitionNameFunc(eventInitiatorId) local eventTargetCoalition = getUnitCoalitionNameFunc(eventTargetId) if initiatorId then if initiatorId ~= eventInitiatorId then return false end end if targetId then if targetId ~= eventTargetId then return false end end if objectId then if eventInitiatorId ~= objectId and eventTargetId ~= objectId then return false end end if weaponId then if weaponId ~= eventWeaponId then return false end end if eventType then if eventType ~= eventEventType then return false end end if initiatorCoalition then if initiatorCoalition ~= eventInitiatorCoalition then return false end end if targetCoalition then if targetCoalition ~= eventTargetCoalition then return false end end return true end end
Ну а теперь…(ТЕСТЫ!!! ТЕСТЫ!!!)
Я не вижу ваши руки!
local events = DebriefingEventsData.getEvents() local filterFunc = getFilterFunc(filter_) print('~~~event count:', #events) local t = os.clock() for i = 1, 1000 do local result = {} for i, event in ipairs(events) do if getEventVisible(event, filter_) then table.insert(result, event) end end end print('~~~getEventVisible', os.clock() - t) t = os.clock() for i = 1, 1000 do local result = {} for i, event in ipairs(events) do if getEventVisible2(event, filter_) then table.insert(result, event) end end end print('~~~getEventVisible2', os.clock() - t) t = os.clock() for i = 1, 1000 do local result = {} for i, event in ipairs(events) do if filterFunc(event) then table.insert(result, event) end end end print('~~~filterFunc', os.clock() - t)
Результаты:
~~~event count: 1467
~~~getEventVisible 1.32
~~~getEventVisible2 2.583
~~~filterFunc 2.286
~~~event count: 1467
~~~getEventVisible 0.698
~~~getEventVisible2 2
~~~filterFunc 1.706
~~~event count: 1467
~~~getEventVisible 1.326
~~~getEventVisible2 2.581
~~~filterFunc 2.284
~~~event count: 1467
~~~getEventVisible 1.642
~~~getEventVisible2 2.203
~~~filterFunc 1.984
~~~event count: 1467
~~~getEventVisible 1.506
~~~getEventVisible2 2.061
~~~filterFunc 1.822
~~~event count: 1467
~~~getEventVisible 1.338
~~~getEventVisible2 2.577
~~~filterFunc 2.275
~~~event count: 1467
~~~getEventVisible 0.75999999999999
~~~getEventVisible2 2.013
~~~filterFunc 1.712
~~~event count: 1467
~~~getEventVisible 1.322
~~~getEventVisible2 2.576
~~~filterFunc 2.281
~~~event count: 1467
~~~getEventVisible 1.323
~~~getEventVisible2 2.574
~~~filterFunc 2.288
~~~event count: 1467
~~~getEventVisible 1.687
~~~getEventVisible2 2.234
~~~filterFunc 1.999
~~~event count: 1467
~~~getEventVisible 1.717
~~~getEventVisible2 2.087
~~~filterFunc 1.808
~~~event count: 1467
~~~getEventVisible 1.743
~~~getEventVisible2 2.109
~~~filterFunc 1.83
Ой, что это? Наш прекрасный оптимизированный код работает хуже неоптимизированного? Похоже, что так. Причем местами «неоптимизированная» версия быстрее «оптимизированной» почти в 2 раза!
Думаю, что в getEventVisible2()
создается слишком много локальных переменных, причем некоторые из них могут не понадобиться. А что не так с filterFunc()
? Там вроде локальных переменных совсем немного? Похоже, что доступ к «замороженным» переменным не так уж и быстр, либо Lua при вызове этой функции должна откуда-то загрузить все upvalues.
В общем, как обычно с оптимизацией — все нужно проверять.
I love Lua upvalues!
Сегодня обнаружил повод для радости. Итак, встречайте — это upvalues!
Это связано с вчерашней темой про калбеки. Калбеком в окно передается Lua функция без параметров. А в нашем существующем редакторе, требуется вызывать функцию с параметром. Что нам поможет? Upvalues!
Дано:
Функция установки калбека для окна:
function setCallback(self, callback, key1, key2, key3, key4) gui.WindowSetCallback(self.widget, callback, key1, key2, key3, key4) end
и функция для установки клавишной комбинации:
-- ... - список кнопок function addKeyCombination(self, callback, object, ...) ??? end
для которой должно быть вызвано callback(object)
.
Что делать? А все очень просто:
-- ... - список кнопок function addKeyCombination(self, callback, object, ...) self:setCallback(function () callback(object) end, ...) end
Мы создаем безымянную функцию без параметров, внутри которой вызываем калбек с параметром. Многоточие пробрасывает остальные параметры функции addKeyCombination
в функцию setCallback
.
Лаконично. Красиво. Изящно.
Браво, Lua!
leave a comment