# Release-mode comparison with LiteSpeed Web Server Enterprise (run 7): 2026-10-12

URL: https://scalws.com/docs/benchmarks/2026-10-12-xeon-release-run7/
Source: `docs/benchmarks/2026-10-12-xeon-release-run7.md` in the ScalWS repository

> Note (2026-11-13): a newer comparison exists, `2026-11-12-xeon-1.0.0.md` (the 1.0.0 package, current nginx and OpenLiteSpeed). This report is kept as the record of its date.

Machine and pinning as in `2026-10-10-xeon-bare-metal-pgo.md`: 2× Xeon E5-2650 v2, server
on four physical cores (CPUs 0-3), upstream and applications on 4-7, `oha` on socket 1;
`performance` governor, turbo off, BIOS power profile at maximum performance (see
`2026-10-11-xeon-release-nginx-openlitespeed.md`). `MODE=release`: 10 s warm-up, 30 s measurement,
5 runs, medians. scalws: the PGO + BOLT build of `tools/pgo-build.sh` (ADR-0047, commit
a7dc639), which adds the Python allocator settings, the fixed-response shortcut and the
controller's cgroup subgroup to run 6; `final` with both experimental settings,
`final-hyper` with defaults.
LiteSpeed Web Server Enterprise 6.3.7 (official release tarball, trial licence) replaces
OpenLiteSpeed as the third server, with `bench/configs/litespeed` (the OpenLiteSpeed
settings in its XML form). It has no fixed-response directive, so `respond` is not
measured for it. Every measurement of every server answered 2xx only. No run was excluded.
The clock log (socket-0 cores every 5 s) shows 2 593–2 600 MHz under load; the samples
below 2.4 GHz fall almost all into the 1 h 45 min the harness stood still (see below), when
the cores were idle.

| scenario | scalws final | final-hyper | nginx 1.28 | nginx 1.31 | LiteSpeed 6.3.7 | final / nginx 1.31 | final / LSWS |
|---|---:|---:|---:|---:|---:|---:|---:|
| static-small | 108 441 | 106 858 | 96 551 | 99 224 | 110 346 | **1.09** | 0.98 |
| static-1m | 11 581 | 11 584 | 11 642 | 11 676 | 8 932 | 0.99 | **1.30** |
| respond | 204 022 | 199 561 | 201 219 | 209 330 | n/a | 0.97 | n/a |
| proxy | 98 888 | 82 883 | 80 011 | 92 496 | 29 975 | **1.07** | **3.30** |
| cache-hit | 160 071 | 159 507 | 84 669 | 85 819 | 48 280 | **1.87** | **3.32** |
| tls-h2-static-small | 96 148 | 94 751 | 66 811 | 68 954 | 69 862 | **1.39** | **1.38** |
| php | 42 569 | 41 811 | 36 217 | 36 897 | 18 689 | **1.15** | **2.28** |
| node | 10 823 | 10 932 | 10 749 | 10 824 | 11 618 | 1.00 | 0.93 |
| python | 6 366 | 6 360 | 6 465 | 6 540 | 5 859 | 0.97 | **1.09** |

Server CPU per request (µs, `cpu_us`): static-small 36.88 / nginx 1.31 40.30 / LSWS 17.64; static-1m 345.38 / 342.56 / 223.62; respond 19.60 / 19.10 / n/a; proxy 40.34 / 43.16 / 66.42; cache-hit 24.98 / 46.59 / 41.24; tls-h2-static-small 41.30 / 57.88 / 28.57; php 62.00 / 78.40 / 106.31; node 65.69 / 66.38 / 91.79; python 84.89 / 87.38 / 117.93.

Versions: nginx 1.28.3 (Ubuntu 26.04 package) and 1.31.6 (built from the signed source
tarball); load: `oha` with 64 connections.

p99 latency of every server (ms, median of 5 runs, `p99_ms`):

| scenario | scalws final | final-hyper | nginx 1.28 | nginx 1.31 | LiteSpeed |
|---|---:|---:|---:|---:|---:|
| static-small | 0.69 | 0.79 | 0.74 | 0.71 | 1.68 |
| static-1m | 6.84 | 8.18 | 6.41 | 6.66 | 19.04 |
| respond | 0.40 | 0.42 | 0.41 | 0.40 | n/a |
| proxy | 0.91 | 1.14 | 1.01 | 0.95 | 4.13 |
| cache-hit | 0.50 | 0.51 | 0.88 | 0.83 | 2.99 |
| tls-h2-static-small | 1.37 | 1.43 | 1.14 | 1.09 | 1.80 |
| php | 1.77 | 1.79 | 2.05 | 2.02 | 5.83 |
| node | 11.49 | 11.37 | 11.53 | 11.45 | 10.69 |
| python | 13.78 | 14.48 | 15.51 | 15.61 | 12.79 |

Server CPU per request of every server (µs, median of 5 runs, `cpu_us_per_req`):

| scenario | scalws final | final-hyper | nginx 1.28 | nginx 1.31 | LiteSpeed |
|---|---:|---:|---:|---:|---:|
| static-small | 36.88 | 37.42 | 41.42 | 40.30 | 17.64 |
| static-1m | 345.38 | 345.27 | 343.56 | 342.56 | 223.62 |
| respond | 19.60 | 20.04 | 19.87 | 19.10 | n/a |
| proxy | 40.34 | 48.22 | 49.96 | 43.16 | 66.42 |
| cache-hit | 24.98 | 25.07 | 47.23 | 46.59 | 41.24 |
| tls-h2-static-small | 41.30 | 41.93 | 59.72 | 57.88 | 28.57 |
| php | 62.00 | 64.20 | 80.31 | 78.40 | 106.31 |
| node | 65.69 | 76.39 | 74.04 | 66.38 | 91.79 |
| python | 84.89 | 99.39 | 95.97 | 87.38 | 117.93 |

Peak resident memory of the server processes (MiB, median of 5 runs, `peak_rss_mib`):

| scenario | scalws final | final-hyper | nginx 1.28 | nginx 1.31 | LiteSpeed |
|---|---:|---:|---:|---:|---:|
| static-small | 40 | 42 | 80 | 80 | 71 |
| static-1m | 40 | 40 | 80 | 80 | 71 |
| respond | 42 | 42 | 80 | 80 | n/a |
| proxy | 43 | 44 | 84 | 83 | 72 |
| cache-hit | 41 | 43 | 81 | 80 | 72 |
| tls-h2-static-small | 53 | 49 | 99 | 98 | 73 |
| php | 44 | 46 | 81 | 81 | 72 |
| node | 42 | 42 | 83 | 84 | 71 |
| python | 42 | 44 | 84 | 84 | 72 |

p99 latency (ms), scalws final / nginx 1.31 / LSWS: static-small 0.69 / 0.71 / 1.68; proxy
0.91 / 0.95 / 4.13; cache-hit 0.50 / 0.83 / 2.99; tls-h2-static-small 1.37 / 1.09 / 1.80;
php 1.77 / 2.02 / 5.83; node 11.49 / 11.45 / 10.69; python 13.78 / 15.61 / 12.79; respond
0.40 / 0.40 / n/a.

## LiteSpeed Enterprise: what to keep in mind

* **Two workers.** The trial licence starts two `lshttpd` workers whatever
  `workerProcesses` says (`pgrep -c -f 'lshttpd - #'` during the runs: 2); nginx and scalws
  use all four server CPUs. On server-bound scenarios LSWS therefore had half the cores.
  Its CPU per request is lower on static files (17.6 vs 36.9 µs on static-small) and it
  still reached 110 346 req/s there, 2 % ahead of scalws `final`; with the four workers of
  a larger licence it would likely lead static-small clearly. It was behind on everything
  that passes through an upstream or the cache (proxy 3.3×, cache-hit 3.3×, PHP 2.3×).
* **Cancelled requests.** In every run `oha` recorded "operation was canceled" against
  LSWS on static-small (1 707–1 871 of ~3.3 million requests, 0.05 %) and static-1m (7–25),
  and once "connection closed before message completed"; no other server had any error
  besides the requests still in flight at the end of the measurement ("aborted due to
  deadline"). Every response LSWS sent was 2xx.
* **PHP.** LSWS's PHP-FPM used half the application CPU of the others (45.9 s vs ~91 s per
  run): with two workers and the same pool (four PHP-FPM processes, `maxConns 4`) it did not
  keep the pool busy.
* **Node.js** is the one runtime where LSWS is ahead (7 %). It sends fewer request headers
  to the application: no `X-Forwarded-For`, `X-Forwarded-Proto` or `X-Request-Id`
  (`2026-10-12-app-respond-analysis.md` measured 83.4k vs 77.0k instructions per request in
  Node.js for the two header sets). scalws keeps these headers.
* **Shutdown.** LSWS ignores SIGTERM unless it has handled a SIGUSR2 first; once in run 7
  the TERM came too early and the harness waited 1 h 45 min (16:05–17:50 local). `bench/run.sh`
  now repeats both signals until the process is gone.

## Compared with run 6

* Python: `final` 6 366 (run 6: 5 957), now 0.97× nginx 1.31 with less server CPU per
  request (84.9 vs 87.4 µs). The managed uvicorn workers get glibc allocator thresholds
  (`MALLOC_MMAP_THRESHOLD_`, `MALLOC_TRIM_THRESHOLD_`), which remove the mmap/munmap per
  request seen on bare metal; application CPU is equal (60 s per run, two saturated
  workers), so the remaining 3 % is on the application side.
* `respond` 0.97× nginx 1.31 (run 6: 0.95×) with the fixed-response shortcut; the ranges of
  the five runs overlap (scalws 203 269–210 092, nginx 201 909–213 934).
* `node` 1.00× nginx (run 6: 1.00×), `php` 1.15× (1.06×), `proxy` 1.07× (1.07×),
  `tls-h2-static-small` 1.39× (1.37×).
* `cache-hit` `final` 160 071 vs 173 288 in run 6 while `final-hyper` is unchanged (159 507
  vs 158 764). The only data-plane change on that path is the fixed-response check in
  routing (one call per request that returns nothing for a proxy route), which
  `final-hyper` runs as well. Open: a second PGO build of the same commit would show
  whether this is profile-to-profile variation.
* `static-small` `final` 108 441 vs 111 956 (−3 %), within the run-to-run spread of earlier
  bare-metal runs.

Results, in the bench host's results directory: `results/xeon-r7` (`aggregate.csv`, `aggregate.txt`, `run-1` …
`run-5`); clock log `r7-mhz.log` on the bench host.
