Раздел:
Триггеры и объекты

Что такое утечка памяти?

Утечка памяти - это ОПГ, которого боялись даже чеченцы не удалённый объект, который больше нигде не используется. Типичный случай утечки - не удалённая точка или группа юнитов. Обычно они происходят в 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.
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.
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.
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.
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
2
32
3 года назад
2
makkad, не ну она по форуму в 101 месте разжевана, пусть не с тестами и картинками, но есть.
Про глобалки, триггеры, и прочие тонкости так же есть.
2
22
3 года назад
Отредактирован makkad
2
quq_CCCP, Т.е. нужно искать эту разрозненную информацию на форумах. Отвечая на твой вопрос "к чему эти тесты", к тому, что это полезно хоть кому то именно в такой форме и именно сегодня.
0
32
3 года назад
0
makkad, они гуглятся, и в статьях вам пишут - обнуляйте переменные, не ну вы читали статьи, или прочую инфу, чтобы потом наоборот делать =)
Достаточно было подшить единую базу, то снова потеряется еще 1 тест...
0
22
3 года назад
Отредактирован makkad
0
quq_CCCP, Статьи чиьал. Переменные обнулял. Но у кого то когда то прочитал, что обнулять возвращаемые переменные и аргументы не нужно. А это не так (возвращаемые переменные нужно также обнулять, аргументы не нужно). Т..е. на моём примере очевидный косяк в этих всех старых базах инфы. Так как системы не было.

quq_CCCP, гуглится наверное всё. Но как гуглить то, о чём не подозреваешь. Или то, что ты думал знал как правильно, но оказывается что нет.
В Том и польза свежих статей в ленте. С актуализацией знаний, в том числе и по Lua.
0
23
3 года назад
Отредактирован Obelick
0
Привет. Интересно было почитать. А что можно сказать насчет юнитов? Решил провести свой эксперимент, но с юнитами.
Начал с гуишных функций и глобалок, закончил этим. Все возможные проделанные мною варианты утекают, включая этот
Загруженные файлы
4
13
3 года назад
4
Obelick, Возможно, что из-за количества "трупов" начинает лагать. Измени KillUnit на RemoveUnit и проверь

И set u = null можно в конце обнулять, в цыкле она перезаписывается
0
23
3 года назад
Отредактирован Obelick
0
Borodach, да, removeunit исправляет утечку. Хотя в свое время мне кто-то говорил что это действие "вредное" и ремувая из игры юнита ты не удаляешь его хендл.
Затестил сейчас спецэффекты.
Сравнил гуишную стандартную функцию создать , а затем удалить и
call DestroyEffect(AddSpecialEffectLoc())
Разницы нет. В обоих случаях потребление памяти пляшет туда-сюда. То вырастет на 100, то упадет на 100 но на дальней дистанции осталось без изменения
0
32
3 года назад
0
Obelick, не если ты уж сравниваешь гуи функции, скачай мемхак анрайза, там есть тест производительности функций, на 1000 итерациях.
Вот там кол-во попугаев хоть как то показывает разницу.
2
28
3 года назад
2
это действие "вредное" и ремувая из игры юнита ты не удаляешь его хендл
Ну это разумеется бред. И таких слухов и недосказанностей на старом форуме много, лучше заново всё проверить.
4
32
3 года назад
4
PT153, стоит отметить что ремув юнитов может стать причной багов, когда работают их абилки или урон наносится от их лица, допустим вылетел снаряд, а ты удалил юнита, удалять чисто визуальных дамми юнито конечно можно, а кастеров абилок нельзя...
0
15
3 года назад
0
Obelick:
Привет. Интересно было почитать. А что можно сказать насчет юнитов? Решил провести свой эксперимент, но с юнитами.
Начал с гуишных функций и глобалок, закончил этим. Все возможные проделанные мною варианты утекают, включая этот
Знаю, что некропостинг, но тут локация p не удаляется.
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.