Performance and Benchmarks¶
Results¶
Sync¶
Why so fast?
We utilize
msgspec.json.decode()andmsgspec.json.encode()to parse json, it is the fastest json parsing tool in Python landWe can support
msgspec.Structmodels, which are faster than pydanticWe compile some hot paths of the framework with mypyc to C code, while keeping fallback Python code in-place
We provide
django.urls.path()drop-in Routing replacement which is x51 times as fast as the default oneWe validate data smartly: we prepare models for validation in advance, so no runtime magic ever happens
We have special “production mode” with fewer checks, so we can have the best of two worlds: strict development workflow and fast runtime for real users
We also support PyPy, which can be several orders of magnitude faster that CPython.
Async¶
While fastapi is faster at the moment, we have several ideas
to optimize django-modern-rest even further,
so it can be on par (or even faster!)
with the fastest python web frameworks in existence.
While keeping 100% of compatibility with the older libs and tools.
mypyc compilation¶
We compile several parts of the framework with mypyc.
What does it mean?
We still write all code in good old pure Python
We add annotations to all code anyway
We then compile some modules from annotated Python to C with
mypycIt starts to work from 4 to 10 times faster for free
We ship pre-built wheels with the built binaries for multiple platforms
For platforms with binary deps, it is possible to disable binary parts and switch to Python code with
DMR_USE_COMPILEDset to0, for example when you need to debug somethingFor unsupported platforms /
sdistinstalls, we still ship pure Python code
What do we compile?
We only compile code that makes sense to be compiled. Criteria:
Does not have IO
Does not have a lot of compiled / uncompiled context switches. For example, compiled code that frequently calls Python code will most like be slower in the result
Is executed on the hot path. Not in import time, but in request’s handing phase
Is rather simple and does not have a lot of magic, otherwise - compilation will not have much effect
Does not have complex typing
Have no external dependencies
It shows a decent speedup in a micro-benchmark
Compiled features¶
Acceptheader parsing and content negotiation
Supported platforms¶
Wheels are available for:
Linux (via both the manylinux and musllinux standards)
macOS (both x86_64 and aarch64)
64-bit versions of Windows (AMD only, ARM is not supported at the moment)
We support wheels for all supported Python versions.
django-modern-rest is a pure Python project, so any “unsupported” platforms
will just fall back to the slower pure Python wheel available on PyPI.
If you want to force
sdist
build, pass --no-binary django-modern-rest,
however this might only be useful in very strange cases.
Building your own wheels from source¶
Run make wheel to run the compilation.
Technical details¶
See source code for our benchmark suite.
Benchmarks!¶
Important Notice¶
All benchmarks are always synthetic. Benchmarks do not test real performance, they test ideas of performance.
Environment¶
Python:
3.13.7, no JIT, no free-threadingOS: MacOS 15.6.1
Device: Apple M2 Pro, mid 2023, 16 Gb of RAM
Assumptions¶
We test only the API part, because
django-modern-restdoes not include any database features, we want to make sure that the part that we are covering is measured, not something elseWe test the minimal possible app
We use the same exact data for all apps
We test production-like setups with
DEBUG=FalseWe test both sync (
gunicorn) and async (uvicorn) version if the requested mode is supported (yes, DRF, we are looking at you)But, we don’t compare sync to async and vice versa, because they have different logic, different deploy strategies, etc
Results¶
Async¶
framework |
is_async |
rps |
tpr |
|---|---|---|---|
fastapi |
True |
10854.6 |
1.843 |
dmr |
True |
7026.27 |
2.846 |
ninja |
True |
4359.12 |
4.588 |
Sync¶
framework |
is_async |
rps |
tpr |
|---|---|---|---|
dmr |
False |
5774.94 |
3.463 |
ninja |
False |
3888.13 |
5.144 |
drf |
False |
3024.24 |
6.613 |
Running the script:¶
Pre-requirements:
Run from benchmarks/ directory:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
just bench::bench
Manual debug¶
Single request:
curl -X POST \
'http://127.0.0.1:8000/async/user/?per_page=1&count=2&page=3&filter=abc' \
-d @payload.json \
-H 'Content-Type: application/json' \
-H 'X-API-Token: some-token-example' \
-H 'X-Request-Origin: some-origin'
Manual bench:
ab -c 20 -n 1000 -l -p payload.json \
-H 'X-API-Token: some-token-example' \
-H 'X-Request-Origin: some-origin' \
-T 'application/json' \
'http://127.0.0.1:8000/async/user/?per_page=1&count=2&page=3&filter=abc'
Feature benchmarks¶
We also benchmark several our features that can be used independently.
We run tests in ./tests/ using https://github.com/CodSpeedHQ/pytest-codspeed
and upload results to https://codspeed.io/wemake-services/django-modern-rest
See codspeed.yml workflow.
Run just benchmarks from the root dir to run them.