What kind
Cost grows where the model says it should not
Which benchmark
native/ tic timing: the resident simulation statement's CPU time per row over a 60-tic idle window and a 240-tic window that runs the chase, the shot and the attack, taken from system.query_log with parsing, analysis, planning and pipeline building subtracted. Interleaved arms on one throwaway container at the pinned digest, scripts/machine-lock.sh held per run.
The numbers
ClickHouse 26.8.2.7, three rounds interleaved, medians
arm window tic analysis FunctionExecute/tic
main d824fc9 60 38.65 ms 21.62 s 13237
troop attack wired 60 43.20 ms 27.87 s 13393
main d824fc9 240 34.46 ms 23.48 s 20971
troop attack wired 240 35.41 ms 27.32 s 21074
statement 1,073,633 -> 1,255,576 bytes
probes on the idle window, same session
both fold bodies replaced by the empty-list answer: main 38.42 troop 42.16 nobodies 41.18 ms
writebacks folded over the hit list (0 or 1 element): paired median +4.42 ms against +4.55 ms
The idle tic gate is +0.5 ms against main in the same session. The wiring of A_TroopAttack and A_SargAttack misses it by about 4 ms on the idle window and passes on the active one (+1.0 ms). The two routine bodies are folds and do not run on an idle tic; removing them recovers about 1 ms. Folding the eleven per-slot writebacks over the hit list recovers 0.1 ms. About 2.8 ms of idle cost per tic is not attributed to any expression.
The machine, and how quiet it was
Apple silicon development machine, other lanes' containers stopped for the timed runs, the machine lock held, arms interleaved. Not a quiet machine: round-to-round spread on this box is 1 to 3 ms, the same size as the effect. The relative figures held across three rounds and two sessions.
What you think is happening
The added expressions that do run on an idle tic are the mt_attackers filter (over arrays the tic already builds), the fourth list of sight pairs (a map over the empty attacker list), scalars over the fold's accumulator, and the eleven arrayMaps that write the claw's columns back. None of those is large. Candidates worth measuring one at a time with QueryAnalysisMicroseconds and FunctionExecute in system.query_log: the accumulator tuple's type (a tuple carrying two Array(Tuple) members through arrayFold may be copied at every read even when empty), the added sight::asking map widening mt_pairs and so the whole of check_sight, and whether the statement's growth by 180 KB moves a per-block fixed cost that is not analysis. Every further monster routine (the hitscan monsters, the missile's impact, the barrel's blast) adds the same shape, so whichever of these it is will be paid again.
What kind
Cost grows where the model says it should not
Which benchmark
native/tic timing: the resident simulation statement's CPU time per row over a 60-tic idle window and a 240-tic window that runs the chase, the shot and the attack, taken fromsystem.query_logwith parsing, analysis, planning and pipeline building subtracted. Interleaved arms on one throwaway container at the pinned digest,scripts/machine-lock.shheld per run.The numbers
The idle tic gate is +0.5 ms against
mainin the same session. The wiring ofA_TroopAttackandA_SargAttackmisses it by about 4 ms on the idle window and passes on the active one (+1.0 ms). The two routine bodies are folds and do not run on an idle tic; removing them recovers about 1 ms. Folding the eleven per-slot writebacks over the hit list recovers 0.1 ms. About 2.8 ms of idle cost per tic is not attributed to any expression.The machine, and how quiet it was
Apple silicon development machine, other lanes' containers stopped for the timed runs, the machine lock held, arms interleaved. Not a quiet machine: round-to-round spread on this box is 1 to 3 ms, the same size as the effect. The relative figures held across three rounds and two sessions.
What you think is happening
The added expressions that do run on an idle tic are the
mt_attackersfilter (over arrays the tic already builds), the fourth list of sight pairs (a map over the empty attacker list), scalars over the fold's accumulator, and the elevenarrayMaps that write the claw's columns back. None of those is large. Candidates worth measuring one at a time withQueryAnalysisMicrosecondsandFunctionExecuteinsystem.query_log: the accumulator tuple's type (a tuple carrying twoArray(Tuple)members througharrayFoldmay be copied at every read even when empty), the addedsight::askingmap wideningmt_pairsand so the whole ofcheck_sight, and whether the statement's growth by 180 KB moves a per-block fixed cost that is not analysis. Every further monster routine (the hitscan monsters, the missile's impact, the barrel's blast) adds the same shape, so whichever of these it is will be paid again.