Performance and Benchmarks

Results

Sync

Why so fast?

  • We utilize msgspec.json.decode() and msgspec.json.encode() to parse json, it is the fastest json parsing tool in Python land

  • We can support msgspec.Struct models, which are faster than pydantic

  • We 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 one

  • We 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?

  1. We still write all code in good old pure Python

  2. We add annotations to all code anyway

  3. We then compile some modules from annotated Python to C with mypyc

  4. It starts to work from 4 to 10 times faster for free

  5. We ship pre-built wheels with the built binaries for multiple platforms

  6. For platforms with binary deps, it is possible to disable binary parts and switch to Python code with DMR_USE_COMPILED set to 0, for example when you need to debug something

  7. For unsupported platforms / sdist installs, we still ship pure Python code

What do we compile?

We only compile code that makes sense to be compiled. Criteria:

  1. Does not have IO

  2. 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

  3. Is executed on the hot path. Not in import time, but in request’s handing phase

  4. Is rather simple and does not have a lot of magic, otherwise - compilation will not have much effect

  5. Does not have complex typing

  6. Have no external dependencies

  7. It shows a decent speedup in a micro-benchmark

Compiled features

  • Accept header 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!

CodSpeed

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-threading

  • OS: MacOS 15.6.1

  • Device: Apple M2 Pro, mid 2023, 16 Gb of RAM

Assumptions

  • We test only the API part, because django-modern-rest does not include any database features, we want to make sure that the part that we are covering is measured, not something else

  • We test the minimal possible app

  • We use the same exact data for all apps

  • We test production-like setups with DEBUG=False

  • We 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.