Добавлен PT153,
опубликован
Раздел:
Триггеры и объекты
Что такое утечка памяти?
Утечка памяти - это ОПГ, которого боялись даже чеченцы не удалённый объект, который больше нигде не используется. Типичный случай утечки - не удалённая точка или группа юнитов. Обычно они происходят в GUI.
Пример
Это очень простой пример. Все юниты на карте 4 раза в секунду лечатся на 100. Однако кроме этого происходит вот ещё что.
код триггера
function Trig_HealUnits_Func001A takes nothing returns nothing
call SetUnitLifeBJ( GetEnumUnit(), ( GetUnitStateSwap(UNIT_STATE_LIFE, GetEnumUnit()) + 100.00 ) )
endfunction
function Trig_HealUnits_Actions takes nothing returns nothing
call ForGroupBJ( GetUnitsInRectAll(GetPlayableMapRect()), function Trig_HealUnits_Func001A )
endfunction
//===========================================================================
function InitTrig_HealUnits takes nothing returns nothing
set gg_trg_HealUnits = CreateTrigger( )
call TriggerRegisterTimerEventPeriodic( gg_trg_HealUnits, 0.25 )
call TriggerAddAction( gg_trg_HealUnits, function Trig_HealUnits_Actions )
endfunction
Функция GetUnitsInRectAll вызывает функцию GetUnitsInRectMatching, которая создаёт группу.
function GetUnitsInRectMatching takes rect r, boolexpr filter returns group
local group g = CreateGroup()
call GroupEnumUnitsInRect(g, r, filter)
call DestroyBoolExpr(filter)
return g
endfunction
И эта группа никак не удаляется. Это утечка.
Но подождите! Это ещё не всё. Обратимся к одной популярной статье про оптимизацию.
Но подождите! Это ещё не всё. Обратимся к одной популярной статье про оптимизацию.
Например, куда девается локальная переменная после того, как триггер кончил исполнение? На самом деле они продолжают сидеть в памяти. И хотя занимают они очень мало, но на протяжении длинной игры, их может накопиться порядочно. Чтобы этого избежать, имеет смысл обнулять локальные переменные после окончания действия триггера (по крайней мере, переменные объектного типа).
GetUnitsInRectMatching создаёт локальную переменную, но никак не обнуляет её. В статье говорится, что это создаёт утечку. Вот это я и решил проверить.
Тестовая карта
код
globals
constant trigger Start = CreateTrigger()
code Callback
endglobals
function LeakyVar takes nothing returns nothing
local location loc = Location(0., 0.)
call RemoveLocation(loc)
endfunction
function NotLeakyVar takes nothing returns nothing
local location loc = Location(0., 0.)
call RemoveLocation(loc)
set loc = null
endfunction
function CheckOtherTypes takes nothing returns nothing
local boolean b = true
local integer i = 5
local real r = 9.
local string s = "1234567890"
local code c = function NotLeakyVar
endfunction
function RemoveLoc takes location loc returns nothing
call RemoveLocation(loc)
endfunction
function ArgumentCheck takes nothing returns nothing
call RemoveLoc(Location(0., 0.))
endfunction
function NewLoc takes nothing returns location
local location loc = Location(0., 0.)
return loc
endfunction
function ReturnCheck takes nothing returns nothing
call RemoveLocation(NewLoc())
endfunction
function LeakyArray takes nothing returns nothing
local location array locs
set locs[0] = Location(0., 0.)
set locs[1] = Location(0., 0.)
set locs[2] = Location(0., 0.)
call RemoveLocation(locs[0])
call RemoveLocation(locs[1])
call RemoveLocation(locs[2])
endfunction
function NotLeakyArray takes nothing returns nothing
local location array locs
set locs[0] = Location(0., 0.)
set locs[1] = Location(0., 0.)
set locs[2] = Location(0., 0.)
call RemoveLocation(locs[0])
call RemoveLocation(locs[1])
call RemoveLocation(locs[2])
set locs[0] = null
set locs[1] = null
set locs[2] = null
endfunction
function NotLeakyTypes takes nothing returns nothing
local player p = Player(0)
local itemtype typ = ConvertItemType(0)
local blendmode blend = ConvertBlendMode(2)
endfunction
function Report takes nothing returns nothing
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0., 0., 8., "8 seconds passed")
endfunction
function StartActions takes nothing returns nothing
local integer i = 1
//set Callback = function LeakyVar
//set Callback = function NotLeakyVar
//set Callback = function CheckOtherTypes
//set Callback = function ArgumentCheck
//set Callback = function ReturnCheck
//set Callback = function LeakyArray
//set Callback = function NotLeakyArray
set Callback = function NotLeakyTypes
loop
call TimerStart(CreateTimer(), 1. / 32., true, Callback)
set i = i + 1
exitwhen i == 1024
endloop
call TimerStart(CreateTimer(), 8., true, function Report)
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0., 0., 5., "Init complete")
endfunction
//! inject main
call SetCameraBounds( -1280.0 + GetCameraMargin(CAMERA_MARGIN_LEFT), -1536.0 + GetCameraMargin(CAMERA_MARGIN_BOTTOM), 1280.0 - GetCameraMargin(CAMERA_MARGIN_RIGHT), 1024.0 - GetCameraMargin(CAMERA_MARGIN_TOP), -1280.0 + GetCameraMargin(CAMERA_MARGIN_LEFT), 1024.0 - GetCameraMargin(CAMERA_MARGIN_TOP), 1280.0 - GetCameraMargin(CAMERA_MARGIN_RIGHT), -1536.0 + GetCameraMargin(CAMERA_MARGIN_BOTTOM) )
//! dovjassinit
call DoNotSaveReplay()
call SuspendTimeOfDay(true)
call ClearMapMusic()
call StopMusic(false)
call TriggerRegisterTimerEvent(Start, 1., false)
call TriggerAddAction(Start, function StartActions)
//! endinject
//! inject config
call SetMapName( "TRIGSTR_003" )
call SetMapDescription( "" )
call SetPlayers( 1 )
call SetTeams( 1 )
call SetGamePlacement( MAP_PLACEMENT_USE_MAP_SETTINGS )
call DefineStartLocation( 0, 0.0, 0.0 )
// Player setup
call SetPlayerStartLocation( Player(0), 0 )
call SetPlayerColor( Player(0), ConvertPlayerColor(0) )
call SetPlayerRacePreference( Player(0), RACE_PREF_HUMAN )
call SetPlayerRaceSelectable( Player(0), true )
call SetPlayerController( Player(0), MAP_CONTROL_USER )
call SetPlayerTeam( Player(0), 0 )
//! endinject
Это весь код карты. Саму карту можно скачать, кликнув по кнопке Скачать.
Тест будет очень простым. Я создаю 1024 таймера, которые 32 раза в секунду вызывают одну и ту же функцию из переменной Callback. Запускаем карту и смотрим потребление памяти. Если растёт - утечки есть.
Утечка локальной переменной типа handle
В этом тесте в Callback будет функция LeakyVar.
function LeakyVar takes nothing returns nothing
local location loc = Location(0., 0.)
call RemoveLocation(loc)
endfunction
Одна локальная переменная весит 4 байта. Если же локальная переменная утекает, то прирост памяти в секунду будет 1024 (таймеры) * 32 (период) * 4 (переменная) байта = 131 072 байта = 128 килобайт. За 8 секунд прирост будет 1 мегабайт.
Прирост оказался больше, но ясно одно - утечки есть. По достижению 170 МБ игра стала подлагивать, а достигнув 180 МБ играть стало невозможно. За всё время утекло примерно 60 * 1024 * 32 = 1 966 080 локальных переменных.
Обнуление локальной переменной типа handle
Теперь я проверю, помогает ли обнуление локальных переменных типа handle устранить утечку.
В этот раз в Callback будет функция NotLeakyVar.
В этот раз в Callback будет функция NotLeakyVar.
function NotLeakyVar takes nothing returns nothing
local location loc = Location(0., 0.)
call RemoveLocation(loc)
set loc = null
endfunction
Обнуление переменной устраняет утечку.
Утечка локальной переменной других типов
В этот раз в Callback будет CheckOtherTypes.
function CheckOtherTypes takes nothing returns nothing
local boolean b = true
local integer i = 5
local real r = 9.
local string s = "1234567890"
local code c = function NotLeakyLoc
endfunction
Не обнулённые локальные переменные типов boolean, integer, real, string и code не вызывают утечку.
Утечка аргумента функции типа handle
В это раз в Callback будет ArgumentCheck.
function RemoveLoc takes location loc returns nothing
call RemoveLocation(loc)
endfunction
function ArgumentCheck takes nothing returns nothing
call RemoveLoc(Location(0., 0.))
endfunction
ArgumentCheck вызывает функцию RemoveLoc, которая принимает локацию как аргумент. Причём аргумент не обнуляется. Я хочу выяснить, будет ли это такой код утекать.
Не обнулённые аргументы функций не утекают.
Утечка локальной переменной, возвращающей значение
Здесь в Callback будет ReturnCheck.
function NewLoc takes nothing returns location
local location loc = Location(0., 0.)
return loc
endfunction
function ReturnCheck takes nothing returns nothing
call RemoveLocation(NewLoc())
endfunction
ReturnCheck удаляет локацию, которую создаёт NewLoc. NewLoc создаёт локацию, сохраняет её в локальную переменную и возвращает локацию из этой переменной. Локальная переменная при этом не обнуляется.
Результат аналогичен результату первого теста.
Утечка локального массива типа handle
Здесь в Callback будет LeakyArray.
function LeakyArray takes nothing returns nothing
local location array locs
set locs[0] = Location(0., 0.)
set locs[1] = Location(0., 0.)
set locs[2] = Location(0., 0.)
call RemoveLocation(locs[0])
call RemoveLocation(locs[1])
call RemoveLocation(locs[2])
endfunction
Тест аналогичен первому, но сейчас создаётся целый массив.
Результат ещё хуже, чем в первый раз.
Обнуление заполненных ячеек локального массива типа handle
Этот тест будет похож на предыдущий, но сейчас все непустые ячейки массива будут обнулены перед концом функции.
В Callback будет NotLeakyArray.
В Callback будет NotLeakyArray.
function NotLeakyArray takes nothing returns nothing
local location array locs
set locs[0] = Location(0., 0.)
set locs[1] = Location(0., 0.)
set locs[2] = Location(0., 0.)
call RemoveLocation(locs[0])
call RemoveLocation(locs[1])
call RemoveLocation(locs[2])
set locs[0] = null
set locs[1] = null
set locs[2] = null
endfunction
Обнуление заполненных ячеек устраняет утечку.
Утечка локальных переменных некоторых подтипов handle
В предыдущих тестах использовалась локация - это подтип handle. Локации можно создавать и удалять. Однако не все подтипы handle можно создавать и удалять. В этом тесте проверим, утекают ли локальные переменные таких типов.
В Callback будет NotLeakyTypes.
В Callback будет NotLeakyTypes.
function NotLeakyTypes takes nothing returns nothing
local player p = Player(0)
local itemtype typ = ConvertItemType(0)
local blendmode blend = ConvertBlendMode(2)
endfunction
Утечек нет.
Выводы
- Локальные переменные некоторых подтипов handle создают утечку, если не были обнулены перед окончанием функции. Если вместо переменной используется массив, то обнулять нужно каждую заполненную ячейку массива.
- Локальные переменные других примитивных типов: boolean, integer, real, string и code - не создают утечку, если не обнулить их.
- Аргументы функций типа handle не создают утечку, если не обнулить их.
Утекающие типы
type handle
type agent extends handle // all reference counted objects
type event extends agent // a reference to an event registration
type widget extends agent // an interactive game object with life
type unit extends widget // a single unit reference
type destructable extends widget
type item extends widget
type ability extends agent
type buff extends ability
type force extends agent
type group extends agent
type trigger extends agent
type triggercondition extends agent
type triggeraction extends handle
type timer extends agent
type location extends agent
type region extends agent
type rect extends agent
type boolexpr extends agent
type sound extends agent
type conditionfunc extends boolexpr
type filterfunc extends boolexpr
type unitpool extends handle
type itempool extends handle
type effect extends agent
type weathereffect extends handle
type terraindeformation extends handle
type fogmodifier extends agent
type dialog extends agent
type button extends agent
type quest extends agent
type questitem extends agent
type defeatcondition extends agent
type timerdialog extends agent
type leaderboard extends agent
type multiboard extends agent
type multiboarditem extends agent
type texttag extends handle
type lightning extends handle
// Reforged
type minimapicon extends handle
type framehandle extends handle
type commandbuttoneffect extends handle
Если переменная имеет тип из списка выше, её нужно обнулить, дабы не допустить утечку.
Список утекающих BJ функций
function PolledWait takes real duration returns nothing
function PlaySound takes string soundName returns nothing
function TriggerRegisterEnterRectSimple takes trigger trig, rect r returns event
function TriggerRegisterLeaveRectSimple takes trigger trig, rect r returns event
function GetInventoryIndexOfItemTypeBJ takes unit whichUnit, integer itemId returns integer
function DelayedSuspendDecayStopAnimEnum takes nothing returns nothing
function DelayedSuspendDecayBoneEnum takes nothing returns nothing
function DelayedSuspendDecayFleshEnum takes nothing returns nothing
function DelayedSuspendDecay takes nothing returns nothing
function IssueHauntOrderAtLocBJ takes unit whichPeon, location loc returns boolean
function WakePlayerUnits takes player whichPlayer returns nothing
function PauseAllUnitsBJ takes boolean pause returns nothing
function ReplaceUnitBJ takes unit whichUnit, integer newUnitId, integer unitStateMethod returns unit
function EnumDestructablesInCircleBJFilter takes nothing returns boolean
function EnumDestructablesInCircleBJ takes real radius, location loc, code actionFunc returns nothing
function NudgeUnitsInRectEnum takes nothing returns nothing
function NudgeItemsInRectEnum takes nothing returns nothing
function NudgeObjectsInRect takes rect nudgeArea returns nothing
function NearbyElevatorExistsEnum takes nothing returns nothing
function NearbyElevatorExists takes real x, real y returns boolean
function ChangeElevatorWallBlocker takes real x, real y, real facing, boolean open returns nothing
function EnumUnitsSelected takes player whichPlayer, boolexpr enumFilter, code enumAction returns nothing
function GetUnitsInRectMatching takes rect r, boolexpr filter returns group
function GetUnitsInRectOfPlayer takes rect r, player whichPlayer returns group
function GetUnitsInRangeOfLocMatching takes real radius, location whichLocation, boolexpr filter returns group
function GetUnitsOfTypeIdAll takes integer unitid returns group
function GetUnitsOfPlayerMatching takes player whichPlayer, boolexpr filter returns group
function GetUnitsOfPlayerAndTypeId takes player whichPlayer, integer unitid returns group
function GetUnitsSelectedAll takes player whichPlayer returns group
function GetForceOfPlayer takes player whichPlayer returns force
function GetPlayersByMapControl takes mapcontrol whichControl returns force
function GetPlayersAllies takes player whichPlayer returns force
function GetPlayersEnemies takes player whichPlayer returns force
function GetPlayersMatching takes boolexpr filter returns force
function GetRandomSubGroup takes integer count, group sourceGroup returns group
function LivingPlayerUnitsOfTypeIdFilter takes nothing returns boolean
function CountLivingPlayerUnitsOfTypeId takes integer unitId, player whichPlayer returns integer
function SetUnitFacingToFaceLocTimed takes unit whichUnit, location target, real duration returns nothing
function SetUnitFacingToFaceUnitTimed takes unit whichUnit, unit target, real duration returns nothing
function MakeUnitsPassiveForPlayer takes player whichPlayer returns nothing
function MeleeVictoryDialogBJ takes player whichPlayer, boolean leftGame returns nothing
function MeleeDefeatDialogBJ takes player whichPlayer, boolean leftGame returns nothing
function GameOverDialogBJ takes player whichPlayer, boolean leftGame returns nothing
function CustomVictoryDialogBJ takes player whichPlayer returns nothing
function CustomDefeatDialogBJ takes player whichPlayer, string message returns nothing
function MultiboardSetItemStyleBJ takes multiboard mb, integer col, integer row, boolean showValue, boolean showIcon returns nothing
function MultiboardSetItemValueBJ takes multiboard mb, integer col, integer row, string val returns nothing
function MultiboardSetItemColorBJ takes multiboard mb, integer col, integer row, real red, real green, real blue, real transparency returns nothing
function MultiboardSetItemWidthBJ takes multiboard mb, integer col, integer row, real width returns nothing
function MultiboardSetItemIconBJ takes multiboard mb, integer col, integer row, string iconFileName returns nothing
function TriggerActionUnitRescuedBJ takes nothing returns nothing
function BlightGoldMineForPlayerBJ takes unit goldMine, player whichPlayer returns unit
function SetPlayerColorBJ takes player whichPlayer, playercolor color, boolean changeExisting returns nothing
function MeleeGrantHeroItems takes nothing returns nothing
function MeleeClearExcessUnit takes nothing returns nothing
function MeleeClearNearbyUnits takes real x, real y, real range returns nothing
function MeleeEnumFindNearestMine takes nothing returns nothing
function MeleeFindNearestMine takes location src, real range returns unit
function MeleeRandomHeroLoc takes player p, integer id1, integer id2, integer id3, integer id4, location loc returns unit
function MeleeStartingUnitsHuman takes player whichPlayer, location startLoc, boolean doHeroes, boolean doCamera, boolean doPreload returns nothing
function MeleeStartingUnitsOrc takes player whichPlayer, location startLoc, boolean doHeroes, boolean doCamera, boolean doPreload returns nothing
function MeleeStartingUnitsUndead takes player whichPlayer, location startLoc, boolean doHeroes, boolean doCamera, boolean doPreload returns nothing
function MeleeStartingUnitsNightElf takes player whichPlayer, location startLoc, boolean doHeroes, boolean doCamera, boolean doPreload returns nothing
function MeleeStartingUnits takes nothing returns nothing
function MeleeCheckForVictors takes nothing returns force
function MeleeCheckForLosersAndVictors takes nothing returns nothing
function MeleeExposePlayer takes player whichPlayer, boolean expose returns nothing
function MeleeExposeAllPlayers takes nothing returns nothing
function MeleeCrippledPlayerTimeout takes nothing returns nothing
function MeleeCheckForCrippledPlayers takes nothing returns nothing
function MeleeInitVictoryDefeat takes nothing returns nothing
function UpdateEachStockBuilding takes itemtype iType, integer iLevel returns nothing
function UnitDropItem takes unit inUnit, integer inItemID returns item
// Reforged
function SmartCameraPanBJ takes player whichPlayer, location loc, real duration returns nothing
Во всех функциях выше есть локальные переменные утекающих типов, которые не были обнулены перед выходом из функции. Старайтесь не использовать их.
А что в Lua?
А в Lua таких проблем нет. Это проблема - утечка не обнулённых локальных переменных - свойственна только для JASS. А потому на патчах 1.31+ нужно использовать именно Lua.
`
ОЖИДАНИЕ РЕКЛАМЫ...
Чтобы оставить комментарий, пожалуйста, войдите на сайт.
Отредактирован quq_CCCP
Насчёт глобалок - память под них выделиться в момент первой записи, и дальше уже по боку что там null или ссылка, а стать проблемой это может лишь если ты допустил ошибку, сначала пытаясь читать данные из этой переменой, а потом записывать, ну а естественно в такие темповые глобалки ты сперва пишешь апотом читаешь, и никак иначе.
Про глобалки, триггеры, и прочие тонкости так же есть.
Отредактирован makkad
Достаточно было подшить единую базу, то снова потеряется еще 1 тест...
Отредактирован makkad
В Том и польза свежих статей в ленте. С актуализацией знаний, в том числе и по Lua.
Отредактирован Obelick
Начал с гуишных функций и глобалок, закончил этим. Все возможные проделанные мною варианты утекают, включая этот
Отредактирован Obelick
Затестил сейчас спецэффекты.
Сравнил гуишную стандартную функцию создать , а затем удалить и
call DestroyEffect(AddSpecialEffectLoc())
Разницы нет. В обоих случаях потребление памяти пляшет туда-сюда. То вырастет на 100, то упадет на 100 но на дальней дистанции осталось без изменения
Вот там кол-во попугаев хоть как то показывает разницу.