Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save koirikivi/c58d30fce18ac1f0d65f06bfa4f93743 to your computer and use it in GitHub Desktop.
Save koirikivi/c58d30fce18ac1f0d65f06bfa4f93743 to your computer and use it in GitHub Desktop.
Benchmark of Django and Python's cached_property decorators (with IO and multithreading)
import pyperf
import time
from concurrent.futures import ThreadPoolExecutor
from django.utils.functional import cached_property as dj_cached_property
from functools import cached_property as py_cached_property
# Most web requests have some IO, like database access. Simulate with sleeping
SLEEP_TIME = 0.01
class TestClass:
@dj_cached_property
def dj_cached(self):
time.sleep(SLEEP_TIME)
return "Test"
@py_cached_property
def py_cached(self):
time.sleep(SLEEP_TIME)
return "Test"
def test_django():
t = TestClass()
t.dj_cached
t.dj_cached
t.dj_cached
t.dj_cached
t.dj_cached
t.dj_cached
t.dj_cached
t.dj_cached
t.dj_cached
t.dj_cached
def test_python():
t = TestClass()
t.py_cached
t.py_cached
t.py_cached
t.py_cached
t.py_cached
t.py_cached
t.py_cached
t.py_cached
t.py_cached
t.py_cached
def django_cache(loops):
range_it = range(loops)
t0 = pyperf.perf_counter()
for loop in range_it:
with ThreadPoolExecutor() as executor:
executor.submit(test_django)
executor.submit(test_django)
executor.submit(test_django)
executor.submit(test_django)
executor.submit(test_django)
executor.submit(test_django)
executor.submit(test_django)
executor.submit(test_django)
executor.submit(test_django)
executor.submit(test_django)
return pyperf.perf_counter() - t0
def python_cache(loops):
range_it = range(loops)
t0 = pyperf.perf_counter()
for loop in range_it:
with ThreadPoolExecutor() as executor:
executor.submit(test_python)
executor.submit(test_python)
executor.submit(test_python)
executor.submit(test_python)
executor.submit(test_python)
executor.submit(test_python)
executor.submit(test_python)
executor.submit(test_python)
executor.submit(test_python)
executor.submit(test_python)
return pyperf.perf_counter() - t0
runner = pyperf.Runner()
runner.bench_time_func("Django Cache", django_cache)
runner.bench_time_func("Python Cache", python_cache)
@koirikivi
Copy link
Author

koirikivi commented Mar 11, 2021

I modified the original benchmark to include IO and multithreading, since that's presumably the context in most Django applications.

After these modifications, the benchmark shows that the perfomance of stdlib's cached_property decorator is worse by an order of magnitude (at least on my laptop):

% python benchmark.py
.....................
Django Cache: Mean +- std dev: 12.8 ms +- 0.2 ms
.....................
Python Cache: Mean +- std dev: 113 ms +- 2 ms

This is because of unsuitable locking implementation in cached_property. See https://bugs.python.org/issue43468

@mtazzari
Copy link

mtazzari commented Aug 2, 2021

Very interesting benchmark! Any idea how does the implementation in the cached-property package performs?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment