Budget and time
✅ verified · the four probes these numbers come from: appendix C
The conclusions first
- Memory is the number of layers times the area painted on each, not the number of calls. A dozen semantic layers, each with strokes landing only inside its own area, caps out at a few gigabytes however many strokes there are; what explodes is three layers per value band per part, which is the one-shot route.
- Time is the number of calls: about 300 a second inside a tree (490 strokes a second at 4x), with a whole stroke being one
paintPathcall; only a tapered stroke goes segment by segment throughpaintLine. - 4x by default: the Krita half is more than twice as slow at 8x (one-shot painting 217 s at 8x against 167 s at 4x; the layered route’s draft, measured on the old route before completion, 374 s against 295 s; building the tree 209 s against 97 s), while after normalizing to 2508 px the criteria read within noise and scaling effects of each other.
Throughput
On a blank single-layer 8K document: paintLine runs at 450 to 850 strokes a second, and sizes of 8, 32, 120 and 400 make no difference – a large brush costs the same as a small one; paintPath runs at 310 to 390. Changing colour or brush size needs no round of the event loop (paint 24 patches in a row without running it and exactly 24 colours read back); running the event loop each time gives 71 strokes a second, a tax of 8.5x. A real layer tree of 307 clipped nodes costs only 7% more, and one paintPath call is 2.3 times faster than the same stroke segment by segment through paintLine. Points are free: a 132-point contour is 0.35 s in one paintPath.
Memory: measured wrongly once
The first version attributed the growth to “233 KB of undo stack per call that cannot be cleared”, arrived at by dividing 2.8 GB by 12,000 strokes. It was measuring the wrong thing (ch. 17, case 26). On the same tree, change one thing: where the strokes land.
| 12,000 strokes | RSS growth |
|---|---|
| all landing in the same three 1100 px areas, first round | +0.65 GB |
| another 12,000 in the same areas | +0.06 GB |
| scattered at random over the whole 8K canvas | +4.21 GB |
| another 12,000 scattered | +0.95 GB |
The same 12,000 calls, seventy times apart. The undo stack has a limit of Krita’s own, and once the tiles are saturated each stroke adds only 5 KB. What grows is the layers’ tile memory plus the groups’ projections: wherever a stroke lands, that 64x64 tile goes from empty to dense, until the whole layer is covered (an 8K RGBA layer is 400 MB). The one-shot route’s tree has over a hundred paint layers, its airbrush at 200 px diameter spills out of each one’s box, and then there are dozens of groups each with its own projection – which is where a dozen gigabytes at 8x comes from. Semantic layering is itself the fix for memory: the layered route paints more than three hundred thousand calls in a few minutes with no trouble at all.
Document.setBatchmode, kritarc’s undoStackLimit and Scratchpad (which has no painting API at all, ch. 15) were all tried and none of them is the road – and now the reason is clear: what was growing was never the undo stack.
Save, close, reopen
saveAs -> close -> openDocument -> addView is faithful (identical pixel hashes, clip, blend and vector layers intact, and painting works after reopening), but it does nothing for memory: closing returns only about 1 GB of undo stack and cache, and the dense layers load straight back out of the .kra (7.32 -> 6.40 -> 8.84 GB). Its use is showing a person the intermediate states: the finished version saves a checkpoint after each top-level group.
Two pitfalls the probe fell into itself: it wrongly concluded that painting after reopening had no effect – the layer was already full, so counting opaque pixels cannot grow and the content hash has to be compared instead; and it held a node handle from the old document, so of course the memory did not come back after closing (ch. 17, cases 27 and 28).
How long each step takes
The plain-Python half (segmentation, arbitrating the lines, recovering the completion, planning) does not depend on the multiplier and runs entirely at 1x; the ComfyUI half depends on the GPU; and the Krita half depends on the multiplier and the number of calls. The measured times on the worked example are in ch. 19, and the orders of magnitude are in the table in “Ten minutes”.