Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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