Hazel performance analysis · ^report(12) · 2026-10-13
Moving a group, not its points
If the Drawing library's picture were a tree of groups, each with a transform, instead of a flat list of polygons, how much smaller would a slider's picture diff be, and would the computation follow? ^report(11) estimated the first from the shapes alone, and its What next, step 1 asked for the real thing. This report builds it for one slide, Full Body (Figure 1), and measures both.
Short answers. One slide, Full Body, rebuilt as a picture of groups; the same 27 slider edits as ^report(11); the same three measures (^report(11)'s terms): the picture diff (what the edit changed in the picture), the trace distance (the fewest evaluation steps any update could take again) and the update cost (the steps an actual update takes again). Steps, not time, and in steps, not shares: today's picture, the tree and the tree flattened each take a different number of steps from scratch.
- The tree draws today's picture exactly. Flattened, its points are today's to within 6×10−14, in all 28 pictures (before each edit and after).
- The picture diff falls to at most 14 nodes on the edits that change the picture (it was up to 39 shapes), and is never larger than before: arm angle 30 shapes to 8 nodes, hand angle 24 shapes to 2 nodes, shirt width 39 shapes to 13 nodes (Figure 2).
- What a transform cannot carry, it does not: a hand's outline is a band of fixed width, so it is made at the hand's size, not grown with it (hand size 24 shapes to 14 nodes); bigger eyes widen the head and lift the hair, by design (eye size 7 shapes to 5 nodes).
- The tree alone is cheaper to build than today's picture; flattening it is not: drawn from scratch, the tree takes 110,260 evaluation steps, 10% fewer than today's picture's 121,973, since placing a part is one transform instead of moving every point; flattening it takes 37,957 steps more, 148,217 in all (Figure 5).
- Not flattened, the computation follows the picture diff: moving or turning a part costs about what an edit that changes nothing costs (4,249 steps). Trace distance, in steps, today's picture to the tree: arm angle 20,206 to 5,104, hand angle 25,026 to 4,807, neck 10,887 to 5,154; widening the shirt 25,506 to 9,196; growing a hand 28,484 to 14,108. The eye edits gain least (eye size 61,876 to 50,937): most of their steps are the check that keeps the mouth clear of the eyes, which the tree does not change (Figure 6).
- Flattened at the end, the gain is gone: flattening walks the whole tree and puts the flat list together again after every edit, 3,247 to 28,018 steps more than the tree alone, so most edits cost more than today (arm angle 20,206 to 28,849); the eye edits come out about even.
- Some of the tree's work is redone whatever the edit: an edit that changes nothing in the picture costs 4,249 steps in the tree, 1,085 today. Which work that is, is not broken down here (What next).
- Hazel's update today does not change: it evaluates the figure again whole, 85.2 to 89.0% of a run from scratch on today's picture and 81.5 to 87.7% on the tree (104,193 and 90,238 steps for an arm-angle edit). The figure is one top-level binding, so the smaller diffs help only an update that can keep parts of a run (
^report(11)).
The picture diff
Today the Drawing library places a part of the figure by moving every one of its points, so a slider that moves a part changes every shape in it: the arm-angle slider moves both arms and both hands, 30 shapes. In the tree, a part is a group: its shapes stay in its own coordinates, and its transform puts it in its parent's. The arm-angle slider changes the transforms of the two arms, their sleeves, the sleeves' joins to the shirt and the two hands, and no shape at all: 8 nodes. The same goes for the other placements: the neck on the body, the face on the neck, the hair on the face, the shoes on the feet. Figure 3 draws the whole tree.
What a transform cannot carry
A transform moves, turns and scales a group as a whole. Three things in Full Body are not a whole moved, turned or scaled, and the tree keeps them as shapes made at their own size:
- A hand's outline. A hand with fingers is drawn twice: its outline, the same hand grown 2.5 units all round and drawn underneath, and then its fill. The fill grows with the hand; the outline's width does not, so it cannot be the same group scaled. The hand-size slider changes the two fills' groups and the twelve outline shapes: 24 shapes to 14 nodes. A renderer's stroke of fixed width (SVG's
vector-effect: non-scaling-stroke) could carry it; the picture would then be drawn a little differently, and this report keeps to today's picture. - An eye's whites and iris. They are a band past the pupil, of a width set by their own sliders, not the pupil grown; they are drawn at their own size inside the eye's group. (Full Body's default eyes have neither.)
- The head, round the eyes. Bigger or wider-set eyes widen the head and lift its top, and the hair grows with it, by design: the face makes room for its eyes. The eye-size slider changes the two eyes' groups, the head's outline and the hair's two groups: 7 shapes to 5 nodes.
The arm-width slider changes the arms' and sleeves' outlines, a reshaping, not a move: 6 shapes either way.
Building the groups
Today the library places a part by moving every one of its points. The tree places a part with one transform, so the tree alone takes 10% fewer steps than today's picture. Flattening it composes every group's transform with its parents' and applies it to every point, and builds the flat list again, list by list: 37,957 steps, more than the tree saved.
The trace distance and the update cost
^report(11)): on today's picture, on the tree flattened at the end, and on the tree not flattened. For scale: today's picture takes 121,973 steps from scratch.A smaller picture diff is a smaller diff of the result. The trace distance is a diff of the computation; on the tree not flattened, it follows the picture diff:
- Moving or turning a part: arm angle 20,206 to 5,104 steps, arm length 20,190 to 6,498, hand angle 25,026 to 4,807, neck 10,887 to 5,154. The edit changes a few transforms, and the shapes inside are made from the same inputs as before, so their steps can be kept; what is left is near the floor below.
- Widening the shirt: 25,506 to 9,196. The shirt is stretched by a group, and the arms, sleeves and hands move out with the shoulders: 13 transforms and no shape, yet about twice the floor's steps, which is not broken down here.
- Growing a part: hand size 28,484 to 14,108; its outlines are still made at the hand's size (above).
- The eyes: eye size 61,876 to 50,937, eye gap 54,239 to 44,664, an iris 70,275 to 55,568. The eyes' shapes are now made from inputs the eye sliders do not change, and the sliders reach them only through their groups' transforms; but most of the eye sliders' steps are the check that keeps the mouth clear of the eyes, as
^report(11)found, and the tree does not change it. - Reshaping a part: arm width 2,927 to 6,236, shirt height 6,553 to 8,901, shoe size 4,056 to 4,838: a little more than today, near the floor.
- The floor: an edit that changes nothing in the picture costs 4,249 steps in the tree, 1,085 today: some of the tree's work is redone whatever the edit.
Flattened at the end, every edit pays for the flattening again (3,247 to 28,018 steps more than the tree alone), and the tree's gains are gone: arm angle 20,206 to 28,849, shirt width 25,506 to 37,214, an edit that changes nothing 6,012. Not flattened, the tree is what a renderer that draws groups (SVG's <g transform>, a scene graph) would take.
Hazel's update today: 85.2 to 89.0% of a run from scratch on today's picture, 81.5 to 87.7% on the tree, for the edits of Figure 2; for an arm-angle edit, 104,193 steps on today's picture, 128,194 on the tree flattened, 90,238 on the tree alone. The whole figure is one top-level binding, and Hazel's reuse is by binding (^report(10)), so it is evaluated again whole whatever its representation; it takes fewer steps on the tree alone only because the tree is cheaper to build. The smaller diffs help an update that can keep parts of a run, a memo keyed by names, as ^report(11) measured; they do not help today's.
Every edit
| slider | picture diff, today (shapes) | picture diff, groups (nodes) | trace distance, today (steps) | trace distance, groups flattened | trace distance, groups not flattened | Hazel's update, today (steps) | Hazel's update, groups not flattened |
|---|---|---|---|---|---|---|---|
| shirt width | 39 | 13 | 25,506 | 37,214 | 9,196 | 108,558 | 96,741 |
| arms (up / down) | 30 | 8 | 20,206 | 28,849 | 5,104 | 104,193 | 90,238 |
| arm length | 30 | 8 | 20,190 | 29,229 | 6,498 | 104,189 | 90,234 |
| hand size | 24 | 14 | 28,484 | 35,085 | 14,108 | 104,189 | 90,234 |
| hand angle | 24 | 2 | 25,026 | 25,890 | 4,807 | 110,025 | 90,263 |
| shirt height | 9 | 5 | 6,553 | 14,678 | 8,901 | 108,048 | 96,147 |
| neck | 9 | 4 | 10,887 | 15,837 | 5,154 | 105,197 | 91,277 |
| smile | 8 | 5 | 18,817 | 23,983 | 13,652 | 104,189 | 90,234 |
| eye size | 7 | 5 | 61,876 | 59,984 | 50,937 | 104,025 | 90,070 |
| arm width | 6 | 6 | 2,927 | 9,859 | 6,236 | 104,189 | 90,234 |
| shoe size | 4 | 2 | 4,056 | 8,723 | 4,838 | 104,189 | 90,234 |
| eye gap | 4 | 2 | 54,239 | 50,411 | 44,664 | 102,429 | 88,474 |
| eye shape (round / narrow) | 2 | 2 | 51,031 | 50,027 | 45,830 | 104,189 | 90,234 |
| eye angle | 2 | 2 | 50,899 | 53,543 | 47,992 | 104,029 | 90,074 |
| shine size | 2 | 2 | 7,297 | 9,861 | 6,614 | 104,189 | 90,234 |
| human fingers | changes the count | 104 | 100,391 | 152,596 | 80,353 | 184,214 | 153,124 |
| iris size | changes the count | 9 | 70,275 | 67,269 | 55,568 | 112,424 | 94,701 |
| mouth open | changes the count | 6 | 41,530 | 43,498 | 33,068 | 134,282 | 111,148 |
| alien fingers | 0 | 0 | 1,085 | 6,012 | 4,249 | 104,189 | 90,234 |
| star points | 0 | 0 | 1,085 | 6,012 | 4,249 | 104,189 | 90,234 |
| star turn (% to the next point) | 0 | 0 | 1,085 | 6,012 | 4,249 | 104,189 | 90,234 |
| claws | 0 | 0 | 1,085 | 6,012 | 4,249 | 104,189 | 90,234 |
| claw length | 0 | 0 | 1,085 | 6,012 | 4,249 | 104,189 | 90,234 |
| eye whites | 0 | 0 | 1,574 | 5,995 | 4,232 | 104,189 | 90,234 |
| spiky teeth | 0 | 0 | 1,070 | 5,995 | 4,232 | 104,189 | 90,234 |
| tongue length | 0 | 0 | 1,944 | 6,869 | 5,106 | 104,189 | 90,234 |
| tongue width | 0 | 0 | 1,987 | 6,912 | 5,149 | 104,189 | 90,234 |
Steps, counted as the evaluator takes them. From scratch: today's picture 121,973, the tree flattened 148,217, the tree alone 110,260. Edits that change how many shapes there are (the number of fingers, an iris, opening the mouth) change which calls are made; their picture diff in the flat picture is not a shape-by-shape count. Edits whose picture diff is 0 move a slider of a style Full Body is not wearing (claws, stars, alien fingers, spiky teeth, a tongue), or eye whites, which show only with an iris.
What next
- Draw the groups. Let the Drawing library's
^picturetake a tree and render it as SVG groups, so the picture diff of Figure 2 reaches the page, and time a slider release against today's. - Find the floor. Break down the 4,249 steps an edit that changes nothing costs in the tree (against 1,085 today), with
^report(11)'s diagnosis of which calls' inputs differ. - Flatten only what changed. A flattening that keeps each group's flat shapes, keyed by the group and its composed transform, would not put the whole list together again after an edit; measure its trace distance against the tree's.
- The other slides. Rebuild Witch and Scenes as groups, where
^report(11)found the largest flat diffs (the witch's woods, the red eyes, a shared symbol for repeated shapes). - An outline that a group can scale. Try a fixed-width stroke for hand outlines and measure the hand-size slider again, with the picture checked by eye.
How this was put together
- Build: #2635 at
c4e64060f1, with^report(11)'s oracle on a local branch, not pushed, and two more of its modes: one exports each edit's picture, and one measures another expression in the picture's place (here, the tree instead of its flattening). - The slide:
full-body-groups.hz, an experimental copy of the Livelit Playground's Full Body slide (not in the Hazel repository): aScenetype of leaves and groups, each group with an affine transform (a b c d e f, as SVG'smatrix), aflattenthat composes them, and the figure's parts placed, armed, handed and eyed as groups. Its picture isflatten(scene). - Checks: each flattened picture against today's slide's, point by point (all 28 within 6×10−14); trees compared node by node, children matched by their longest common subsequence, numbers within 10−9 counted equal, and the flat pictures counted with the same tolerance.
- Edits and runs: the 27 slider edits of
^report(11)on Full Body, each moved by a fifth of its range; the slide cut at its picture, analyzed withStatics.mk_unmemoized, evaluated from scratch and incrementally, as in^report(11). Today's picture was measured again on the same build as the tree; it matches^report(11)'s run step for step. Steps are deterministic, so one run of each suffices; every control is clean. - The numbers, for programs too: every table in this report, and what its numbers measure, is also in this page as JSON, in its
<script type="application/json" id="report-data">element, so a reader or an agent that fetches the page alone has the dataset. The same, with any raw files that survive, is in012/data/.