A CPU quota does not change Chromium 153's processor count

A two-CPU quota left Chromium reporting fourteen processors. Restricting affinity changed the count to two. Nine sessions show why throughput needs its own measurement.

7 min read
RSS

A browser can report fourteen logical processors while its container receives an aggregate CPU budget of two. That does not require a patched navigator property. We reproduced it with Debian's Chromium 153.0.8010.52, then compared the same binary with its affinity restricted to two CPUs.

This distinction matters when interpreting Heretic's compute findings. A worker throughput curve measures completed work under the conditions of that run. Before comparing it with a reported processor count, we need to know what each number describes.

Two ways to restrict the same browser

We started a fresh headless browser for each condition in an isolated container on an aarch64 Linux VM. The browser ran headless with sandboxing disabled inside the disposable container; it had no external network access. The control could use CPUs 0 through 13. The quota case kept that affinity mask and used Docker's --cpus 2. The affinity case used --cpuset-cpus 0,1 without a quota.

The browser binary was identical in every case. We did not override navigator.hardwareConcurrency. The page and every worker reported the same value within each case.

Condition Effective CPU set cpu.max Page and worker hardwareConcurrency
Control 0–13 max 100000 14
Two-CPU quota 0–13 200000 100000 14
Two-CPU affinity 0–1 max 100000 2

The Linux CPU controller expresses the quota as a time budget and period. 200000 100000 allows 200,000 microseconds of CPU time per 100,000-microsecond period. Several runnable threads can consume that budget concurrently. It does not restrict execution to two named CPUs.

The upstream code for Chromium 153.0.8010.52 explains the observed count. On Linux, SysInfo::NumberOfProcessors() starts from the configured logical processor count, narrows it using sched_getaffinity, and caches the result. That function does not read cpu.max. The navigator accessor returns this count.

This is a source explanation consistent with the tested Debian binary, not a rebuild attestation. The HTML standard also permits a count below the logical processors potentially available. It does not promise a particular throughput from that count.

Measure completed work separately

Each worker performed a dependent xorshift32 loop with the same seed:

js
let x = 123456789;
for (let i = 0; i < iterations; i++) {
  x ^= x << 13;
  x ^= x >>> 17;
  x ^= x << 5;
}
postMessage(x >>> 0);

We warmed eight persistent workers, then ran widths of one, two, four and eight with three repetitions per width. Sweep order alternated between ascending and descending widths. The page measured elapsed time from dispatch until it received every result. A separate Python implementation checked each answer using repeated squaring of the xorshift transform over GF(2).

A width-eight round contains eight times the arithmetic of a width-one round. We calculate aggregate speedup as:

text
speedup(width) = width × median(time at width 1) / median(time at width)

Each condition uses its own single-worker baseline. These are throughput ratios, not inferred processor counts.

The first experiment used 80 million iterations per worker. A second experiment used fresh browser sessions and 800 million iterations. Its quota-limited rounds spanned many budget periods. Duration and session changed together, so their individual effects are not isolated. Both experiments also retained 200,000-iteration rounds to expose short-duration behavior.

For the 800-million-iteration rounds on 25 September, the median times were:

Condition One worker Four workers Eight workers Eight-worker speedup
Control 1,173.0 ms 1,197.5 ms 1,285.7 ms 7.30×
Two-CPU quota 1,105.5 ms 2,464.5 ms 9,099.5 ms 0.97×
Two-CPU affinity 1,090.9 ms 2,258.6 ms 4,670.2 ms 1.87×

The quota case still reported fourteen processors. Its eight-worker repetitions accumulated 91, 89 and 93 throttled periods. Control and affinity eight-worker repetitions accumulated none. The median whole-cgroup CPU consumption at width eight was approximately 7.96, 2.00 and 2.00 CPU-seconds per elapsed second respectively. Those counters include the browser and Python controller.

The two restrictions therefore produced similar aggregate CPU consumption at width eight, different navigator counts, and different completed-work throughput.

The timing difference needs another control

The 80-million-iteration quota run reached 1.87× at eight workers. The 800-million run reached 0.97×. Both returned correct answers. Across both experiments, all 588 worker outputs, including warmups, passed the independent arithmetic check.

We repeated the 800-million-iteration experiment on the following day using the same immutable image, identical probe and workload bytes, and three new browser sessions:

Condition Reported processors Eight-worker speedup on 25 September Eight-worker speedup on 26 September
Control 14 7.30× 6.05×
Two-CPU quota 14 0.97× 1.89×
Two-CPU affinity 2 1.87× 2.03×

The reported counts repeated. The quota case's earlier throughput collapse did not. Its repeat consumed a median of approximately 2.00 CPU-seconds per elapsed second at width eight and recorded 71, 70 and 68 throttled periods. All 294 additional worker outputs, including warmups, passed the arithmetic check.

The original samples remain in the results. We have not isolated the cause of their throughput difference, and cannot attribute it to workload length. This fixture does not control physical CPU placement, power state or other work on the host. The repeat's control baseline also changed, from 1,173.0 to 1,719.1 milliseconds. The nine browser sessions establish the count distinction in this build, but do not establish a portable timing curve or hardware ceiling.

Short rounds have another problem. In the first quota experiment, the 200,000-iteration eight-worker rounds reported a 3.20× median-based ratio with zero throttled periods. Those rounds lasted a median of one millisecond, far shorter than the 100-millisecond budget period. Message overhead, timer resolution and quota phase matter at that duration. A brief ratio above two does not show that the quota failed.

What this changes about a compute finding

Heretic's inspected compute.cores-below-claim and compute.cores-exceed-claim rules are Conditional. Their premises depend on how observed parallel work relates to the reported availability. This experiment supplies a stock-browser control for that distinction. An accurate description of the observed throughput can be useful while the inference of a false processor declaration remains unproved.

The fixture did not execute Heretic's collector or request a production assessment. It tested no Apostate or CloakBrowser binary. It also used headless automation; unchanged navigator properties do not make that session a human visitor. Those are separate observations with separate premises.

Reproduce the comparison

The reproduction archive contains the standalone fixture, nine recorded sessions, source references and checksum analyzer. Python 3 can validate the recorded results without running a browser:

sh
python3 browser/analyze.py
python3 browser/analyze.py --long
python3 replication-2026-09-26/analyze.py

For new measurements, the archive explains how to build and run fresh containers without overwriting the recorded samples. It requires Docker with cgroup v2 and CPUs 0 and 1 available. The Dockerfile pins the Debian base image, but later package installation may obtain a different Chromium version. The original image is not distributed; compare the recorded browser version and binary hash before calling a rebuild identical.

Start each browser after setting the CPU controls. Record the page and worker counts, retain the arithmetic outputs, and inspect throttling alongside elapsed time.

Next article A tunnel endpoint is not always a TCP endpoint An IP-in-IP packet experiment compares forwarding with TCP termination on the same delayed link.

The probe runs on your own browser.

The public demo returns the verdict and its named findings.