@@ -628,4 +628,124 @@ func getUser(ctx *breeze.Context) {
628628 " name" : " Alice" ,
629629 })
630630}
631- ```
631+ ```
632+
633+ ---
634+
635+ ## FastAPI vs FastAPI-StartKit (Python benchmark)
636+
637+ Two Python ASGI entries in this harness implement the ** same three benchmark
638+ routes** with identical handlers (` GET / ` → empty, ` GET /user/{id} ` → the id as
639+ plain text, ` POST /user ` → empty). This section compares them.
640+
641+ ### Overview
642+
643+ | | ** fastapi** | ** fastapi-startkit** |
644+ | ---| ---| ---|
645+ | What it is | The raw [ FastAPI] ( https://fastapi.tiangolo.com ) micro-framework (Starlette + Pydantic). | A batteries-included application framework built ** on top of** FastAPI (Laravel-style providers / config / service container). |
646+ | Benchmark app | A single ` FastAPI() ` instance in ` server.py ` with three route handlers. | An ` Application ` booted with a ` FastAPIProvider ` ; the same three routes are registered through the framework's router (` providers/ ` , ` config/ ` , ` bootstrap/ ` ). |
647+ | Directory | ` python/fastapi ` | ` python/fastapi_startkit ` |
648+
649+ Both wrap the same underlying FastAPI / Starlette request path — ` fastapi-startkit `
650+ adds a thin framework layer around it.
651+
652+ ### Correctness
653+
654+ Both pass the shared route spec (` .spec/route_spec.rb ` ) ** 6/6** , verified through
655+ the standard Docker + rspec harness and confirmed in-container
656+ (` docker exec … curl localhost:3000 ` , so it is immune to host-networking
657+ artifacts):
658+
659+ | Route | Expected | fastapi | fastapi-startkit |
660+ | ---| ---| ---| ---|
661+ | ` GET / ` | ` 200 ` , empty body | ✅ | ✅ |
662+ | ` GET /user/0 ` | ` 200 ` , body ` 0 ` | ✅ | ✅ |
663+ | ` POST /user ` | ` 200 ` , empty body | ✅ | ✅ |
664+
665+ ### Setup / dependencies
666+
667+ | | ** fastapi** | ** fastapi-startkit** |
668+ | ---| ---| ---|
669+ | Declared dependency | ` fastapi>=0.139,<0.140 ` | ` fastapi-startkit[fastapi]==0.47.0 ` |
670+ | Underlying FastAPI | ` 0.139.0 ` | ` 0.139.0 ` (transitive, via ` fastapi-startkit==0.47.0 ` ) |
671+ | App composition | single ` FastAPI() ` in ` server.py ` | ` Application ` + ` FastAPIProvider ` (providers / config / bootstrap) |
672+ | Engines | uvicorn (default), hypercorn, daphne, granian | uvicorn (default), hypercorn, daphne, granian |
673+ | Default build | ` .Dockerfile.uvicorn ` | ` .Dockerfile.uvicorn ` |
674+ | Python | 3.14 | 3.14 |
675+
676+ The ` fastapi-startkit ` dependency is pinned to ` ==0.47.0 ` , which resolves the
677+ ** same FastAPI 0.139.0 / Starlette 1.3.1** the baseline uses, so both Python
678+ entries benchmark on an identical FastAPI/Starlette stack; both are built and run
679+ from the same default ` .Dockerfile.uvicorn ` engine.
680+
681+ ### Performance
682+
683+ Both serve identical handlers over the ** same ASGI server (uvicorn)** in the
684+ ** same container recipe** , so the per-request hot path is the same FastAPI /
685+ Starlette routing; ` fastapi-startkit ` adds framework wiring at ** boot** (provider
686+ / router / container setup), not on the per-request path.
687+
688+ A controlled, same-host relative comparison (from the Benchmark Runner) shows the
689+ two are ** close on the GET routes** (within a few percent) with a ** larger
690+ framework overhead on ` POST /user ` ** at higher concurrency.
691+
692+ ** Relative, same-host comparison — NOT official benchmark figures.**
693+
694+ Method (verbatim):
695+
696+ > macOS + OrbStack; Docker python:3.14-slim; uvicorn --workers=nproc (11
697+ > workers); load = oha 1.14, keepalive/connection-reuse, latency-correction;
698+ > 8s × 4 reps averaged; concurrency 64 and 256; 100% success every cell. Matched
699+ > stack both sides: Python 3.14.6, fastapi 0.139.0, starlette 1.3.1
700+ > (fastapi_startkit built from fastapi-startkit 0.47.0).
701+
702+ Relative delta (fastapi-startkit vs fastapi; negative = fastapi-startkit slower):
703+
704+ | Route | Δ @ c64 | Δ @ c256 |
705+ | ---| ---| ---|
706+ | ` GET / ` | −2.9% | −6.4% |
707+ | ` GET /user/0 ` | −1.5% | −6.2% |
708+ | ` POST /user ` | −17.3% | −18.8% |
709+
710+ Absolute throughput (host-specific, for context only): ` fastapi ` ≈ 41–47k rps,
711+ ` fastapi-startkit ` ≈ 33–40k rps, 100% success across all cells.
712+
713+ Disclaimer (verbatim):
714+
715+ > Relative, same-host comparison (macOS/OrbStack), NOT official benchmark
716+ > figures. Load uses keepalive, not the harness's --disable-keepalive;
717+ > authoritative numbers require run.sh on Linux. Requires fastapi-startkit 0.47
718+ > so both run FastAPI 0.139.
719+
720+ > ✅ ** Both sides match the committed build.** The committed benchmark pins
721+ > ** ` fastapi-startkit==0.47.0 ` ** , which resolves the same ** FastAPI 0.139.0 /
722+ > Starlette 1.3.1** the baseline uses. These deltas therefore reflect the
723+ > committed, apples-to-apples build — not a version-skewed comparison.
724+ >
725+ > ** Why ` POST /user ` is slower.** It is * not* ` fastapi-startkit ` per-request
726+ > code — the hot path is stock FastAPI / Starlette. The gap comes from
727+ > ** FastAPI 0.139's ` include_router ` ** : unlike FastAPI ≤ 0.124 (which flattened
728+ > included routes into the app router), 0.139 keeps a nested
729+ > ` fastapi.routing._IncludedRouter ` node on ` app.router.routes ` , adding a
730+ > per-request resolution layer. This is ** intended FastAPI behavior** (cached
731+ > candidate resolution / router-identity), and it is ** reproducible in plain
732+ > FastAPI** — any app that registers routes via ` include_router ` on 0.139 pays
733+ > it. ` fastapi-startkit ` uses ` include_router ` as its idiomatic route-module
734+ > registration, so the benchmark shows this representative cost rather than
735+ > hiding it (e.g. via flat ` add_api_route ` ).
736+ >
737+ > ` include_router ` 's overhead is a long-standing, acknowledged FastAPI topic.
738+ > A related upstream discussion,
739+ > [ fastapi/fastapi #5343 ] ( https://github.com/fastapi/fastapi/issues/5343 ) ,
740+ > reports ` include_router ` performance cost and requests a top-level
741+ > ` FastAPI(router=…) ` bypass — though that report concerns ` include_router ` 's
742+ > * construction-time* cost (per-route introspection in the app-factory pattern),
743+ > a different mechanism from the * per-request* ` _IncludedRouter ` resolution
744+ > measured here. Both reflect the same theme: ` include_router ` carries overhead
745+ > that flat registration avoids.
746+
747+ > ⚠️ ** Benchmarking caveat.** Reliable * absolute* numbers under the harness's
748+ > standard ` --disable-keepalive ` were not obtainable on macOS (ephemeral-port /
749+ > ` TIME_WAIT ` exhaustion), so the figures above use keepalive and are host-
750+ > specific. Authoritative absolute throughput/latency requires the Linux
751+ > harness (` run.sh ` ).
0 commit comments