Contact support

We reply by e-mail, usually within two days.

Google reCAPTCHA checks this submission against abuse; data is sent to Google. The script loads only once you open this form.

← All posts

Every request loads all of lib/

Line 46 of this service's single entry file:

foreach (glob(__DIR__ . '/lib/*.php') as $f) { require_once $f; }

That is 65 files and 1.5 megabytes, loaded on every request. A counter image, a statistics page, a 404 — all the same. Yesterday that happened 20,734 times.

26 counter design families — 715 KB, one gets used flags 189 KB · charts 158 KB · everything else 470 KB cold, no opcode cache: 132 ms opcodes cached on disk: 18.2 ms opcodes in shared memory: 0.33 ms — the glob and 65 stat calls 65 files, 1.5 MB, 56 classes, every request

The number that matters is the small one

Loading all of it from scratch, with the opcode cache switched off, takes about 132 milliseconds. That is the number that would make this a scandal, and it is not the number the server pays.

With opcodes already compiled and held in shared memory, what is left is the directory listing and one stat() per file, because timestamp validation is on: 0.33 milliseconds. At yesterday's traffic that is under seven seconds of work across the whole day.

In between sits a third measurement, 18.2 milliseconds, taken with the opcode cache reading from disk instead of memory. It is an upper bound rather than the real figure — it is what happens when the cache exists but is slower than the real one. It is reported because it is the only warm number that can be measured from the command line, and pretending otherwise would be the easy mistake.

So: this costs almost nothing, and it costs almost nothing entirely because of something else. Turn off the opcode cache and the same line costs four hundred times more. That is a real dependency, and it is worth knowing it is there rather than discovering it during an upgrade.

What is actually in the 1.5 megabytes

Forty-seven per cent of it — 715 kilobytes across 26 files — is counter design families. Drawing a counter image needs exactly one of them. The other 25 are loaded, declared, and never touched.

After that: 189 kilobytes of flag data, needed on the geography page; 158 kilobytes of charting library, needed when something draws a chart. Neither is involved in serving a counter image, which is the request this service answers more than any other.

The visible cost of that is memory rather than time. Peak memory per request goes up by about four megabytes, and unlike the opcodes — which are shared between all processes — that part is paid per request, in parallel, by every worker at once.

Somebody already noticed

The glob is lib/*.php. It is not recursive, and two directories sit just underneath it: lib/live/ with 229 kilobytes and lib/recht/ with 182 kilobytes. Four hundred kilobytes that used to be in the load path and now are not.

They were moved because they are big and rarely needed: the statistics sections and the legal texts. Being one directory deeper is the whole mechanism. There is no configuration, no lazy loader, no autoload map — a file is either in the glob or it is not.

That is a real fix and it is worth saying so, because the obvious next step would be an autoloader, and an autoloader here would be a large change to solve a problem that a mkdir already solved twice.

Why it stays

A single glob is the simplest thing that can possibly work, and on the evidence it does work: a third of a millisecond, on a Raspberry Pi, on the path that has to be fast. Replacing it with per-file requires means every new file needs a line somewhere, and the line that gets forgotten produces a fatal error in production rather than a slightly larger load.

What it does need is the thing that made it survivable in the first place, written down: if a file in lib/ gets big, it moves into a subdirectory and gets required where it is used. That is the rule those two directories were following, and it was not written anywhere until now.

The general version is duller than the measurement. "Everything loads everything" is usually the wrong shape, and it is worth knowing whether it is costing you anything before rebuilding it. Here it costs 0.33 milliseconds and four megabytes, and the four megabytes are the part worth watching, because they scale with concurrency and the milliseconds do not.

Advertisement