⚡️ Speed up function in_async_context by 82% - #3955
Conversation
Here’s an optimized version that avoids the relatively expensive try/except path and `asyncio.get_running_loop()` call. Instead, it directly checks the loop using the faster `asyncio._get_running_loop()` "private" function, which is what `get_running_loop()` uses internally but without error handling overhead. This saves a function call, exception handling, and is safe as of Python 3.7+ (including 3.12). This is the fastest and least memory-heavy way in stock Python 3.12+. (Django does this in their async context checks as well: https://github.com/django/django/blob/main/django/utils/asyncio.py)
Reviewer's GuideThe PR replaces the slower try/except-based check in in_async_context with a direct call to asyncio._get_running_loop(), removing exception handling overhead and achieving an 82% speedup. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey @misrasaurabh1 - I've reviewed your changes and they look great!
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
Hi, thanks for contributing to Strawberry 🍓! We noticed that this PR is missing a So as soon as this PR is merged, a release will be made 🚀. Here's an example of Release type: patch
Description of the changes, ideally with some examples, if adding a new feature.Release type can be one of patch, minor or major. We use semver, so make sure to pick the appropriate type. If in doubt feel free to ask :) Here's the tweet text: |
There was a problem hiding this comment.
Greptile Summary
This PR optimizes the in_async_context() function in strawberry/utils/inspect.py to achieve an 82% performance improvement. The change replaces the original try/except pattern that used asyncio.get_running_loop() with a direct call to the private asyncio._get_running_loop() API.
The original implementation attempted to get the running event loop and caught RuntimeError exceptions when no loop was present, returning False in that case. The optimized version directly calls asyncio._get_running_loop() and checks if the result is None, which is functionally equivalent but avoids the overhead of exception handling and an additional function call.
This function is used throughout the Strawberry GraphQL codebase to determine whether code is executing in an asynchronous context, which is critical for proper handling of async resolvers, dataloaders, and other async operations. The performance improvement is particularly valuable since this utility is likely called frequently during GraphQL query execution.
The change follows a pattern used by Django's async utilities, suggesting this is a recognized optimization technique in the Python ecosystem for performance-sensitive async context detection.
Confidence score: 3/5
- This PR is moderately safe to merge but has some risk due to reliance on private APIs
- The score reflects the trade-off between significant performance gains and potential future compatibility issues with the private
_get_running_loop()API - Files that need more attention:
strawberry/utils/inspect.py- the implementation relies on a private asyncio API that could change in future Python versions
1 file reviewed, no comments
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #3955 +/- ##
==========================================
- Coverage 94.75% 94.41% -0.35%
==========================================
Files 520 528 +8
Lines 33947 34338 +391
Branches 1759 1803 +44
==========================================
+ Hits 32168 32419 +251
- Misses 1497 1627 +130
- Partials 282 292 +10 🚀 New features to boost your workflow:
|
CodSpeed Performance ReportMerging #3955 will not alter performanceComparing Summary
|
| return False | ||
| else: | ||
| return True | ||
| return asyncio._get_running_loop() is not None |
There was a problem hiding this comment.
There was a problem hiding this comment.
Hrmm interesting..
Yeah, apparently get_running_loop uses that and raises RuntimeError if it returns None
This change would prevent the if loop is None from there, which makes us have to except RuntimeError, which, at least until Python 3.11, had some overhead (3.11 introduced zero-cost exceptions)
My only concern here is to depend on a private member that could change anytime and cause issues without us knowing.
And checking the difference in times, before it was taking 0,00119 ms, with this change, it takes 0,000709 ms. Yes, it is a 67% performance improvement 🤣, but at the same time negligible, considering we don't call this that much
Sorry codeflash, I don't think this is a good change 😅
There was a problem hiding this comment.
yep the speedup was tiny but thought that this might be on the hot path, so thought might be good to merge. I plan to sync with @patrick91 about the types of optimizations that matters the most to your project
Saurabh's comments - This is sometimes preferred by other users because it looks cleaner. Plus since this seems to be used in important parts of the codebase, that's why i am opening it here.
📄 82% (0.82x) speedup for
in_async_contextinstrawberry/utils/inspect.py⏱️ Runtime :
25.6 microseconds→14.1 microseconds(best of32runs)📝 Explanation and details
Here’s an optimized version that avoids the relatively expensive try/except path and
asyncio.get_running_loop()call.Instead, it directly checks the loop using the faster
asyncio._get_running_loop()"private" function, which is whatget_running_loop()uses internally but without error handling overhead.This saves a function call, exception handling, and is safe as of Python 3.7+ (including 3.12).
This is the fastest and least memory-heavy way in stock Python 3.12+.
https://github.com/python/cpython/blob/b13a5df52fc854d1097e8b5419cb8802dc4059e0/Lib/asyncio/mixins.py#L13
✅ Correctness verification report:
⚙️ Existing Unit Tests and Runtime
codeflash_concolic_mk522t7p/tmpwowt6j5b/test_concolic_coverage.py::test_in_async_contexttest_inspect.py::test_in_async_context_synctest_pytest_inlinesnapshotdisable_teststest_dataloaders_py_teststest_inspect_py_teststest_deprecations_py__replay_test_0.py::test_strawberry_utils_inspect_in_async_contexttest_pytest_inlinesnapshotdisable_teststypestest_resolver_types_py_teststypestest_parent_type_future_anno__replay_test_0.py::test_strawberry_utils_inspect_in_async_context🌀 Generated Regression Tests and Runtime
⏪ Replay Tests and Runtime
codeflash_concolic_mk522t7p/tmpwowt6j5b/test_concolic_coverage.py::test_in_async_contexttest_inspect.py::test_in_async_context_synctest_pytest_inlinesnapshotdisable_teststest_dataloaders_py_teststest_inspect_py_teststest_deprecations_py__replay_test_0.py::test_strawberry_utils_inspect_in_async_contexttest_pytest_inlinesnapshotdisable_teststypestest_resolver_types_py_teststypestest_parent_type_future_anno__replay_test_0.py::test_strawberry_utils_inspect_in_async_contextTo edit these changes
git checkout codeflash/optimize-in_async_context-md4t2j8vand push.Summary by Sourcery
Enhancements: