perf(thumbnails): raise decode-concurrency cap cpus/2 -> cpus

Shrink-on-load decoupled peak heap from source resolution (~18-25 MB per decode
regardless of MP), so the RAM ceiling that justified halving decode concurrency
is gone. max_concurrent_decodes() now defaults to all cores, with an
OXICLOUD_THUMBNAIL_DECODE_CONCURRENCY override for ops tuning.

Measured on the real service path (Semaphore + spawn_blocking), 14 cores:
- 12MP: 92.7 -> 133.7 photos/s (1.44x)
- 24MP: 49.5 -> 69.9 photos/s (1.41x)
peak heap unchanged; cpus*2 yields nothing, confirming cpus is the right ceiling
for CPU-bound work (the gap to 2x is rayon oversubscription -- Task 1.7).

Adds a semaphore-bounded throughput harness (Table D) to the bench example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DioCrafts
2026-06-21 15:32:15 +02:00
parent fd5808c157
commit 51713b218d
3 changed files with 127 additions and 9 deletions
+24 -1
View File
@@ -148,7 +148,30 @@ visually indistinguishable). Output bytes unchanged (e.g. 12 MP: 57→58 KB).
### Follow-ups this unlocked
- **Task 1.5** (raise `cpus/2` → `cpus`): peak heap no longer scales with MP, so
the OOM ceiling that justified halving concurrency is largely gone.
the OOM ceiling that justified halving concurrency is largely gone. ✅ done below.
- The `MAX_DECODE_PIXELS` 50 MP reject could be relaxed — huge JPEGs now decode
cheaply at 1/8 — but that is a behaviour change, deferred.
---
# Phase 1.5 results — raise decode-concurrency cap (`cpus/2` → `cpus`)
`max_concurrent_decodes()` now defaults to all cores (was half), overridable via
`OXICLOUD_THUMBNAIL_DECODE_CONCURRENCY`. Safe only because Phase 1.1 decoupled
peak heap from source resolution.
Measured with a harness that mirrors the **real service path** (Table D:
`tokio::Semaphore(permits)` + `spawn_blocking`, many concurrent requests),
14 cores, 3 s window:
| case | 7 permits (`cpus/2`, old) | 14 permits (`cpus`, new) | 28 (`cpus*2`) |
|-----------|--------------------------:|-------------------------:|--------------:|
| jpeg_12mp | 92.7 | **133.7 (1.44×)** | 133.3 (—) |
| jpeg_24mp | 49.5 | **69.9 (1.41×)** | 71.1 (+1.7%) |
- **~1.4× throughput** on the real path, for free; peak heap unchanged (17–25 MB).
- `cpus*2` yields nothing → `cpus` is the right ceiling for CPU-bound work.
- It's 1.4× not 2× because `render_all` fans its 3 sizes onto rayon, so 7 permits
already partly fill all cores — the remaining headroom is **Task 1.7**
(rayon oversubscription).