When a map slows down the usual move is to find the code that looks expensive and change it. That guess is often wrong, because the cost of code translated into triggers is not visible in the source: one read is dozens of triggers, and two lines that look alike can differ tenfold.
epTrace ships beside euddraft. It prints a cost figure per annotated call, which turns "iterating every unit is expensive" into a number.
[ 24 ] using_loopunit.main(); // 맵에 유닛 약 30마리 / about 30 units on the map [ 10 ] using_unit_group.main(); [ 97 ] using_loopunit.main(); // 500마리 이상 / 500+ units [ 9 ] using_unit_group.main();
Reading it is simple: measure two implementations under the same conditions and compare. "The same conditions" is the part that matters - in the table above the gap is a little over twofold at 30 units and more than tenfold at 500. Measure one situation only and you will pick the wrong one.
Measure before changing anything that runs every cycle, and measure before accepting somebody's claim that a pattern is cheap. Most cost claims in this scene were passed along without a measurement, and they change between versions.
If measuring is impractical, one rule carries most of the weight: do not read the same value repeatedly. Reads are expensive and writes are cheap, so anything that does not change inside a loop should be read once outside it.
In practice three wastes come up again and again, and all three are the same mistake: the question is being asked at the wrong granularity.
| 유닛 루프 안의 Bring | A Bring asks a per-player question and a unit loop runs per unit, so inside one it runs once per unit instead of once per player. Check it before the loop, store the answer in a counter, and have the loop read the counter. Switching to a unit group changes nothing. |
|---|---|
| 반복해서 읽는 배열 | Every read of an array element is dozens of triggers. Pull one that does not change inside the loop out into a const. |
| 플레이어마다 분기하는 런타임 루프 | A runtime loop that then lists "if player 1", "if player 2" pays for the loop and keeps the duplication. Seven separate triggers are better, and a compile-time foreach writes them from one copy of the code. |
There is a reason for the last one: there is a CurrentPlayer but no CurrentLocation. A Bring whose location varies per player cannot ride the CP trick, and rewriting its location fields from a variable costs more triggers than it saves. If the locations are numbered consecutively, $L("p1") + p is the whole of it.
And look at the classic triggers before you profile the EUD side. The shape that makes a map slow is an Always() trigger with a pile of unit-targeting actions on it. Each one has to walk units and test them against a location, every cycle, whether or not anything changed.
| MoveUnit · Modify계열 · SetInvincibility · KillUnitAt · RemoveUnitAt · Order | All of them have to work out which units are inside a location. |
|---|---|
| 액션이 조건보다 무겁습니다 | A Modify and a Bring make the same test and the action costs more, so gating the action behind the condition wins - on a map where that unit is usually absent, one Command in front of it is a large saving. |
| 최악은 갈 자리가 없는 MoveUnit | When the destination is full of units or off the map, the engine hunts for free space and does not find any. This is the exact contrast with writing coordinates into the struct, which goes to the exact spot or nowhere. |
And checking several players on a classic trigger runs it that many times: players = {P5, P6, P7, P8} with CurrentPlayer in the actions is four executions. Where the work is not really per-player - invincibility on a whole force's buildings, say - give the trigger to one player and address the force.
Why a variable costs 72 bytesWhich way round you write a condition is the cost