Fifty-two seconds to seventy-five milliseconds
The login page's modules were arriving on the X1 and then nothing happened. Between the last module landing and its entry script's verdict there were twelve minutes on one boot with nothing said at all, and nine more on another.
Twelve minutes is not a slow machine. Twelve minutes is a wrong algorithm, and the honest first step was to stop guessing about it on the laptop and time the same work on the host.
Fifty-two seconds for one module
Parsing one 2.6 MB module makes a few million syntax-tree nodes of a few dozen bytes each. The process heap was a single first-fit free list, walked for every allocation and for every free.
That parse takes fifty-two seconds in release on an M-series Mac. The 757 KB module beside it takes six.
Behind the allocator added here, the same two parses take seventy-five and twenty-two milliseconds.
Fourteen size classes, sixteen bytes up to one thousand and twenty-four, sit in front of the linked-list heap. A class is one free list pushed and popped at the head, and a class that runs dry carves a sixty-four kilobyte slab from the backing heap. Anything larger, or wanting more than sixteen-byte alignment, goes to the backing heap as before. A block freed into a class stays in that class, which is the usual trade for this shape and the right one for a process that is about to parse the next megabyte.
Every user process gets it, because every process has the same heap. The kernel's heap is left alone, deliberately - it has a different allocation pattern and it is not the thing that was slow.
Two smaller things found on the way
The parser's step budget was a fixed two million steps. The 5.6 MB chunk behind the same page spent all of them by line 102 of 2,146 and reported "gave up at line 102", which reads precisely like a syntax error on line 102. A budget that does not scale with the input is an instrument that lies about large inputs. It now grows with the source.
And the browser says which entry it is about to run before it runs it, so the next time the log's timestamps separate the rebuild from the run rather than leaving twelve minutes of silence to be interpreted.
What is not built
The seventy-five milliseconds is measured on the host. The X1 is a slower part and the figure there will be larger; what has gone is the shape of the curve, not a specific number on that machine, and I have not run the comparison on the laptop.
A block freed into a size class never returns to the backing heap. For a process that parses a large bundle and then runs it, that is the correct trade. For a long-lived process whose allocation sizes change over its life it is a leak with a bounded size, and nothing here measures that bound.