Performance overhead of using anyio #543
Replies: 9 comments 44 replies
-
The most common use case is a I've been contemplating on providing a method of permanently selecting a back-end via an environment variable, at the obvious cost of compatibility when an app uses two back-ends, for whatever reason. On import, this would replace all the functions with their back-end specific versions, without going through My long term plan is to get rid of |
Beta Was this translation helpful? Give feedback.
-
I'd prefer something like
(or
Good luck. You'll certainly need it. |
Beta Was this translation helpful? Give feedback.
-
If you want to use asyncio and trio in the same program, you need to use a Trio mainloop and supplement that with Method of last resort, frankly, as |
Beta Was this translation helpful? Give feedback.
-
This becomes a problem if anything else has imported
Don't I know that 😏 |
Beta Was this translation helpful? Give feedback.
-
Well, that's why I wrote to put it at the top of the main program. There's been nobody yet to import anything from. Besides, the environment variable needs to come from somewhere. You wouldn't want to require a separate wrapper just because your code uses an asyncio-only library and thus requires that backend; on the other hand, if your code sets that envvar before it imports anything (other than |
Beta Was this translation helpful? Give feedback.
-
I'm glad to hear there is interest in this! The implementation specific selector function by an env-var would be the quickest and easiest improvement, but it does not eliminate the overhead completely. Freezing the backend in the selector improved the performance from 71% to 92%, this is somewhat similar to what would be expected of a contant-function selected by an env-var: _froze = None
def get_asynclib(asynclib_name: Optional[str] = None) -> Any:
global _froze
if _froze is None:
if asynclib_name is None:
asynclib_name = sniffio.current_async_library()
modulename = "anyio._backends._" + asynclib_name
try:
_froze = sys.modules[modulename]
except KeyError:
_froze = import_module(modulename)
return _froze However, adapting my benchmark code a little can demonstrate that using the specific backend directly has zero overhead: import anyio._backends._asyncio as any_asyncio
async def _busy_sleep_anyio(cl):
while True:
await any_asyncio.sleep(0)
cl[0] += 1 I would suggest that providing zero-overhead in general would be ideal. This could be possible if there was a new canonical import path for the backend like |
Beta Was this translation helpful? Give feedback.
-
Would it be possible to make a light version of anyio, one that merely tries to import trio and falls back to asyncio, and just copies implementations straight from a backend? |
Beta Was this translation helpful? Give feedback.
-
On 07.07.24 23:08, Raphael Krupinski wrote:
When using |trio-asyncio| anyio should run with either backend, no?
It doesn't know whether there's a Trio runner in control of the asyncio
"loop", and testing for that situation would add even more overhead.
…--
-- mit freundlichen Grüßen
--
-- Matthias Urlichs
|
Beta Was this translation helpful? Give feedback.
-
Hi! I'm new to this discussion, so apologies in advance for my likely confusion. I understand that while using trio, asyncio could also be indirectly in use, so we cannot avoid the per-call backend dispatch. Is this also possible when using asyncio? Could trio be also in use? |
Beta Was this translation helpful? Give feedback.
-
Hi!
Preface
Firstly, I'd like to clarify that I've been developing asyncio applications and have not used trio/curio. I have not found the need to really consider using them so far, but different folks prefer one over the other for whatever reason, and that's fine. As far as I understand anyio can be used to make them work together in some capacity in the same program if two different dependencies use one or the other for example. For this reason some libraries have started to just migrate to anyio that have previously been asyncio-only.
Problem
My problem with libraries depending on anyio is that the performance overhead of anyio can be significant while there is absolutely zero benefit for it, because only asncyio is ever utilized in my setups.
I've looked over the libraries that migrated over to anyio and anyio itself. I've quickly concluded that there are many great utilities that are available in anyio, so asking for an anyio-free version (usually by revert) is not even worth trying. After they've migrated to anyio, many custom utilities were dropped that would need to be re-introduced to the tree with tests, that most won't even consider, because anyio provides them. There is no way to convince said libraries to remove anyio for asyncio-only environments.
Measurements
My main gripe is with the
get_asynclib
private function. It selects the "current" asynchronous backend that is in use, so it is necessarily used all the time. The following code focuses on the difference of using native asyncio code and this backend-selecing wrapper:The snippet above measures how many times the backend's sleep can be invoked with the native asyncio and the anyio-wrapped asyncio backend. On my machine the output is:
So compared to natively calling functions there can be an up to 29% performance penalty.
Discussion
I'd like to initiate a discussion about how anyio selects the backend and how it could be improved.
Beta Was this translation helpful? Give feedback.
All reactions