Benchmarks

One core of an Apple M3 Pro, Python 3.13, 64 keep-alive connections, median of three 5-second runs per rung. Every number comes from a JSON file under bench/baseline/ in the repository; this page is generated from those files.

Where things stand

Microseconds per request and requests per second, one core; lower microseconds is better. The first column is the day-one ladder run of FastAPI 0.141 on uvicorn, the other two are the current tree.

RouteFastAPI 0.141 day one, uvicornnotslowapi, uvicornnotslowapi, granian
raw ASGI app13.7 µs73,040 req/s13.5 µs74,172 req/s7.8 µs128,801 req/s
Starlette route19.5 µs51,183 req/s17.4 µs57,533 req/s8.5 µs117,204 req/s
Starlette, int path + str querynot measured20.9 µs47,773 req/snot measured
notslowapi, typed dict return31.2 µs32,033 req/s18.3 µs54,793 req/s9.1 µs110,154 req/s
notslowapi, untyped dict returnnot measured21.2 µs47,257 req/snot measured
notslowapi, route via include_routernot measured21.2 µs47,173 req/snot measured
Starlette, 50 routesnot measured20.4 µs49,117 req/snot measured
notslowapi, 50 routesnot measured21.9 µs45,746 req/snot measured
notslowapi, 50 routes via include_routernot measured26.3 µs37,968 req/s17.3 µs57,864 req/s
notslowapi, int path + str query57.0 µs17,531 req/s24.2 µs41,405 req/s14.3 µs69,742 req/s
notslowapi, pydantic body + response_model52.1 µs19,212 req/s26.1 µs38,295 req/s20.1 µs49,777 req/s

Rungs added after day one show as not measured in the first column. Single-core numbers; multi-core scaling needs a separate client machine. Source files: results_ladder_v1.json and results_ladder_v3.json.

Every change, measured before and after

Each row is one committed change, measured in the same session with the previous tree checked out for the before run. Rungs not touched by a change are controls. A dot marks a rung that got faster.

changerungbefore µsafter µsdelta
fix1l1b_starlette_params25.322.4-12%
fix1l2_fastapi_dict31.230.7-2%
fix1l3_fastapi_params50.735.3-30%
fix2l2_fastapi_dict31.026.3-15%
fix2l3_fastapi_params36.232.6-10%
fix2l4_fastapi_model42.736.7-14%
fix5l2_fastapi_dict25.724.6-4%
fix5l2c_fastapi_included29.227.2-7%
fix5l3_fastapi_params31.629.7-6%
fix5l4_fastapi_model36.532.3-11%
fix6l1_starlette19.519.4-0%
fix6l1b_starlette_params21.522.3+4%
fix6l2_fastapi_dict24.623.8-3%
fix7l4_fastapi_model32.331.5-3%
fix7l2_fastapi_dict23.822.7-4%
fix8l1_starlette19.418.6-4%
fix8l1b_starlette_params22.321.6-3%
fix8l2_fastapi_dict22.722.7-0%
fix9l3_fastapi_params29.727.5-7%
fix9l4_fastapi_model31.530.4-4%
fix9l2_fastapi_dict22.722.0-3%
fix10l1_starlette17.918.3+2%
fix10l2_fastapi_dict21.821.3-2%
fix10l1c_starlette_50routes26.720.5-23%
fix10l5_fastapi_50routes36.325.2-31%
fix11l2c_fastapi_included28.726.8-7%
fix11l5_fastapi_50routes31.925.1-21%
fix11l5b_fastapi_50routes_included92.231.7-66%
fix12l2c_fastapi_included26.823.1-14%
fix12l5_fastapi_50routes25.125.1-0%
fix12l5b_fastapi_50routes_included31.729.8-6%
fix13l1_starlette18.618.0-3%
fix13l1b_starlette_params21.721.0-3%
fix13l2_fastapi_dict21.621.0-3%
fix14l2_fastapi_dict21.620.8-4%
fix14l4_fastapi_model30.628.0-8%
fix14l4_fastapi_model[granian]28.425.1-12%
fix15_16l1_starlette17.517.0-3%
fix15_16l2_fastapi_dict20.518.4-10%
fix15_16l3_fastapi_params26.125.3-3%
fix15_16l2_fastapi_dict[granian]10.79.2-13%
fix15l2_fastapi_dict20.522.6+11%
fix15l3_fastapi_params26.624.7-7%
fix15l2_fastapi_dict[granian]14.010.6-24%
fix17l2_fastapi_dict18.918.2-3%
fix17l3_fastapi_params25.424.9-2%
fix17l4_fastapi_model27.226.8-2%
fix17l2_fastapi_dict[granian]9.49.9+6%
fix18l2_fastapi_dict19.418.9-2%
fix18l3_fastapi_params26.125.1-4%
fix18l2_fastapi_dict[granian]10.512.6+20%
fix19l2_fastapi_dict18.418.0-2%
fix19l3_fastapi_params24.824.6-1%
fix19l2_fastapi_dict[granian]10.010.0+0%
fix20l1_starlette17.216.9-2%
fix20l2_fastapi_dict17.817.1-4%
fix20l5_fastapi_50routes21.821.6-1%
fix20l2_fastapi_dict[granian]8.78.9+2%
fix21l1_starlette17.516.8-4%
fix21l2b_fastapi_untyped20.720.6-1%
fix21l1_starlette[granian]10.29.4-9%
fix22l2_fastapi_dict17.217.0-1%
fix22l2b_fastapi_untyped20.318.0-11%
fix23l1b_starlette_params20.118.3-9%
fix23l3_fastapi_params24.822.4-10%
fix23l3_fastapi_params[granian]14.512.3-15%

Method

The ladder isolates layers: a raw ASGI app, a Starlette route, then notslowapi routes of increasing complexity, all under the same server flags. Load comes from oha; the native split between interpreter and kernel comes from sample(1); Python-level attribution from pyinstrument started in the ASGI lifespan. Surprising numbers are re-measured in alternating after, before, after runs before they count. Details in the docs.