Skip to content

parse() is superlinear (quadratic ? cubic ?) in the number of at-rules #92

@sloonz

Description

@sloonz

cssom parse() struggle when a stylesheet contains a lot of at-rules

import { parse } from "@acemir/cssom";
import { performance } from "node:perf_hooks";

function median(a) {
  const b = [...a].sort((x, y) => x - y);
  return b[Math.floor(b.length / 2)];
}

// if t = C*n^a => log(t) = log(C) + a log(n)
// So a is the slope of log(t) = f(log(n))
function localExponent(n1, t1, n2, t2) {
  return (Math.log(t2) - Math.log(t1)) / (Math.log(n2) - Math.log(n1));
}

function makeCSS(n) {
  let out = "";
  for (let i = 0; i < n; i++) {
      out += "@media all{.sidebar{max-width:calc(33.33333% - 30px);min-width:calc(33.33333% - 30px)}}";
  }
  return out;
}

function benchParse(css, reps = 3) {
  const times = [];
  for (let r = 0; r < reps; r++) {
    const t0 = performance.now();
    parse(css);
    times.push(performance.now() - t0);
  }
  return { med: median(times), min: Math.min(...times), max: Math.max(...times) };
}

let prev = null;

for (let n = 1; n <= 8192; n *= 2) {
  const css = makeCSS(n);

  const { med, min, max } = benchParse(css);

  const t_over_n = med / n;
  const t_over_n2 = med / (n * n);
  const a = prev ? localExponent(prev.n, prev.t, n, med).toFixed(3) : "";

  console.log(
    `${new Date().toISOString()}  n=${n}` +
    ` parse_med=${med.toFixed(3)}ms [min=${min.toFixed(3)} max=${max.toFixed(3)}]` +
    ` t/n=${t_over_n.toExponential(3)}` +
    ` t/n^2=${t_over_n2.toExponential(3)}` +
    ` a≈${a}`
  );

  prev = { n, t: med };
}

Results :

2026-01-12T08:02:48.534Z  n=1 parse_med=0.402ms [min=0.163 max=13.237] t/n=4.023e-1 t/n^2=4.023e-1 a≈
2026-01-12T08:02:48.538Z  n=2 parse_med=0.233ms [min=0.225 max=0.281] t/n=1.164e-1 t/n^2=5.819e-2 a≈-0.790
2026-01-12T08:02:48.539Z  n=4 parse_med=0.476ms [min=0.334 max=0.528] t/n=1.189e-1 t/n^2=2.973e-2 a≈1.031
2026-01-12T08:02:48.541Z  n=8 parse_med=0.522ms [min=0.437 max=0.676] t/n=6.529e-2 t/n^2=8.161e-3 a≈0.135
2026-01-12T08:02:48.544Z  n=16 parse_med=0.949ms [min=0.946 max=1.015] t/n=5.929e-2 t/n^2=3.706e-3 a≈0.861
2026-01-12T08:02:48.556Z  n=32 parse_med=3.815ms [min=3.137 max=4.670] t/n=1.192e-1 t/n^2=3.726e-3 a≈2.008
2026-01-12T08:02:48.614Z  n=64 parse_med=19.045ms [min=18.580 max=20.343] t/n=2.976e-1 t/n^2=4.650e-3 a≈2.320
2026-01-12T08:02:49.033Z  n=128 parse_med=139.074ms [min=139.047 max=140.658] t/n=1.087e+0 t/n^2=8.488e-3 a≈2.868
2026-01-12T08:02:52.278Z  n=256 parse_med=1079.296ms [min=1079.147 max=1086.483] t/n=4.216e+0 t/n^2=1.647e-2 a≈2.956
2026-01-12T08:03:18.694Z  n=512 parse_med=8642.909ms [min=8569.836 max=9203.149] t/n=1.688e+1 t/n^2=3.297e-2 a≈3.001
2026-01-12T08:06:49.825Z  n=1024 parse_med=70540.290ms [min=68913.875 max=71676.931] t/n=6.889e+1 t/n^2=6.727e-2 a≈3.029
2026-01-12T08:37:11.430Z  n=2048 parse_med=623447.724ms [min=559588.192 max=638568.616] t/n=3.044e+2 t/n^2=1.486e-1 a≈3.144

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions