Добавлен , опубликован
Раздел:
Основы

Вступление

Для чего эта статья?

За относительно долгое время, что я провёл работая с Jass, я как и другие пользователи получил уйму информации из уст в уста о тех или иных функциях, методах или же проблемах, которые присутствуют в языке Jass. Однако, практически большая часть из этой информации не имела ни подтверждения, ни малейшего примера, фактически всё было основано на "наблюдении" и якобы "тестах". Потому, когда я дополнил начальную наработку MemHack, и добавил возможность делать бенчмарки, а также разобрал структуры игры и заручился CheatEngine для разбора байткода Jass, мне стало интересно проверить эти мифы на деле.
Потому - эта статья будет служить как фактическим доказательством или опровержения того или иного мифа исходя из реальных данных, основанных на реальных тестах, которые можно будет воспроизвести и убедиться в их действительности. Конечно же эта статья уже опоздала в плане своей полезности, но это не отменяет того, что для всё ещё заинтересованных личностей она будет очень и очень полезной.

Но всё же.. зачем?

Смысл относительно прост, по большей части я это делаю, чтобы не повторять одни и те же ответы каждый раз, когда спрашивают по тем или иным "проблемам", "утечкам", "разницы в скорости" и прочему. Ну и иметь показательный материал тех или иных заявлений, дабы каждый кто будет в чём-либо сомневаться, мог смело глянуть эту статью, а если интересующий его вопрос не найден, то запросить его к рассмотрению.

Список Мифов

Локальные хендлы всегда утекают!

раскрыть
Ранее считалось, что любая локальная сложная переменная (всё, что является handle, то бишь юниты, способности и так далее) утекают и затрачивают 4 байта памяти вне зависимости обнулена она или нет. Однако - это оказалось лишь на половину правдой, ибо утечка ЕСТЬ, однако лишь в случае, если мы СОЗДАЁМ ОБЪЕКТ и присваиваем её в локальную переменную и в конце кода мы не обнуляем как раз эту переменную. То бишь мы можем передать референс этой локальной переменной в глобальную, что позволит нам обнулить локальную переменную и фактически убрать утечку связанную со "сложными" локальными переменными.
Пример:
Это ВЫЗОВЕТ утечку.
	function DoSomething takes nothing returns nothing
		local unit u = CreateUnit( Player( 0 ), 'hpea', 0., 0., 270. )
		....
	endfunction
Однако, если же вы вызовете RemoveUnit( u ), то это не будет являться утечкой, так как юнит был обработан и удалён.
Это НЕ ВЫЗОВЕТ утечку
	function DoSomething takes nothing returns nothing
		local unit u = CreateUnit( Player( 0 ), 'hpea', 0., 0., 270. )
		....
		set u = null
	endfunction
Это НЕ ВЫЗОВЕТ утечку
	globals
		unit uTemp = null
	endglobals
	
	function main takes nothing returns nothing
		set uTemp = CreateUnit( Player( 0 ), 'hpea', 0., 0., 270. )
	endfunction

	function DoSomething takes nothing returns nothing
		local unit u = uTemp
		....
	endfunction
Чтобы понять, почему же так, стоит взглянуть на байткод Jass, взглянем на вариант без и с утечкой.
Для начала, предоставлю скриншот того, как же я получаю JASM байткод, я написал функцию получения Джасс ноды из игры по её имени, то бишь это кусок из хештаблицы, в ней же хранится ссылка на байткод, что она выполняет по оффсету 0x18.
теперь рассмотрим один из вариантов:
Мы получаем байткод:
байткод
05070000
00000F80
0E290700
00000F7D
11290000
00000F80
0C000000
00000000
27000000
Что выдаёт нам:
Результат
0x05 = local | 0x07 = type где номер = тип, 0x07 = Handle | 0x00 = nothing | 0x00 = nothing
argument = variable id ( то бишь 0xF80 ) переменные в Jass индексируются и далее вызываются по индексу.
0x0E = getvar | 0x29 = register (номер регистра) | 0x07 = type | 0x00
argument = variable id ( то бишь 0xF7D )
0x11 = setvar | 0x29 = register | 0x00 | 0x00
argument = variable id ( то бишь 0xF80 )
0x0C = literal | 0x00 | 0x00 | 0x00 - ибо функция возвращает nothing, потому в R0 записывается 0.
0x27 = return
Если же Вам всё это ничего не говорит (большинству скорее всего это ничего и не скажет), это банально значит, что локальные переменные достаточно близки к аргументам функций по логике, а аргументы обнулять не нужно. Однако если же был создан новый объект, то без обнуления локальной переменной внутриигровой счётчик хендлов повысится на 1 единицу.
Простой ответ - сами переменные не утекают ни при каких обстоятельствах и они преобразуются в нечто статичное, и им выделена память единожды. Потому, утечки вызываются фактически банальной проблемой логики в самом коде, то бишь вы создали хендл, а затем его не удалили и так может повторяться уйму раз. Но это не утечка, ибо тогда по этой логике call CreateUnit и так далее - утечки, ибо этот юнит просто создан и дальше нам нужно его найти, чтобы убить или удалить и так далее.
Видео примеры
Пример с юнитом:
Пример с локацией:
Пример с группой:
Примеры с аргументами функций:
1)
2)
Подводя итоги, можно смело сказать, что никакого "ужаса" от использования локалок попросту нету, конечно же это не отменяет факта, что даже их стоит использовать с умом и их обнуление просто на просто необходимо, чтобы занятые регистры были очищены когда функция закончится и будет выполнена 0x27 (return) что повлечёт очистку стека. Да и фактически нет никакого оправдания тем, кто не обнуляет локальные "сложные" переменные, ибо их обнуление не добавляет нагрузки на JassVM и фактически уменьшает возможные утечки.
Хотелось бы внести поправку/дополнение (за что спасибо PTR153), в случае если вы создаёте объект и присваиваете его локалке, или же создаёте какой-либо объект внутри функции и присваиваете его локальной переменной, то её ОБЯЗАТЕЛЬНО нужно обнулить, в случае если созданный объект не был удалён и вы продолжаете его использовать, то всё что нужно - это присвоить его какой-нибудь глобальной переменной и вернуть её.
Краткий пример
function TestFunctionEx takes nothing returns nothing
    local location loc = Location( .0, .0 )
    set Loc = loc
    set loc = null
    call RemoveLocation( Loc )
endfunction
То бишь логика в том, что мы можем подать объект из локальной переменной в глобальную и далее уже без утечки провести с ней операцию.

Нативка X быстрее нативки Y!

раскрыть
Я думаю многие частенько встречались с обсуждениями скоростей нативок, и чаще всего под "удар" попадала GetUnitX против GetWidgetX, и общепринятно, что GetWidgetX быстрее чем GetUnitX. Однако, так ли это? Так давай же узнаем!
Для начала, я предложу Вам посмотреть псевдокод game.dll обоих функций:
Псевдокод GetUnitX и GetWidgetX
Как видите, он фактически идентичен, CUnit является расширением CWidget, однако обработка позиции делается всегда на уровне CWidget, а так как это расширенный класс, то разницы в скорости не может быть физически. Собственно потому и получается вот так:
Сравнение скорости GetUnitX и GetWidgetX
Обе функции заняли 5 мс на 10000 повторов, значит реальная задержка одного повтора 500 наносекунд +- 1-2 наносекунды погрешности. Конечно гигантский плюс GetWidgetX в её универсальности, ибо её можно применить ко всем видам виджетов без типизации, однако на этом её плюсы заканчиваются и в остальном она идентична GetUnitX.

Хештаблица медленная!

раскрыть
Это пожалуй один из самых ужасных мифов, которые просто на просто вводят меня в бешенство. Конечно же, этот миф "двоякий", ибо вопрос заключается в том, что в понятии "медленная" и в сравнении с чем? Зачастую те, кто используют vJass структуры, сравнивают хещтаблицу с этими псевдо-структурами, опираясь на то, что обращение к массиву - всегда быстрее чем обращение к хештаблице. Перед тем, как опровергнуть этот миф, я обязан согласиться с тем, что обращение к массиву конечно же быстрее, ибо JASM байткод вызывает 0x10 (getvar[]) и фактически берёт из уже "вшитого" списка функций по индексу, то бишь то, что делает хештаблица, только на более "низком" уровне.
Чтобы провести анализ скорости, давайте возьмём обычный массив чисел и число из хештаблицы и посмотрим эти варианты. Для простоты тестов, я не буду использовать StringHash для создания ключей, скорость с ними будет показан позже, и пример "ускорения" хештаблицы для особых фанатиков псевдоскорости.
Сравнение скорости сохранения значения
Сравнение скорости загрузки значения
Как мы видим getvar[] и setvar[] у массивов равен по скорости, точно так же как и Save равен по скорости Load у хештаблиц, потому далее будут сравниваться именно скорости загрузок (ибо мы чаще загружаем, чем сохраняем данные).
Смею предположить, что просмотрев примеры, будет вопрос "но тут же сравниваются нулевые ключи с последним индексом массива, так нечестно!", а что если я Вам скажу, что разница в значении числа создаёт минимальную погрешность и не более? Не верится? Так давай те же проверим!
"Честное" сравнение скорости загрузки значения
Как видите, скорость не изменилась, однако, если мы теперь рассмотрим варианты со StringHash, то разница будет более заметной. Рассмотрим в начале самый распространённый вариант применения (который в целом я использую у себя в Jass коде).
Сравнение скорости загрузки значение через StringHash
Ох как! Разница почти в 4.5 раз. Однако, не забывайте, что фактическая разница исчисляется на 1 вызов и не берёт во внимание таймер перебора всех значений в структуре, то бишь это сравнение строго одного массива и значения в хеше. Рассмотрим их фактические задержки, 3 мс / 10000 = 300 наносекунд и 13 мс / 10000 = 1300 наносекунд, то бишь борьба идёт за 1000 наносекунд, что является 0.001 мс или же 0.000001 секунды. Потому мне лично сложно представить, где эта разница даст о себе знать и как часто делается 10000 повторов или же вызовов чего-либо.
А теперь рассмотрим вариант "ускорения" хештаблицы, я думаю Вы прекрасно догадываетесь как же это можно сделать, но всё же рассмотрим это!
Ускорение хештаблицы
Как видите, разница в скорости упала вновь до +- прежнего значения с погрешность, что в итоге делает разницу вновь лишь в 2 раза, однако даже так - эта разница слишком минимальна, чтобы это вызывало каке-либо проблемы. Для сравнения, средняя функция мемхака порой в 20 раз медленнее нативок, но это не делает эти функции абсолютно непригодными для использования, ведь так?

Хештаблица занимает слишком много памяти игры!

раскрыть
Спасибо JackFastGame за напоминание об этом мифе. Для того, чтобы была возможность сравнивать или же оценивать занимает ли тот или иной объект много места, нужно найти его структуру и посмотреть, как игра работает с ними. Потому в начале стоит рассмотреть их фактические изначальные размеры, которые выделяет игра.
Размер менеджера хештаблиц
Размер хештаблицы
Для прояснения, игра создаёт менеджер Хештаблиц единожды, каждая хештаблица (всего их может быть 256) фактически - это дополнение внутриигровой хештаблицы (ибо не стоит забывать, что абсолютно все объекты в игре загружаются игрой из хештаблицы. Это в целом Вы можете увидеть достаточно подробно в МемХаке, где достаточно часто считываются объекты через их хешключи.
Пример такого в игре:
Хештаблицы игры
Но что же тогда делает хештаблица, которую мы можем создать в Jass? Она является ничем иным, как "дочерней" таблицей главной таблицы, которой выделено 0-255 ключей, то бишь 256 индексов, что можно увидеть тут:
Максимальной количество Хештаблиц
Собственно, если хештаблица не имеет никаких вписанных в неё значений, то её "изначальный" вес будет равен 0x28 байтам, далее каждый новый "хешключ" (который состоит из родительского и дочернего ключа) будет добавлять по 4 байта (то бишь никакого отличия от массивов). Исключение - это "стринг", там каждая буква = один байт, что опять же идентично обычным переменным.
Подводя итоги, можно смело сказать, что хештаблица не представляет собой какую-то громоздкую систему и её "засорение" всегда будет на руках её пользователя, однако хештаблица нам позволяет динамически очищать память, путём RemoveSavedHandle и прочего или же полной очистки дочерних ключей или же всей хештаблицы, что освободит всю занятую ей память.

Deg2Rad и Rad2Deg медленные!

раскрыть
Это как вы скорее всего и догадались очередной миф, который базируется вокруг "идеи", по которой прямое умножение джасс переменных должно быть быстрее нативок, однако это не так. Давайте начнём с примеров:
Пример Deg2Rad
Давайте теперь рассмотрим Rad2Deg:
Пример Rad2Deg
Результат одинаковый в обоих случаях, но почему же? Для этого нам нужно посмотреть в обработчик JASM байткода, который ссылается на опкод 0x22 (multiplication):
Обработкич Байткода
А теперь посмотрим на Deg2Rad:
Deg2Rad Code
В итоге получается, что умножение фактически превосходит вызов нативки по общим операциям, но всё это сводится к погрешности, которая банально делает эти операции равными. Такая же учесть следует и за Pow( dx, 2 ) в сравнении с dx * dx, они тоже равны по скорости.

Юниты жрут больше фпса чем Эффекты!

раскрыть
С выходом Warcraft 3 Reforged и с появлением API для эффектов, начал зарождаться вопрос, а что же в итоге эффективнее и меньше нагружает игру? Отвечу кратко, эффекты конечно же нагружают меньше, ввиду того, что эффект - это расширение CSpriteUber, а CUnit расширяет CWidget, который хранит не только CSpriteUber, а так же круги выделения, полосу здоровья, и прочую информацию. Говоря проще разница структур фактически колоссальная.
Однако, если вы думаете, что итог очевиден, то вы ошибаетесь. На деле игре фактически до шляпы, ибо без наведения мышки на юнита/выделения и если полосы здоровья скрыты, то нужная мощь GPU используется одинаково, как доказательство, как обычно предъявляю скриншоты.
Сравнение
Юниты:
Эффекты:
Подводя итоги, можно смело сказать, что фактически гигантской или же ощутимой разницы просто нет и мне не понятно откуда вообще взялся миф об этом. Но надеюсь это более чем опровергает этот миф.

Операция not true медленнее чем true == false (или схожие аналоги)

раскрыть
Данный миф существует с давних-давних времён, а если ещё точнее образовался с hiveworkshop, когда некто пытался замерять задержку исполнения функций (сама библиотека для измерений была в целом неточная) и множество тестов показали, что булевые операции с not оказывались медленнее чем == true или == false.
На деле это конечно же неправда и как минимум идёт в разрез с обще поставленной логикой, а если точнее - чем меньше байткода, тем быстрее он выполнится. Посмотрим же на приведённый пример.
Пример
На скриншоте мы видим две булевые операции not true и true == false, рассмотрим байткод:
Байткод (not true):
0C510800 - literal
00000001 - value
25610000 - operator not
00000000
2A510000 - jump if
00001483 - register
16000000 - call jass
00000A93 - jass function
2B000000 - jmp
00001484 - register
28000000 - label
00001483 - register
28000000 - label
00001483 - register
0C000000 - literal
00000000
27000000 - retn
Байткод (true == false):
0C510800 - literal
00000001 - va
13510000 - push 0x51
00000000
14530000 - pop 0x53
00000000
1A535352 - eq 0x53 0x53 0x52
00000000
2A530000 - jmpf
00001483 - where to
16000000 - call jass
00000A93 - func id
2B000000 - jmp
00001484 - where to
28000000 - label
00001483 - id
28000000 - label
00001484 - id
0C000000 - literal
00000000 - retval
27000000 - ret
Как вы можете увидеть, операция через not меньше не только по байткоду, но и по выделенным "данным", ибо в варианте true == false мы выделяем 2 литерала, один под true, другой под false и третий под результат, а для получения результата используется дополнительная команда eq (equals - равно).
Собственно выходит, что чем короче булевые операции, чем меньше в целом действий, тем она будет быстрее (что как обычно - логично).

Trigger​Add​Action утекает!

раскрыть
Очередной древний миф, который опять же пробрался к нам с далёких просторов hiveworkshop и был так же "перепроверен" на xgm. Однако, хоть этот миф и правдив, но правда немного в другом и виной тому, как обычно "божественные пальчики" программистов Blizzard Entertainment.
Начну с краткого, нет, сама TriggerAddAction не при чём, виной всему TriggerClearActions и DestroyTrigger, которые просто на просто не имеют кода деструктора самого Action, а вот TriggerRemoveAction его имеет! А это значит, если вы сохраните triggeraction в переменную и удалите его через TriggerRemoveAction, то никакой "утечки" не будет.
Перейдём к самим тестам!
Проверка "утечек"
Для начала проверим количество созданных хендлов после: CreateTrigger, TriggerAddAction и TriggerAddCondition.
Как видите, всего добавилось 30000 хендлов, ибо каждый цикл +3 хендла и всего 10000 циклов.
Посмотрим результат после DestroyTrigger:
Как вы можете заметить, удалилось лишь 20000 хендлов, вместо 30000, но почему же? Хоть я и дал ответ ранее, стоит рассмотреть эту проблему подробнее.
Давайте же посмотрим строго на TriggerAddAction и что же будет после DestroyTrigger:
Результат после DestroyTrigger
Как вы видите, хендлов всё так же 10000, а это значит, что TriggerClearActions не работает. Но почему же? - спросите вы, чтобы ответить на этот вопрос нужно для начала посмотреть на TriggerClearConditions, почему же он работает?
TriggerClearConditions
Большинству из вас не будет понятно куда нужно смотреть и что это вообще такое, я упрощу вам жизнь, просто запомните вот это:
(*(void (__stdcall **)(int, int))(**(uint32_t **)((signed int)result <= 0 ? 8 : result + 2) + 0x5C))( ); // CAgentWar3::Destroy
TriggerClearActions
Ничего не заметили? Если заметили, то можете похлопать себя по плечу, я горжусь вами! А если нет, то я вас понимаю, ибо разница очень и очень МАЛЕНЬКАЯ и заключается как раз в
(*(void (__stdcall **)(int, int))(**(uint32_t **)((signed int)result <= 0 ? 8 : result + 2) + 0x5C))( ); // CAgentWar3::Destroy
которой тут просто... нет... а это значит, что хендл triggeraction просто на просто не удаляется и остаётся в памяти игры.
Итог: утекает не TriggerAddAction, а отсутствие удаление деструктора triggeraction в DestroyTrigger и TriggerClearActions.

Kill​Sound​When​Done очищает sound хендл!

раскрыть
Не секрет, что в огромном количестве карт используются звуки, однако правильно ли они используются, почти во всех случаях - нет. Однако, виноваты в этом далеко не картоделы, а в очередной раз сами Blizzard.
Каламбур KillSoundWhenDone связан с тем, что да, оно удаляет выполняемый звук, однако оно не удаляет сам хендл, то бишь CSoundWar3 и так как нет нативки на прямую установку/замену звука, то остаётся висячий хендл.
Посмотрим на счётчик хендлов без и с KillSoundWhenDone:
KillSoundWhenDone Тест
Как видите на уровне хендлов разницы нет вообще, однако KillSoundWhenDone убивает сам звук, что всё-таки освободит память занятую непосредственно звуком.
А теперь посмотрим мою функцию RemoveSound, которая будет доступна в UjAPI начиная с 1.0.24.92 версии.
RemoveSound
По итогу, получается что используя стандартный метод вызова звука, а точнее:
function PlaySound takes string soundName returns nothing
    local sound soundHandle = CreateSound(soundName, false, false, true, 12700, 12700, "")
    call StartSound(soundHandle)
    call KillSoundWhenDone(soundHandle)
endfunction
Или же метод без утечки индекса:
function PlaySoundEx takes string soundName returns nothing
    local sound soundHandle = CreateSound(soundName, false, false, true, 12700, 12700, "")
    call StartSound(soundHandle)
    call KillSoundWhenDone(soundHandle)
	set soundHandle = null
endfunction
Мы всё-равно не решаем проблему утечки CSoundWar3, а это значит, что единственное верное решение (по-крайней мере на ванилле/рефе) - это каждый раз создавать уникальный звук для героя и т.д. и не удалять его через KillSoundWhenDone, а останавливать через StopSound( s, false, true ) и использовать его вновь, а не создавать новый, надясь на то, что он будет таки очищен.

Информация:

Что нужно для проверки скорости функций и прочего?

Всё достаточно просто, просто скачайте мой MemHackAPI отсюда: xgm.guru/p/wc3/memoryhackapi
В папке UselessTesting будет триггер Testing в нём есть функция TestBenchmarking, в первый и второй цикл вставьте функции, что вы хотите сравнить. Когда оба цикла завершается, будет высвечено сколько каждый из циклов занял в мс (миллисекунды). Оба цикла делаются 10000 раз, чтобы получить большее число без умножений и неточностей. Собственно, чтобы получить одиночную задержку, надо разделить полученное число на 10000 или установленное Вами значение.

Заключение:

За свою долгую историю, Warcraft 3 пожалуй создал пожалуй самое мощное начало для мододелов, что повлекло за собой уйму отдельных игр, которые ранее были картами в этой игре. Зачастую, многие создатели (включая меня) сильно недооценивали Jass в целом и всячески пытались найти тот или иной повод упрекнуть его в той или иной проблеме, не проведя нужных проверок, чтобы выявить реальную причину.
К моему большому сожалению, по сей день большинство картоделов опираются на те или иные мифы, которые услышали на Hive, а некоторые услышали что-то на xgm и других сайтах, и вроде как не от каких-либо случайных людей, а по факту проверенных, однако даже они могут ошибаться.
Потому, я очень надеюсь, что даже бегло прочитав мою статью, она развеет сложившиеся ложные впечатления о Jass и эти вопросы навсегда отпадут.
`
ОЖИДАНИЕ РЕКЛАМЫ...
5
20
2 года назад
5
Резервирую первый комментарий для дополнинй.
Также, хочу сообщить, что данная статья ещё не доделана, просто хотелось загрузить хотя бы один самый главный миф как можно скорее. Со временем я добавлю ещё мифов и предоставлю данные для их опровержения, если у кого-то есть варианты, которые вы бы хотели предложить, то предлагайте, постараюсь их проверить в кратчайшие сроки!
2
27
2 года назад
2
Unryze, не раз замечал как люди пытаются обойти функцию IsTerrainPathable путём проверки проходимости предметом (SetItemPosition) или юнитом (SetUnitPosition), по привычке сам начал использовать проверку предметом, можно об этом тоже пару слов добавить
2
20
2 года назад
Отредактирован Unryze
2
Unryze, не раз замечал как люди пытаются обойти функцию IsTerrainPathable путём проверки проходимости предметом (SetItemPosition) или юнитом (SetUnitPosition), по привычке сам начал использовать проверку предметом, можно об этом тоже пару слов добавить
Проверю, почему нет, конечно по скорости IsTerrainPathable (по логике должен всегда быть быстрее), конечно мне сложно понять смысл обхода этой функции (разве что когда-то был косяк в ней, что она возвращала инверсию, то бишь если проходимо, то вернёт false).
В любом случае, спасибо за предложение! :)
Провёл тест, разница в 13мс на 10000 повторов:
rsfghd если тебе не сложно, можешь пояснить в чём была суть отказа от IsTerrainPathable, чтобы я примерно сообразил как это расписать. Ибо по скорости, оно всегда медленнее, по-мимо старого косяка инверсии флага, ничего вспомнить не могу. :(
Загруженные файлы
2
27
2 года назад
Отредактирован rsfghd
2
Unryze, смешно получается, суть как раз была что IsTerrainPathable медленнее чем сместить тот же предмет, спрятать его, а потом сравнивать координаты предмета и нужной точки
даже откопал старый вопрос с комментариями от сильных людей: xgm.guru/p/wc3/168497

а, стоп, IsTerrainPathable имеет ещё один косяк если не ошибаюсь, оно не проверяет по нормальному проходимость путей от декораций

раскрыть
    local item it = CreateItem( 'spsh', 500.00, 500.00 )
    local real x
    local real y
    
    call CreateDestructable( 'ATtr', 0.00, 0.00, 270.00, 1.00, 0 ) 
    
    call SetItemVisible( it, false )
    
    if not IsTerrainPathable( 0.00, 0.00, PATHING_TYPE_WALKABILITY ) then
        call BJDebugMsg( "IsTerrainPathable: проходимо" )
    else
        call BJDebugMsg( "IsTerrainPathable: непроходимо" )
    endif
    
    call SetItemPosition( it, 0.00, 0.00 )
    call SetItemVisible( it, false )
    set x = GetItemX( it )
    set y = GetItemY( it )
    
    if ( x + 1.00 < 0.00 or x - 1.00 > 0.00 ) or ( y + 1.00 < 0.00 or y - 1.00 > 0.00 ) then
        call BJDebugMsg( "Предмет: непроходимо" )
    else
        call BJDebugMsg( "Предмет: проходимо" )
    endif
Загруженные файлы
4
15
2 года назад
4
Unryze, про хэш-таблицу ещё был миф, что это огромный объект, который лучше больше одного раза не создавать, даже в статье на этом сайте было написано об этом.
2
20
2 года назад
2
Unryze, смешно получается, суть как раз была что IsTerrainPathable медленнее чем сместить тот же предмет, спрятать его, а потом сравнивать координаты предмета и нужной точки
даже откопал старый вопрос с комментариями от сильных людей: xgm.guru/p/wc3/168497

а, стоп, IsTerrainPathable имеет ещё один косяк если не ошибаюсь, оно не проверяет по нормальному проходимость путей от декораций

раскрыть
    local item it = CreateItem( 'spsh', 500.00, 500.00 )
    local real x
    local real y
    
    call CreateDestructable( 'ATtr', 0.00, 0.00, 270.00, 1.00, 0 ) 
    
    call SetItemVisible( it, false )
    
    if not IsTerrainPathable( 0.00, 0.00, PATHING_TYPE_WALKABILITY ) then
        call BJDebugMsg( "IsTerrainPathable: проходимо" )
    else
        call BJDebugMsg( "IsTerrainPathable: непроходимо" )
    endif
    
    call SetItemPosition( it, 0.00, 0.00 )
    call SetItemVisible( it, false )
    set x = GetItemX( it )
    set y = GetItemY( it )
    
    if ( x + 1.00 < 0.00 or x - 1.00 > 0.00 ) or ( y + 1.00 < 0.00 or y - 1.00 > 0.00 ) then
        call BJDebugMsg( "Предмет: непроходимо" )
    else
        call BJDebugMsg( "Предмет: проходимо" )
    endif
Да, вот этот косяк я забыл, хотя по факту это и не косяк, ибо проверяется именно проходимость земли. Ну, тогда тут не миф, а даже есть причина использовать SetPosition, всяко неплохо! JackFastGame:
Unryze, про хэш-таблицу ещё был миф, что это огромный объект, который лучше больше одного раза не создавать, даже в статье на этом сайте было написано об этом.
Когда буду у компьютера добавлю это в статью, спасибо!
3
29
2 года назад
3
Было интересно и очень познавательно. Жду ещё разоблачений других мифов или их подтверждения.
0
27
2 года назад
Отредактирован rsfghd
0
Unryze, ещё кое-что интересно, работа с темповой группой
что быстрее: делать всё в цикле без булекспра или делать всё в булекспре? бенчмарк вовсе минусовый результат показывает, но вероятно тут проблемы уже моего компа
раскрыть
globals
    private constant group TempG = CreateGroup( )
    private unit bj_lastFilterUnit = null
endglobals

native UnitAlive takes unit id returns boolean

private function cond takes nothing returns boolean
    set bj_lastFilterUnit = GetFilterUnit( )
    if UnitAlive( bj_lastFilterUnit ) then
        call SetWidgetLife( bj_lastFilterUnit, GetWidgetLife( bj_lastFilterUnit ) )
    endif
    return false
endfunction

function TestBenchmarking takes nothing returns nothing
    local integer p     = 0
    local integer i     = 0
    local integer time  = 0
    local unit u
    local boolexpr b = Condition( function cond )
    
    set i    = 0
    set time = GetLocalTime( 0 )
    loop
        exitwhen i == 10000
        call GroupEnumUnitsInRect( TempG, bj_mapInitialPlayableArea, null )
        
        loop
            set u = FirstOfGroup( TempG )
            exitwhen u == null
            call GroupRemoveUnit( TempG, u )
            if UnitAlive( u ) then
                call SetWidgetLife( u, GetWidgetLife( u ) )
            endif
        endloop
        
        // Some stuff here
        set i = i + 1
    endloop
    set time  = GetLocalTime( 0 ) - time
    call DisplayTextToPlayer( GetLocalPlayer( ), 0, 0, "First Delay: " + I2S( time ) + "ms" )
    
    set i    = 0
    set time = GetLocalTime( 0 )
    loop
        exitwhen i == 10000
        call GroupEnumUnitsInRect( TempG, bj_mapInitialPlayableArea, b )
        // Some other stuff here
        set i = i + 1
    endloop
    set time  = GetLocalTime( 0 ) - time
    call DisplayTextToPlayer( GetLocalPlayer( ), 0, 0, "Second Delay: " + I2S( time ) + "ms" )
endfunction

Загруженные файлы
2
28
2 года назад
Отредактирован PT153
2
мы СОЗДАЁМ переменную и присваиваем её в локальную переменную
Создаём объект

Простой ответ - сами переменные не утекают ни при каких обстоятельствах и они преобразуются в нечто статичное, и им выделена память единожды. Потому, утечки вызываются фактически банальной проблемой логики в самом коде, то бишь вы создали хендл, а затем его не удалили и так может повторяться уйму раз.
Ладно, тогда почему тут всё удаляется, а потребление ОЗУ растёт? В твоих примерах с группой и локацией переменная обнуляется.

	globals
		location Loc
	endglobals

	function DoSomething takes nothing returns nothing
		local location l
		set Loc = Location(0., 0.)
		set l = Loc
	endfunction
Вот это утекает?
0
27
2 года назад
0
PT153, ну ссылка локальной же не обнуляется, утекает значит
2
20
2 года назад
Отредактирован Unryze
2
Unryze, ещё кое-что интересно, работа с темповой группой
что быстрее: делать всё в цикле без булекспра или делать всё в булекспре? бенчмарк вовсе минусовый результат показывает, но вероятно тут проблемы уже моего компа
раскрыть
globals
    private constant group TempG = CreateGroup( )
    private unit bj_lastFilterUnit = null
endglobals

native UnitAlive takes unit id returns boolean

private function cond takes nothing returns boolean
    set bj_lastFilterUnit = GetFilterUnit( )
    if UnitAlive( bj_lastFilterUnit ) then
        call SetWidgetLife( bj_lastFilterUnit, GetWidgetLife( bj_lastFilterUnit ) )
    endif
    return false
endfunction

function TestBenchmarking takes nothing returns nothing
    local integer p     = 0
    local integer i     = 0
    local integer time  = 0
    local unit u
    local boolexpr b = Condition( function cond )
    
    set i    = 0
    set time = GetLocalTime( 0 )
    loop
        exitwhen i == 10000
        call GroupEnumUnitsInRect( TempG, bj_mapInitialPlayableArea, null )
        
        loop
            set u = FirstOfGroup( TempG )
            exitwhen u == null
            call GroupRemoveUnit( TempG, u )
            if UnitAlive( u ) then
                call SetWidgetLife( u, GetWidgetLife( u ) )
            endif
        endloop
        
        // Some stuff here
        set i = i + 1
    endloop
    set time  = GetLocalTime( 0 ) - time
    call DisplayTextToPlayer( GetLocalPlayer( ), 0, 0, "First Delay: " + I2S( time ) + "ms" )
    
    set i    = 0
    set time = GetLocalTime( 0 )
    loop
        exitwhen i == 10000
        call GroupEnumUnitsInRect( TempG, bj_mapInitialPlayableArea, b )
        // Some other stuff here
        set i = i + 1
    endloop
    set time  = GetLocalTime( 0 ) - time
    call DisplayTextToPlayer( GetLocalPlayer( ), 0, 0, "Second Delay: " + I2S( time ) + "ms" )
endfunction

Первый из группы будет всегда быстрее, фильтр фактически равен обычному форгруппу (просто убирает нужду в дополнительном переборе, ибо можно сделать всё в фильтре). По поводу -мс скорости - такое бывает, когда таки слишком много операций и поток насильно обрывается, обычно вызвано слишком долгим тайм-аутом.
> мы СОЗДАЁМ переменную и присваиваем её в локальную переменную
Создаём объект

Простой ответ - сами переменные не утекают ни при каких обстоятельствах и они преобразуются в нечто статичное, и им выделена память единожды. Потому, утечки вызываются фактически банальной проблемой логики в самом коде, то бишь вы создали хендл, а затем его не удалили и так может повторяться уйму раз.
Ладно, тогда почему тут всё удаляется, а потребление ОЗУ растёт? В твоих примерах с группой и локацией переменная обнуляется.

	globals
		location Loc
	endglobals

	function DoSomething takes nothing returns nothing
		local location l
		set Loc = Location(0., 0.)
		set l = Loc
	endfunction
Вот это утекает?
Немного неправильно выразился, исправлю, спасибо. Я имел ввиду, что мы в начале создаём переменную и затем даём ей референс объекта.
"Ладно, тогда почему тут всё удаляется, а потребление ОЗУ растёт? В твоих примерах с группой и локацией переменная обнуляется."
Смотри видео внимательно, оно потом падает, ибо проходят и другие действия. Оно не растёт без конца. Оно ещё и падает
Предоставленный код тобою - утекает лишь в случае если его повторять и не удалять используемый объект.
PT153, ну ссылка локальной же не обнуляется, утекает значит
Дело там не в обнулении, там оно ничего не даст. Я примером пытался пояснить, что у текает не сама "локальная", а объект, который присваивается. То бишь пример с CreateUnit по факту.
4
8
2 года назад
Отредактирован goodlyhero
4
Я решил проверить, насколько серьезны будут утечки, если не обнулять локалки, используя множественный запуск нижеописанной функции.
В результате небольшого наблюдения можно увидеть, что при этом, занимаемое место в таблице хэндлов растет, и она сама раздувается, за счет периодических перевыделений памяти, что, при больших количествах таких утечек вызовет лаги при, собственно, перевыделении, и, при достаточном терпении, окончание доступной памяти. (Однажды сам столкнулся с проблемой вылета на такой карте из-за стандартной и проблемной стандартной функции CountLivingPlayerUnitsOfTypeId, после более чем трех часов игры, после того, как в нее были внесены исправления в области обнуления локалок, криты прикратились)
Конечно, это не быстро, но не менее 3х байт за не до конца обнуленные ссылки на хэндл, что можно пронаблюдать на видео, а, при желании, и повторить самому.
Карту прилагаю.
2
20
2 года назад
Отредактирован Unryze
2
Я решил проверить, насколько серьезны будут утечки, если не обнулять локалки, используя множественный запуск нижеописанной функции.
В результате небольшого наблюдения можно увидеть, что при этом, занимаемое место в таблице хэндлов растет, и она сама раздувается, за счет периодических перевыделений памяти, что, при больших количествах таких утечек вызовет лаги при, собственно, перевыделении, и, при достаточном терпении, окончание доступной памяти. (Однажды сам столкнулся с проблемой вылета на такой карте из-за стандартной и проблемной стандартной функции CountLivingPlayerUnitsOfTypeId, после более чем трех часов игры, после того, как в нее были внесены исправления в области обнуления локалок, криты прикратились)
Конечно, это не быстро, но не менее 3х байт за не до конца обнуленные ссылки на хэндл, что можно пронаблюдать на видео, а, при желании, и повторить самому.
Карту прилагаю.
Растёт последний индекс хендла (он будет расти в любом случае, даже если ты дождёшься удаления юнита - и это правильно). Конечно я не сталкивался ещё с моментами когда достигался лимит хендлов, но судя по коду игры, если хендл "пустой", то он выдаётся уже новому юниту (если нет нового доступного), то бишь есть "переработка" хендлов.
Ниже прикрепил мой пример, как видишь, на чистой карте тупо с оборотом и таймеров, ничего не растёт.
Если не сложно, скинь потом оффсеты по которым ты вышел на число хендлов, думаю будет полезно глянуть.
Ладно, я пожалуй спать, завтра и статью обновлю (внесу правки), заодно и про Хештаблицу дополню пункт по поводу её размера.
Редактирование:
Гляну ещё и предоставленное видео по локации, ещё раз проведу у себя тесты, ибо я тоже помню, что в какой-то момент, где не было обнуления начала уходить память. Конечно посыл не был тем, чтобы все забили на обнуление, стоит наверное перефразировать момент с советом на обнуление и выразить его более строго. Ибо фактическое наличие обнуления - не потребляет уймы операций, так ещё и служит элементом безопасности.
Редактирование 2:
Решил сделать как в примере 1024 таймера, результат у меня почему-то тот же:
Либо у меня какой-то магический компьютер, то ли моя WFE какими-то выкрутасами эту проблему решает (что слишком сомнительно, ибо ничего подобного в моей программе нет).
Вариант без ВФЕ, результат тот же:
Карта:
4
8
2 года назад
Отредактирован goodlyhero
4
pGameWar3 = *(pGameDLL+0xab65f4)
pGameState = *(pGameWar3+0x1c)
pGameState+0x194 - сколько мест в таблице выделено
pGameState+0x198 - сколько мест в таблице занято
pGameState+0x19с - Массив структур, хранящих количество ссылок на хэндл, указатель на объект, и, возможно, еще что-то.
pGameState+0x1A0 - Шаг повышения размера таблицы.
Я предполагаю, что при наличии ссылок, хэндл не будет перевыдан другоу объекту, что, я надеюсь, логично, но, вероятно, сам, оригинальный объект, которому соответствует хэндл, может быть удален.
Строго говоря, утечка в 3?! байта - не критично, но за несколько часов, при достаточном усердии, можно набрать достаточно. Но в коротких играх, вероятно, источником проблем такая утечка не будет.
А безопасности, в рамках моего понимания, необнуление не даст, так как по сути, если на сущность нет ссылок на хэндл, а сама сущность удалена - нет никаких способов с ей провзаимодействовать или пострадать от её отсутствия.
2
20
2 года назад
Отредактирован Unryze
2
А, я гениален, не стоило в 4 утра делать тесты, я мало того что закомментировал строчку вызова теста, так я ещё и неправильно функцию обозвал. Переделаю все тесты ещё раз, чтобы наверняка.
pGameWar3 = *(pGameDLL+0xab65f4)
pGameState = *(pGameWar3+0x1c)
pGameState+0x194 - сколько мест в таблице выделено
pGameState+0x198 - сколько мест в таблице занято
pGameState+0x19с - Массив структур, хранящих количество ссылок на хэндл, указатель на объект, и, возможно, еще что-то.
pGameState+0x1A0 - Шаг повышения размера таблицы.
Я предполагаю, что при наличии ссылок, хэндл не будет перевыдан другоу объекту, что, я надеюсь, логично, но, вероятно, сам, оригинальный объект, которому соответствует хэндл, может быть удален.
Строго говоря, утечка в 3?! байта - не критично, но за несколько часов, при достаточном усердии, можно набрать достаточно. Но в коротких играх, вероятно, источником проблем такая утечка не будет.
А безопасности, в рамках моего понимания, необнуление не даст, так как по сути, если на сущность нет ссылок на хэндл, а сама сущность удалена - нет никаких способов с ей провзаимодействовать или пострадать от её отсутствия.
Спасибо, я то смотрел на эти оффсеты и забыл о них совсем. Обнуление вызывает поп стека, что скорее всего и отвечает за очищение потенциальной утечки. В любом случае все тесты надо будет переделать, ну и поправить пояснение по ним.
По поводу хендла, я думаю без синтетики банально не дойти до того, когда старый хендл (то бишь его Id будет перевыдан новому юниту), ибо как раз этого и боялись многие, которые полагались на GetHandleId для индексаций, хеширования и т.д. Но суть в том, что в логике игры оно есть, чтобы предотвратить так называемое превышение лимита памяти.
Всем спасибо, в ближайшее время поправлю,
Редактирование:
Все мои тесты подтвердились, всё-таки оригинал карты на которой я проводил тесты не в 4 утра - показала те же результаты, хоть что-то хорошее. То бишь ответ:
Предыдущий ответ в 4 утра
Я решил проверить, насколько серьезны будут утечки, если не обнулять локалки, используя множественный запуск нижеописанной функции.
В результате небольшого наблюдения можно увидеть, что при этом, занимаемое место в таблице хэндлов растет, и она сама раздувается, за счет периодических перевыделений памяти, что, при больших количествах таких утечек вызовет лаги при, собственно, перевыделении, и, при достаточном терпении, окончание доступной памяти. (Однажды сам столкнулся с проблемой вылета на такой карте из-за стандартной и проблемной стандартной функции CountLivingPlayerUnitsOfTypeId, после более чем трех часов игры, после того, как в нее были внесены исправления в области обнуления локалок, криты прикратились)
Конечно, это не быстро, но не менее 3х байт за не до конца обнуленные ссылки на хэндл, что можно пронаблюдать на видео, а, при желании, и повторить самому.
Карту прилагаю.
Растёт последний индекс хендла (он будет расти в любом случае, даже если ты дождёшься удаления юнита - и это правильно). Конечно я не сталкивался ещё с моментами когда достигался лимит хендлов, но судя по коду игры, если хендл "пустой", то он выдаётся уже новому юниту (если нет нового доступного), то бишь есть "переработка" хендлов.
Ниже прикрепил мой пример, как видишь, на чистой карте тупо с оборотом и таймеров, ничего не растёт.
Если не сложно, скинь потом оффсеты по которым ты вышел на число хендлов, думаю будет полезно глянуть.
Ладно, я пожалуй спать, завтра и статью обновлю (внесу правки), заодно и про Хештаблицу дополню пункт по поводу её размера.
Редактирование:
Гляну ещё и предоставленное видео по локации, ещё раз проведу у себя тесты, ибо я тоже помню, что в какой-то момент, где не было обнуления начала уходить память. Конечно посыл не был тем, чтобы все забили на обнуление, стоит наверное перефразировать момент с советом на обнуление и выразить его более строго. Ибо фактическое наличие обнуления - не потребляет уймы операций, так ещё и служит элементом безопасности.
Редактирование 2:
Решил сделать как в примере 1024 таймера, результат у меня почему-то тот же:
Либо у меня какой-то магический компьютер, то ли моя WFE какими-то выкрутасами эту проблему решает (что слишком сомнительно, ибо ничего подобного в моей программе нет).
Вариант без ВФЕ, результат тот же:
Карта:
Стоит игнорировать, ибо код не вызывался вообще. (Примеры для статьи делались в карте где не было этого косяка).
PT153 по поводу твоего примера, хотел бы исправить свой ответ (не стоило мне спешить с ответом с телефона).
Рассмотрим код ещё раз и взглянем на его байткод:
	globals
		location Loc
	endglobals

	function DoSomething takes nothing returns nothing
		local location loc
		set Loc = Location(0., 0.)
		set loc = Loc
	endfunction
Байткод
05070000 -> local handle
00000487 -> её id
0C290500 -> literal register (type) location
13290000 -> push register
0C2A0500 -> literal register (type) location
132A0000 -> push register
15000000 -> callnative
00000314 -> id нативки
11000000 -> setvar
00000F7D -> id переменной
0E2B0700 -> get var register (type) Handle
00000F7D -> id переменной
112B0000 -> setvar register
00000487 -> id переменной
0E2C0700 -> get var register (type) Handle
00000F7D -> id перемнной
132C0000 -> push register
15000000 -> callnative
00000315 -> id нативки
0C000000 -> literal R0
27000000 -> return
Что мы получаем? 3 фактических push, так как локальный реестр не был обнулён, то popstack (который вызывается всегда) решает его не трогать. Рассмотрим теперь что добавиться при RemoveLocation и обнулении обоих переменных и даст ли это что-либо.
call RemoveLocation( Loc )
set Loc = null
set loc = null
Добавит:
Байткод
0C2D0200 -> literal register (type) null
112D0000 -> setvar register
00000F7D -> id переменной
0C2E0200 -> literal register (type) null
112E0000 -> setvar register
00000487 -> id переменной
И мы получаем, что утечка "устранена". Однако обнуление глобальной по факту - бесполезно, потому мой поспешный ответ был частично правильным. То бишь посыл Location в глобалку, а затем её присваивание - равноцельно прямому посылу создания объекта.
Однако если мы возьмём это:
globals
    location Loc = Location( 0., 0. )
endglobals

function TestFunctionEx takes nothing returns nothing
    local location loc = Loc
endfunction

function TestFunction takes nothing returns nothing
    local integer i = 0
    
    loop
        exitwhen i > 1000
        call TimerStart( CreateTimer( ), .01, true, function TestFunctionEx )
        set i = i + 1
    endloop
endfunction
Что даст:
Байткод
05070000 -> local (type) handle
00000487 -> id переменной
0E2A0700 -> getvar register handle
00000F7D -> id переменной
112A0000 -> setvar register
00000487 -> id переменной
0C000000 -> literal R0
27000000 -> return
То утечки нет ввиду того, что более нету push действий и мы не добавляем регистры, в действии можно посмотреть на видео:

PT153 вопрос к тебе, как думаешь, утекает ли это? Заведомо прошу не делать тесты, а просто сделать свои предположения.
globals
    hashtable HashTable = InitHashtable( )
    location Loc = Location( .0, .0 )
endglobals

function TestFunctionEx takes nothing returns nothing
    local unit u = LoadUnitHandle( HashTable, 123, 321 )
endfunction

function TestFunction takes nothing returns nothing
    local integer i = 0
    
    call SaveUnitHandle( HashTable, 123, 321, CreateUnit( Player( 0 ), 'Hpal', 0., 0., 270. ) )
    
    loop
        exitwhen i > 1000
        call TimerStart( CreateTimer( ), .01, true, function TestFunctionEx )
        set i = i + 1
    endloop
endfunction

Добавлен новый пункт в статье про размер Хештаблиц. Спасибо JackFastGame за идею.
Загруженные файлы
2
28
2 года назад
Отредактирован PT153
2
Смотри видео внимательно, оно потом падает, ибо проходят и другие действия. Оно не растёт без конца. Оно ещё и падает
Предоставленный код тобою - утекает лишь в случае если его повторять и не удалять используемый объект.
Я предоставил ссылку на мою статью, где показывается, что если удалять точку и не обнулять переменную, потребление ОЗУ растёт. В твоей статье это якобы опровергается, но в тестах с локацией, что ты привёл, локалка обнуляется (ну и разумеется потребление ОЗУ остаётся неизменным). Это не опровержение, а подтверждение.
вопрос к тебе, как думаешь, утекает ли это
Судя по твоему тесту с юнитов - утечек не будет.

Я так понял, проблема с локалками возникает в том случае, если в функции создаётся хендл.
То есть, если создаётся хендл, то ЛЮБУЮ локалку этой функции, в которой этот хендл находится или находился в процессе выполнения функции, нужно обнулить.

И прекратите цитировать комментарий целиком по нескольку раз в ответе.

05070000 -> local handle
00000487 -> её id
0C290500 -> literal register (type) location
13290000 -> push register
0C2A0500 -> literal register (type) location
132A0000 -> push register
15000000 -> callnative
00000314 -> id нативки
11000000 -> setvar
00000F7D -> id переменной
0E2B0700 -> get var register (type) Handle
00000F7D -> id переменной
112B0000 -> setvar register
00000487 -> id переменной
0E2C0700 -> get var register (type) Handle
00000F7D -> id перемнной
132C0000 -> push register
15000000 -> callnative
00000315 -> id нативки
0C000000 -> literal R0
27000000 -> return
А почему тут два раза callnative? Нативка же одна в примере, Location.
Скорее всего часть взята из дополненного примера, где есть RemoveLocation.

Не могу понять, почему в моём примере байткод начинается с
05070000 -> local handle
00000487 -> её id
В то время как в твоём примере ниже
05070000 -> local (type) handle
00000487 -> id переменной
Хотя обе функции начинаются с local location.
Думаю, тут какая-то ошибка.

Нужно исправить все ошибки и более подробно объяснить, в каких ситуациях возникает утечка, а в каких нет. И главное, почему утечка переменной вообще возникает:
3 фактических push, так как локальный реестр не был обнулён, то popstack (который вызывается всегда) решает его не трогать
С чего он решает не трогать?
0
28
2 года назад
0
	globals
		location Loc
	endglobals

	function DoSomething takes nothing returns nothing
		local location loc
		set Loc = Location(0., 0.)
		set loc = Loc
	endfunction
В данном примере важно понять, утекает ли локальная переменная loc без обнуления или нет. Тут не нужно удалять локацию, она занесена в глобалку и будет использоваться в другом коде.
0
8
2 года назад
Отредактирован goodlyhero
0
Я помню, мудрые создали эту штуку, для красивого отображения, должно помочь не искать каждый раз нужное место, пусть и имеет свои минусы.
0
20
2 года назад
Отредактирован Unryze
0
PT153 local location loc всегда в начале будет 05070000, это банальное объявление для VM, что объявляется локальная переменная с типом. То бишь было бы local integer то стало бы 05060000.
В примере утекает именно локальная loc если её не обнулить, но утечка так же будет если не вызвать RemoveLocation, если же мы это делаем единожды и когда-нибудь этот объект удалим то обнуления локалки - достаточно.
А по поводу почему всё local handle - потому что байткоду плевать на прямой тип переменной, ибо для VM все переменные расширяющие Handle - это Handle.
		OPCODE_VARIABLE_NOTHING = 0,   // "nothing"
		OPCODE_VARIABLE_UNKNOWN,       // "unknown"
		OPCODE_VARIABLE_NULL,          // "null"
		OPCODE_VARIABLE_CODE,          // "code"
		OPCODE_VARIABLE_INTEGER,       // "integer"
		OPCODE_VARIABLE_REAL,          // "real"
		OPCODE_VARIABLE_STRING,        // "string"
		OPCODE_VARIABLE_HANDLE,        // "handle"
		OPCODE_VARIABLE_BOOLEAN,       // "boolean"
		OPCODE_VARIABLE_INTEGER_ARRAY, // "integer array"
		OPCODE_VARIABLE_REAL_ARRAY,    // "real array"
		OPCODE_VARIABLE_STRING_ARRAY,  // "string array"
		OPCODE_VARIABLE_HANDLE_ARRAY,  // "handle array"
		OPCODE_VARIABLE_BOOLEAN_ARRAY, // "boolean array"
"Судя по твоему тесту с юнитов - утечек не будет."
Верно, утечки не будет.
"Я так понял, проблема с локалками возникает в том случае, если в функции создаётся хендл.
То есть, если создаётся хендл, то ЛЮБУЮ локалку этой функции, в которой этот хендл находится или находился в процессе выполнения функции, нужно обнулить."
Абсолютно верно, однако если же мы не удалим объект и "утеряем" его и никогда сами не удалим, то будет утечка не переменной, а непосредственно объекта.
2
29
2 года назад
2
Unryze, есть миф, что if then {без кода} else {код} endif быстрее, чем if not then {код} endif. Я так и не получил утвердительного ответа на вопрос — так ли это?
Извиняюсь, если спрашиваю уже баянистую тему.
0
22
2 года назад
0
Ярг Восьмой, бред какой-то
0
20
2 года назад
Отредактирован Unryze
0
Unryze, есть миф, что if then {без кода} else {код} endif быстрее, чем if not then {код} endif. Я так и не получил утвердительного ответа на вопрос — так ли это?
Извиняюсь, если спрашиваю уже баянистую тему.
Этот миф имеет место существованию! Давайте же рассмотрим вновь байткод:
Сравнение байткода
Байткод func1
0C1A0800 -> literal register (type) bool
261A0000 -> not register
2A1A0000 -> jmpf register
000012AD -> label для прыжка
16000000 -> calljass (вызов джасс функции, операция схожа с callnative)
00000A93 -> id функции
2B000000 -> jmp
000012AE -> label id прыжка
28000000 -> label
000012AD -> id label'а
28000000 -> label
000012AE -> id label'а
0C000000 -> literal R0
27000000 -> ret
Байткод func2
0C1B0800 -> literal register (type) bool
2A1B0000 -> jmpf register
000012AF -> label для прыжка
2B000000 -> jmp
000012B0 -> label id прыжка
28000000 -> label
000012AF -> id label'а
16000000 -> calljass (вызов джасс функции, операция схожа с callnative)
00000A93 -> id функции
28000000 -> label
000012B0 -> id label'а
0C000000 -> literal R0
27000000 -> ret
Фактически, если "лень" читать и разбирать опять байткод и все операции, то можете исходить из логики, что любой код, где меньше "действий", создаст меньше байткода (если конечно же их базовая структура схожа), собственно где "действий" меньше, то она будет быстрее, что и получается тут:
Результат скорости
Ярг Восьмой, бред какой-то
Как видишь, оказалось не бредом, хотя и разница слишком минимальная и на уровне погрешности, что не делает этот миф очень важным, но даёт пример того, что меньше лишнего кода - всегда лучше. Это как сравнение not flag и flag == false, not flag будет всегда быстрее, ибо выполняется на 1 операцию меньше.
4
8
2 года назад
4
Unryze
Я отыскал в закоулках своей памяти карту, в которой возникала проблема из-за таких утечек. Там таблица хэндлов раздувалась примерно до 400 мегабайт, и, замечу, одним куском, с реаллокацией чего сложности возникнут раньше чем с кусками маленькими.)
Есть даже сейв, с которым можно сразу посмотреть проблему.
Даже удивительно, что что-то такое сохранилось.
2
20
2 года назад
Отредактирован Unryze
2
Unryze
Я отыскал в закоулках своей памяти карту, в которой возникала проблема из-за таких утечек. Там таблица хэндлов раздувалась примерно до 400 мегабайт, и, замечу, одним куском, с реаллокацией чего сложности возникнут раньше чем с кусками маленькими.)
Есть даже сейв, с которым можно сразу посмотреть проблему.
Даже удивительно, что что-то такое сохранилось.
Изучил карту, но тут разница есть большая, там не только утечки создают новые хендлы (а точнее их id), очень много CreateNUnits и прочих утекающих bj функций. А так, в целом, да, можно на этой карте проверить "лечение" утечек, добавить нужные обнуления, заменить bj-мусор и сравнить результаты. Я более чем уверен, что разница будет колоссальная. Особенно если все триггеры смерти заменить одним.
Но не стоит забывать, что занятый вес карты - это не только хендлы - это ещё и все модели, эффекты которые привязаны к существующим юнитам, декорации и прочее. Но конечно, как не крути - эта карта кишит утечками и каждую секунду добавляется по 30-40 килобайт, порой и больше.
Редактирование: причём потребление памяти увеличивается даже когда по факту ничего и не происходит. Удивительная карта в общем.
Спасибо за находку! :)
0
15
2 года назад
0
Было бы очень интересно почитать подобные технические особенности работы, только про Lua в WC3.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.