worstshape

command
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: BSD-3-Clause Imports: 8 Imported by: 0

Documentation

Overview

Command worstshape finds the document shape a handler set is slowest on, by running the same handlers over documents of the same size in different shapes and ranking them.

The point is that document size is the wrong axis. Held at 200 KB, the shapes below span a factor of about 1900 in time per byte, and what they span is handler calls per byte - not nesting depth, not markup validity, not how much of the document matches. Measured on an M3 Pro, fastest of seven passes, with a handler set of two element selectors and a document-level text and comment handler:

shape                            ns/byte   alloc B/KB    calls
implied end tags (li)          103.565     19,667.8  120,000
malformed: unclosed tags        50.974      6,561.0   40,000
attribute-heavy anchors         42.233      3,284.0   17,646
many text nodes                 41.267      7,290.0   44,444
tables                          35.122      5,180.9   31,578
many siblings                   29.497      2,986.3   18,181
deep nesting                    25.376      2,986.9   18,183
many comments                   16.926      2,055.7   25,000
bogus comments                  13.478      1,646.1   20,000
malformed: stray end tags        3.144          8.5        0
one element, many attributes     1.601          8.6        1
one long comment                 0.481          7.7        1
one long text node               0.076          8.8        2
raw text (script)                0.067          8.8        2
entities                         0.065          8.0        2
one long attribute value         0.055          8.6        1

Three things worth taking from that.

The worst shape is a list of <li> with no closing tags, and it is worst because it produces three handler calls per element: the element itself, its text, and the empty final chunk that ends the text node. Forty thousand list items in 200 KB is 120,000 calls. It is not a pathological document - it is what a navigation menu looks like.

A document of stray end tags is nearly free, and that is B194 showing up as a cost: no handler ever sees a stray end tag, so 200 KB of them produce zero calls and allocate 8.5 bytes per KB, which is the floor. The same floor holds for one long text node, one long comment, a script, and one element with a 200 KB attribute value - all of them one or two calls.

So a rewrite's cost is set by how finely the document is divided, and a handler set that looks cheap on prose can be twenty times more expensive on markup of the same size. The allocation column is the same story and does not depend on the machine: 19.7 KB allocated per KB of input at the top, about 8.5 bytes per KB at the bottom.

The ranking is a property of the handler set, not of the library, which is why this is a harness rather than a table: point it at your own handlers.

One caveat about the ns/byte column. The cheapest shapes here take a few microseconds, and a clock that ticks every 340µs - the Windows runner's - reports them as exactly 0. That is the clock's answer rather than an error, so nothing here asserts a duration is above zero, and the gates are the allocation and call columns, which are the same number on every machine. The tool says so when it happens.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL