New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimized @async_unsafe. #14911
Optimized @async_unsafe. #14911
Conversation
Before:
After:
Calling A small boost but it multiplies as |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmm. I never thought fetching from os.environ
would be the slow part, especially compared to try/except (which won't be zero-cost until .... 3.11?).
Nevertheless, I can replicate the difference.
In [1]: import os
In [2]: %timeit os.environ.get('DJANGO_ALLOW_ASYNC_UNSAFE') # pssst, it's not set...
1.06 µs ± 2.17 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Before:
[... prelude]
In [3]: %timeit ai()
1.79 µs ± 23.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
After:
[...prelude]
In [3]: %timeit ai()
454 ns ± 2.26 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
On that basis, the proposed change makes sense to me, but I'm not well-versed enough in the async story in general to say it's correct (beyond the tests passing), so I'll leave it without approval.
(eg: is there any semantic difference in asking for a loop regardless of the DJANGO_ALLOW_ASYNC_UNSAFE
var? is that side-effect free? I'm not qualified to say tbh)
Good question on the semantics. I think it's side effect free to ask for the running loop, but this is obscured a bit by some thread local and caching logic in https://github.com/python/cpython/blob/main/Modules/_asynciomodule.c |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree get_running_loop
should be side-effect free. (Ultimately it just looks up the pointer set in set_event_loop()
.)
I never thought fetching from os.environ would be the slow part...
Indeed 🧐
Are the import changes related to the optimisation? 🤔
They're another micro-optimization to require fewer attribute accesses. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, fine, seems sensible 👍
(I'll process this in a bit.)
2f17cf5
to
364d86c
Compare
Whilst we're micro-optimising could we use
Or would the use of the private function be too subject-to-change for us to use? |
Switched the order of the checks to reduce the overhead. Async unsafe methods are *normally* called syncrhonously, so we can avoid the overhead of checking the environment variable in the regular path.
364d86c
to
37d9ea5
Compare
@tim-mccurrach I think that would be a bridge too far. |
haha. Fair enough :) |
@carltongibson
(ai2 is the function as it stands in this PR). It's also (perhaps more importantly) more readable (IMO). But I agree, it may well be a bridge too far 🤷 |
@tim-mccurrach given that Django 4.1 will support 4 or 5 versions of Python using private APIs is not a good idea. |
👍 Yes, that seems sensible. |
Switched the order of the checks to reduce the overhead. Async unsafe methods are normally called syncrhonously, so we can avoid the overhead of checking the environment variable in the regular path.