Repository navigation
Conversation
``` Samples: 200000 - current=318.4 MB peak=318.4 MB Samples: 100000 - current=159.2 MB peak=159.2 MB Samples: 50000 - current=79.6 MB peak=79.6 MB Samples: 1000 - current=1.6 MB peak=1.6 MB Samples: 500 - current=0.8 MB peak=0.8 MB ```
``` Samples: 200000 - current=318.4 MB peak=318.4 MB Samples: 100000 - current=159.2 MB peak=159.2 MB Samples: 50000 - current=79.6 MB peak=79.6 MB Samples: 1000 - current=1.6 MB peak=1.6 MB Samples: 500 - current=0.8 MB peak=0.8 MB ```
… usage ``` Samples: 200000 - current=318.4 MB peak=318.4 MB Samples: 100000 - current=159.2 MB peak=159.2 MB Samples: 50000 - current=79.6 MB peak=79.6 MB Samples: 1000 - current=1.6 MB peak=1.6 MB Samples: 500 - current=0.8 MB peak=0.8 MB ```
``` Samples: 200000 - current=318.4 MB peak=318.4 MB Samples: 100000 - current=159.2 MB peak=159.2 MB Samples: 50000 - current=79.6 MB peak=79.6 MB Samples: 1000 - current=1.6 MB peak=1.6 MB Samples: 500 - current=0.8 MB peak=0.8 MB ```
…ory usage ``` Samples: 200000 - current=318.4 MB peak=318.4 MB Samples: 100000 - current=159.2 MB peak=159.2 MB Samples: 50000 - current=79.6 MB peak=79.6 MB Samples: 1000 - current=1.6 MB peak=1.6 MB Samples: 500 - current=0.8 MB peak=0.8 MB ```
``` Samples: 200000 - current=318.4 MB peak=318.4 MB Samples: 100000 - current=159.2 MB peak=159.2 MB Samples: 50000 - current=79.6 MB peak=79.6 MB Samples: 1000 - current=1.6 MB peak=1.6 MB Samples: 500 - current=0.8 MB peak=0.8 MB ```
…ry usage ``` Samples: 200000 - current=318.4 MB peak=318.4 MB Samples: 100000 - current=159.2 MB peak=159.2 MB Samples: 50000 - current=79.6 MB peak=79.6 MB Samples: 1000 - current=1.6 MB peak=1.6 MB Samples: 500 - current=0.8 MB peak=0.8 MB ```
…ry usage ``` Samples: 200000 - current=318.4 MB peak=318.4 MB Samples: 100000 - current=159.2 MB peak=159.2 MB Samples: 50000 - current=79.6 MB peak=79.6 MB Samples: 1000 - current=1.6 MB peak=1.6 MB Samples: 500 - current=0.8 MB peak=0.8 MB ```
``` Samples: 200000 - current=318.4 MB peak=318.4 MB Samples: 100000 - current=159.2 MB peak=159.2 MB Samples: 50000 - current=79.6 MB peak=79.6 MB Samples: 1000 - current=1.6 MB peak=1.6 MB Samples: 500 - current=0.8 MB peak=0.8 MB ```
``` Samples: 200000 - current=156.8 MB peak=156.8 MB Samples: 100000 - current=78.4 MB peak=78.4 MB Samples: 50000 - current=39.2 MB peak=39.2 MB Samples: 1000 - current=0.8 MB peak=0.8 MB Samples: 500 - current=0.4 MB peak=0.4 MB ```
| unwrapped = inspect.unwrap(cast(Callable[..., Any], _impartial(call))) | ||
| return unwrapped | ||
|
|
||
|
|
||
| def _impartial(func: Callable[..., Any]) -> Callable[..., Any]: | ||
| def _impartial(func: Callable[..., Any] | None) -> Callable[..., Any] | None: |
There was a problem hiding this comment.
[info] This is unrelated to my PR but mypy was complaining in the pre-commit check. The change looks safe and sensible
|
@YuriiMotov is there anything needed to move this one forward and get some feedback/review? Thanks |
|
@tiangolo , I'd be great if we can some feedback on this before the PR gets lost. Thanks |
|
@tiangolo could you please advise how to move this PR forwards, it's becoming more and more important as we fall behind FastAPI updates. Thanks! |
--------- Co-authored-by: Federico Jasson <[email protected]>
|
This pull request has a merge conflict that needs to be resolved. |
|
Thank you for the investigation in the original discussion and the work on this PR! 🙌 I used it plus some additional work and reports to base the work in this one #16049 It is now released in FastAPI 0.140.0. 🚀 I'll now close this one. Thank you! |
Context
In #14742, I reported a significant spike in memory usage when upgrading FastAPI from v0.120.4 to v0.121.x (and later versions).
My analysis traced the regression to this refactor, which replaced computed fields in
__post_init__withfunctools.cached_property.Root cause
The Python docs for
functools.cached_propertynote:While the worst of this was addressed in Python 3.12+ python/cpython#101815, there is still an overhead: accessing
__dict__on an instance forces creation of a full dictionary object rather than a compactPyDictValues, adding aPyDictObject(three fields) per instance. At scale, this adds up.The exact quote from the cpython issue:
Benchmarks
I wrote a script to measure memory usage across varying numbers of
Dependantinstances:Benchmark script
Before (current
master, Python 3.13.12):After (this PR):
Memory usage is reduced by approximately 50% across all cases.
Approach
The key enabler for the memory reduction is the use of
__slots__. However,__slots__is not compatible withfunctools.cached_property, so the caching strategy had to change.I considered reverting to the previous
__post_init__approach but decided against it for two reasons:Dependantinstantiation, even if never accessed.__post_init__harder to maintain.Instead, this PR introduces a different caching mechanism that preserves lazy evaluation while being compatible with
__slots__.