Pretext Lab
24Perf bench
Arc 8 · Production Patterns

Perf bench

The last lesson is a tool. Two panels, same job: measure workload items of public-domain prose against a 400-pixel column. The left panel reads heights off the DOM. The right panel asks Pretext. Each panel times itself.

Crank the workload. Run one pane, then the other. At workload 500 both finish in a blink. At workload 5000 the numbers separate. Use this to answer a single decision: for your use case, does Pretext earn its seat?

DOM measurement
measurements/sec
last pass
items
probe → getBoundingClientRect() per item · forces layout flush
Pretext engine
measurements/sec
last pass
items
handles prepared once at mount · layout() is pure arithmetic
1000
run both panes to compare — ratio

Mechanism

Both panels walk the same pool of 1,000 public-domain paragraphs and measure the rendered height of each at a fixed column width. The inputs are identical; only the engine differs.

DOM panel: for each item we push text into an off-screen probe, set the probe's width, and read getBoundingClientRect().height. That read forces the browser to flush any pending style/layout work. Doing it N times gives us a measured height N times — and N forced flushes.

Pretext panel: at mount we call prepare() once on each item and cache the handle. Each run iterates the cached handles and calls layout(handle, 400, lineHeight). No DOM touch, no flush, no canvas call. The work is word-wrap arithmetic over cached segment widths.

At low workload both engines are fast enough that the difference doesn't matter. At high workload the DOM panel's time scales with N layout flushes; the Pretext panel's time stays a handful of milliseconds. Your user's slider handler is a high-workload environment in disguise.

Application

This bench is the decision moment. If your component's measurement budget per frame is 16ms and you need to measure 500 items during a drag — is the DOM panel's rate enough, or do you need the Pretext panel's? Run it.

Use it before you adopt the library: confirm the gap is real on your machine. Use it after you adopt: sanity-check a regression by running the Pretext pane and watching the rate stay where it was. Use it when a teammate asks whether it's worth the dependency: run both, show the number.

The shape of the answer does not depend on marketing. It depends on your browser, your machine, your text, and the engine you picked.

"I learned this, at least, by my experiment: that if one advances confidently in the direction of his dreams, and endeavors to live the life which he has imagined, he will meet with a success unexpected in common hours."

Henry David Thoreau, Walden (1854, conclusion)

Direct Claude

The course ends here. The primitives are small; the applications are not.

"confirm it's faster before adopting" run the bench on your machine, your text, your browser "500× isn't marketing" side-by-side at high workload; both engines measuring the same text "does this component need Pretext" compare rate to the measurement budget per frame you actually have "make sure a regression didn't land" run the Pretext pane — the rate should be stable across refactors
The lab is over. You have the mental model (Arc 1), the primitives (2–4), the rich-inline and manual paths (5–6), the rendering targets (7), and the production patterns (8). What will you build?
two engines, same job, side-by-side timings
import { prepare, layout } from '@chenglou/pretext';

// Mount: prepare once per item. The handles are shared across runs.
const handles = items.map(t => prepare(t, FONT));

// Pretext pane: N layout() calls. Pure arithmetic.
function runPretext(n) {
  const t0 = performance.now();
  for (let i = 0; i < n; i++) {
    void layout(handles[i % handles.length], 400, LINE_H).height;
  }
  const ms = performance.now() - t0;
  return { ms, rate: n / (ms / 1000) };
}

// DOM pane: N probe reads. Each read flushes layout.
function runDOM(n) {
  const t0 = performance.now();
  for (let i = 0; i < n; i++) {
    probe.textContent = items[i % items.length];
    probe.style.width = '400px';
    void probe.getBoundingClientRect().height;  // forces layout
  }
  const ms = performance.now() - t0;
  return { ms, rate: n / (ms / 1000) };
}