Repository navigation
perf: add bench/memray/locust tooling + fix i18n dict-copy allocation - #32
Merged
Merged
Conversation
…ocation Introduces a first-class performance harness for the project: - pytest-benchmark (`make bench`) for micro-benchmarks, gated behind a `perf` marker so `make test-py` stays unchanged. - memray (`make memray-run`, `make memray-flamegraph`) for allocation profiling of arbitrary Python targets. - locust + memray load-profiling (`make loadtest`, `make loadtest-memray`) that starts uvicorn under memray, seeds a forged-session-cookie admin so traffic exercises real authenticated code paths, and renders a flamegraph. The load-profile also surfaced a real own-code allocation hotspot: `build_i18n_block` was calling `dict(registry.messages(locale))` on every Inertia page render to coerce a `MappingProxyType` into something the JSON encoder accepts. Across a 100-user / 30s run this was ~37K allocations and ~58 MB churn for data that never changes within a process. `I18nRegistry.messages_snapshot(locale)` builds the plain-dict copy once at `load()` time and hands out the same instance on every call. After the fix the function is no longer in the top-50 allocation sites; total allocations drop by ~35K and churn by ~59 MB while throughput and latency stay flat.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
pytest-benchmark,memray, andlocust— wired through the Makefile (make bench,make memray-run,make loadtest,make loadtest-memray) so the project can measure CPU, allocations, and load behavior without ad-hoc scripts.build_i18n_blockwas copying the i18n messages dict on every Inertia render. A newI18nRegistry.messages_snapshot(locale)caches the plain-dict copy atload()time.What changed
Tooling
pytest-benchmark>=4.0,memray>=1.14(non-Windows),locust>=2.31added to thedevgroup.perfpytest marker gates benchmarks out of the defaultpytestrun;make benchopts them in. A sample benchmark againstforge_session_cookiedemonstrates the pattern.make memray-run/make memray-flamegraphrecord and render allocation profiles of any Python target (defaults to the benchmark suite).make loadtestdrives an already-runningmake dev;make loadtest-memraystarts uvicorn under memray, seeds an admin, drives it with locust, shuts down (process-tree-aware) and emits an HTML flamegraph.scripts/loadtest_seed.pymints a signed Starlette session cookie so the locustfile'sAuthedUserexercises real product-service code paths (bypasses the login rate limiter under load)..memray/and.benchmarks/added to.gitignore.Fix
messages_snapshot(locale) -> dict[str, str]that returns a plain-dict copy built once atload()time. Rationale recorded in the docstring.dict(registry.messages(locale))(per-request copy) toregistry.messages_snapshot(locale).Why
Under
make loadtest-memraywith 100 users / 30s of authenticated traffic,build_i18n_blockwas the #6 allocation site with ~37K allocations and ~58 MB of churn per run — for data that is immutable within a process. The surrounding code had a comment explaining the copy was required because Inertia's JSON encoder doesn't acceptMappingProxyType; moving the copy to load-time is a one-off rather than per-request.After the fix, the function is no longer in the top-50 sites. Remaining hotspots (
starlette.routing.Route.matches,itsdangeroussigning,json.iterencode,sqlalchemy.await_only,inertia.templating) are all vendored framework code.Measurements (100 users, 30s, authenticated scenarios)
build_i18n_blockrankTest plan
uv run pytest framework/core/tests/test_i18n.py framework/hosting/tests/→ 136 passedmake benchruns the sample benchmark and prints timingsmake loadtest-memray(default args) seeds a user, profiles a full run, and emits a flamegraph; port is freed on exitbuild_i18n_blockdrops out of the top allocation sites