Performance
Measured, not asserted. The method is at the bottom so you can re-run it and disagree.
Head to head #
Same box, same data, same probe: 20,000 attachments, 200 folders (20 roots × 9 children), 16,000 assignments. FileBird 6.5.8 seeded into its own tables with the identical structure, so both trees answer the same question. 2 vCPU, MariaDB, PHP-FPM 8.3.
| Media page (list) | Media page (grid) | wp/v2/media?per_page=40 | |
|---|---|---|---|
| Core only | 5 q · 14 ms | 5 q · 13 ms | 82 ms |
| + this plugin | 7 q · 13 ms | 7 q · 14 ms | 116 ms |
| + FileBird | 13 q · 20 ms | 13 q · 18 ms | 92 ms |
| Both | 14 q · 19 ms | 14 q · 21 ms | 123 ms |
Where it wins #
This plugin adds 2 queries to the media library screen. FileBird adds 8. Four times fewer, on the page every media task starts from — and the page time is indistinguishable from core's. It costs roughly nothing to have installed, while FileBird costs about 5 ms and 8 queries on every load.
That gap is the no-build-step decision showing up as a number. FileBird ships roughly 765 KB of React into your admin; this ships plain JavaScript and hand-written CSS.
Where it loses #
This plugin adds 34 ms to wp/v2/media. FileBird adds 10. They are three times
better than us on the call the grid actually waits for.
The reason is not flattering but it is understandable: our taxonomies are
REST-visible, so core assembles term data for all 40 attachments in the response.
FileBird's folders live in custom tables and are not in wp/v2/media at all, so
there is nothing for them to slow down. Their advantage here is the direct
consequence of the thing we criticise them for — the folders are invisible to the
API.
That does not excuse 34 ms. It is an N+1 shape: term data for a page of attachments should be one primed cache, not per-item lookups. Open item, not yet fixed, and published here rather than left out.
The folder tree #
vergeml/v1/tree, 20,000 attachments:
| Folders | Queries | Handler | Payload |
|---|---|---|---|
| 200 | 4 | 22 ms | 18.8 KB |
| 2,000 | 4 | 79 ms | 189 KB |
Flat query count across a tenfold increase in folders. The tree is O(1) in queries. Handler time grows with payload serialisation, which is linear and expected.
FileBird has no equivalent endpoint to compare against — it builds its tree into the page rather than serving it, which is why the comparison above is page-level.
Query counts agree across environments #
The same endpoints, measured on two different VPS with different operating systems, different PHP versions two releases apart, different database versions and different library sizes:
| Endpoint | Box A | Box B |
|---|---|---|
vergeml/v1/tree | 7 | 7 |
organize-step | 3 | 3 |
organize-run | 1 | 1 |
organize-cancel | 2 | 2 |
librarian-schemes | 1 | 1 |
librarian-batches | 1 | 1 |
Different hardware, different OS, different PHP, different row counts, same integers. That is what "a property of the algorithm" means, and it is why a disagreement between environments is read as a bug rather than a hardware difference.
(These counts were taken with a different probe and dataset from the folder-scaling
table above, which is why the /tree figure differs between the two. Each number is
reported as it was measured rather than reconciled after the fact.)
Search by meaning #
A 20,000-image meaning search returns in under half a second. That is possible because a compact projection of each vector is stored beside the full one, so the comparison reads a small column rather than unpacking 20,000 full-precision vectors.
The search also runs under a time budget — 2 seconds by default — and returns what it has rather than stalling the screen.
Method #
- Probe: an mu-plugin that separates handler time from WordPress boot for REST, and logs queries, milliseconds and memory for admin pages.
- Page figures: four requests, the last three averaged, the first discarded as cold.
- Plugins toggled with
wp plugin activate/deactivatebetween runs on the same install, so the data never changes underneath the comparison.
Query count is the number that transfers. It is a property of the algorithm, so it is identical in a WASM sandbox and on real MariaDB — verified. Wall-clock in a WASM sandbox is meaningless: PHP-wasm spends about 2.4 seconds booting WordPress on every request, so core's own endpoints time the same as ours there.
Provenance #
The head-to-head figures were taken on a 20,000-attachment dataset that no longer exists — the library was swept and reseeded while chasing test debris. The numbers stand as recorded, and nothing has been re-measured to replace them, deliberately: a smaller library gives different wall-clock, and quietly swapping one in would turn a real comparison into an incomparable one.
Sizing the AI work #
| Work | Chunk size |
|---|---|
| Describing, on screen | 24 files per step, 8 in flight |
| Describing, background | 20 seconds of work per cron tick, chunks of 8 |
| Reference scan | 200 posts per chunk |
| Duplicate hashing | 25 per batch, 200 per request |
| Folder-sorting apply | 25 files per step, 3.5 s budget |
| Clustering | 200 files per batch, 2.5 s budget |
Every one of those is a chunk size chosen to sit inside the 30-second
max_execution_time that shared hosting still ships, with room for the last call to
finish. Nothing here can time out on a small host, and every one of them resumes
where it stopped.
Written from version 3.15.0 of the plugin. Last updated 2026-10-02. All documentation · Legal · Support