7 ms·
After 2 years of using asyncio in production, I recommend to avoid it if you can. With async programming, you take the complexity of concurrent programming, whi
by kissgyorgy 4y ago
After 2 years of using asyncio in production, I recommend to avoid it if you can. With async programming, you take the complexity of concurrent programming, which is way harder than you can imagine.
Also nobody mentions this for some reason, but asyncio doesn't make your programs faster, in fact it makes everything 100x SLOWER (we measured it multiple times, compared the same thing to the sync version), but makes your program concurrent (NOT parallel), where the tradeoff is you use more CPU, maybe less memory, and can saturate your bandwidth better. Never do anything CPU-bound in an async loop!
- acedTrex 4y agoThis is an insane take, if you are doing async you already HAVE a need for concurrent programming. Async just makes that simpler to read and write.
- throwawaymaths 4y ago> Async just makes that simpler to read and write. Really? I definitely had a huge struggle reading async python, because generators and yields break the concept of "what is a function" and struggled writing async python because of function coloring.
- andrewstuart 4y agoDay to day Python async should be using generators and yields.
- nine_k 4y agoGenerators are great for predictable concurrency. Things like asynchronous I/O, with timeouts and stuff, require more support on the language and stdlib level.
- dekhn 4y agoI am really not a fan of generators and yields for the same reason. But over time I've come to see it as ever-so-slightly more elegant syntax sugar for a class that maintains internal state to sequentially return specific values in response to a series of calls to a function.
- throwawaymaths 4y agoThe least they could have done is sugared it to something that isn't "def", like "gen" or "defgen"
- jborean93 4y agoUnless I'm misunderstanding your comment they do, it's `async def` to denote a coroutine.
- dekhn 4y agoThey're referring to generator functions. A generator function typically open a resource and then use a for loop to read (for example) a line at a time and yield it. once you yield, control returns to the caller (like a return) but subsequent calls to next on the returned expression pick back up at the yield point. So function calls maintain state-behind-the-scenes. It's not just a simple call-and-return model anymore. To me this violates a core principle: the principle of least surprise. It completely changes how I have to reason about function execution, especially if there are conditionals and different yields.
- nicwolff 4y agoIt's the buried `yield` in a loop somewhere, magically changing your function into a generator, that can be confusing – it's on the original coder to have the docstring say `"""Generator yielding records from DB."""` or something.
- kissgyorgy 4y agoI'm pretty sure a lot of people don't understand the tradeoffs and don't really need concurrent code. There are really only a couple of use-cases where it's really handy: for example an API gateway, where you only get and send HTTP requests. Other than that, I don't see how it worth the insane complexity (compared to sync code). You can write web servers in a sync manner; Flask, Django is way better if you only need an API, choosing FastAPI for a simple REST API is a huge mistake.
- thunky 4y ago> FastAPI for a simple REST API is a huge mistake FastAPI can be sync or async, although sync will have "a small performance penalty" https://github.com/tiangolo/fastapi/issues/260#issuecomment-495945630 https://github.com/tiangolo/fastapi/issues/260#issuecomment-...
- walls 4y agoWhat kind of APIs are you writing that don’t involve db lookups or calls to other services?
- samwillis 4y agoAsyncio does not make your db calls or io faster, it does make your code more complex. The only thing async io gives you is scalability of many MANY concurrent io operations. You have to either be pushing seriously large traffic or doing a lot of long running websocket/sse/long polling type requests. The only other use case is lots of truly concurrent io within one request/response cycle. But again that is unusual, most apis have low single digit db queries that are usually dependent on one another removing any advantage of async.
- fprct 4y ago> The only other use case is lots of truly concurrent io within one request/response cycle. But again that is unusual, most apis have low single digit db queries that are usually dependent on one another removing any advantage of async. Why just "within one cycle"? What about situation where you spend 1s in single db query and are 100ms cpu bound during request handling?
- calpaterson 4y agoUnfortunately the majority of people who use asyncio in the real world do not have a need for concurrenct programming. They are drawn to it because they believe it is faster, which, generally, it is not. That isn't to say there aren't reasonable usecases - but most people using asyncio are writing webapps which spend a lot of time in the CPU generating or outputting JSON/GraphQL/HTML.
- nine_k 4y agoI think this may be influenced by Node. With JS and Node, async is cheap and ergonomic, so it's very widely used, even if the latency gains are marginal. With Python and asyncio, neither assumption holds, but the idea of shaving off some latency persists: if we rewrite everything using asyncio, our DB accesses can be parallelized, yay! This may or may not be a net win in latency; reworking your DB access patterns may gain you more.
- kissgyorgy 4y ago> reworking your DB access patterns may gain you more. Nope: https://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/ https://techspot.zzzeek.org/2015/02/15/asynchronous-python-a...
- deleted 4y ago[deleted]
- nine_k 4y agoI don't say that asyncio gives you nothing! Go for it with your DB access if you have already gone async in your design. But often the lower-hanging fruit is doing more joins in the DB, fetching fewer columns, and writing your query more thoughtfully. Async only helps if you can do something while waiting for the DB to complete your query. A common anti-pattern, exacerbated by ORMs, is doing a bunch of small queries while passing bits of data between them in Python; moving that to asyncio won't help much, if any.
- 4y ago
- qbasic_forever 4y agoAsync python by design is not concurrent, nothing is executing at the same time on different cores/threads. There's one worker loop and one task running on it at a time.
- _bohm 4y agoI think you mean it is not parallel. Cooperative multitasking is absolutely a type of concurrency
- srik 4y agoConcurrency is not Parallelism. You should watch this quintessential talk about the difference - https://www.youtube.com/watch?v=oV9rvDllKEg https://www.youtube.com/watch?v=oV9rvDllKEg
- dekhn 4y agoThis is Rob's perspective, but it's not universally shared. In my mind, all concurrency is a form of parallelism (parallel tasks, but not parallel threads) while not all parallelism is concurrency. I have frequently solved concurrency problems with thread pools, rather than using non-blocking IO, because the programming paradigm is a lot simpler.
- BerislavLopac 4y agoYou got the the wrong way round: all parallelism is a form of concurrency. In Python, thread pools and async work mostly the same in practice, except that threads have a bigger overhead. So they can be used to solve the same problems.
- deleted 4y ago[deleted]
- jpicard 4y agoasyncio gets a lot of hype, but I prefer concurrent.futures. It feels more simple and general since I don't have to make everything awaitable.
- qbasic_forever 4y agoThis isn't a problem specific to python though. You hint at it, async programming (single thread worker loop) is not the right way to deal with CPU-bound tasks. NodeJS or any other async language would have the same problems. You want multiprocessing or multithreading, where work is distributed to different CPU cores at the same time. Python gives you the ability to use any of those three paradigms. Choose wisely.
- whakim 4y agoThis just sounds like you fundamentally misunderstood how asyncio works and applied it to a use-case which it wasn't suited to. Why would you use async if you're doing a bunch of CPU-bound work?
- number6 4y agoAsyncio is not ment for CPU Bound Task but for IO Bound Tasks. You should have used multiprocessing. The problem you describe is exactly what asyncio is used for: saturate your bandwidth better. maybe the right aproach would have been a Threadpool? Plus you don't have to refractor the task. Just let it run sync, but you can make it also async
- JosephRedfern 4y agoDepending on the specifics, a threadpool proabably wouldn't help with a CPU bound task due to GIL limitations. `multiprocess.Pool` might (though process pools aren' without their overheads)
- matsemann 4y agoIn other languages the async paradigm work for multiple kind of workflows, not just heavily IO bound ones.
- number6 4y agoCan you give me an example which languages do this?
- matsemann 4y agoIn Kotlin for instance you can execute your coroutines with different "dispatchers", which have different behaviors. You can for instance use a dispatcher with multiple threads in a threadpool, run it in the main thread, a specific thread, in threads with low/high priority etc. Basically allowing you to write code using the async/coroutine paradigm, but then control its execution from the outside. So I've also used coroutines in Kotlin for CPU bound tasks with a nice speedup, where multiple sub-tasks get executed in parallel and then gathered when needed to produce new tasks etc., in a granular way that would be very intrusive doing with threadpools. Here I basically took the existing coroutine code and slapped some more threads on it. With that said, I'm not entirely sold on Kotlin's way either. Both Kotlin and Python have the "what color is your function" problem.
- doliveira 4y agoThe clue is in the name: asyncIO is for, well, I/O bound stuff...
- powersnail 4y agoAsync in general (not just python) doesn’t speed up any computation. The value is allowing you to do something else while waiting for IO to finish. If you are constantly doing IO, async is a good tool. Otherwise, it won’t help.
- harpiaharpyja 4y agoI've experienced something similar using asyncio as well, but I think you're wrong to blame the inherent complexity of concurrent programming. I've used curio as an alternative concurrent programming engine for a med-large project (and many small ones). In comparison to asyncio, it's a joy to use.
- kissgyorgy 4y agoWhat I'm saying if you don't need concurrency, don't trade off simplicity. For REST APIs and websites, probably not needed.
- jerrygenser 4y agoRest APIs and websites are IO bound. Your python program is there purely coordinating between disk io, database (network) io, calls to other services (io). You can be choreographing thousands of requests concurrently and still barely doing any cpu. The overhead to do a similar service using threadpools is much higher and the reason asyncio exists.
- wiredfool 4y agoMy opinion is that async/evented is a (useful) performance hack. You wouldn't do it that way for any reason other than you can get better performance than way than by using threading or other pre-emptive multitasking. It's not a better developer experience than writing straight forward code where threads are linear and interruptions happen transparently to the flow of the code. There are foot guns everywhere, with long running tasks that don't yield or places where there are hidden blocking actions. It reminds me a bit of the old system 6 mac cooperative multitasking. It was fine, and significantly faster because your program would only yield when you let it do it, so critical sections coule be guaranteed to not context shift. However, you could bring the entire machine to a halt by holding down the mouse button, as eventually an event handler would get stuck waiting for mouse up. Pre-emptive multitasking was a huge step forward -- it made things a bit slower on average, but the tail latency was greatly improved, because all the processes were guaranteed at least some slice of the machine.
- samwillis 4y ago"Performance" is too wide a term, asyncio does not improve performance in the most widely understood interoperation, "speed". Talking about performance benefits of asyncio causes people to misunderstand where it is best used. "Scalability" is a better word to use when talking about asyncio. Along with describing complex concurrent programming such as for a GUI, where the added syntactical complexity if outweighed by the reduced boilerplate of traditional GUI programming.
- wiredfool 4y agoYeah, talking speed in the manner of C1M sorts of things. Server stuff - where python tends to be rather than GUI side. It's demonstrably faster for IO to not be context switching with threads -- but if threads were equal weight to events, I don't the event programming style being more programmer productive than the threaded style. I can see where events are useful in a GUI context, but UI Thread + thread pool dispatch still works well, if your framework supports it. Just this week, I got to the bottom of a performance issue in django because the developers were using async, and then doing eleventy billion db queries to import a big csv, thereby blocking all other requests. One of the questions I ask on our programming interviews is "how would you make this (cpu bound) thing go faster" -- Async is definitely a low quality answer to that, when things like indexing or hash lookup vs array scan are still on the table.
- m3047 4y ago> 100x SLOWER (we measured it multiple times, compared the same thing to the sync version) I'd need to know the methodology. Asyncio is "gated", meaning that coroutines execute in tranches. When you queue up a bunch of coroutines from another coroutine, they don't execute right away instead they go in a list. Then when the current tranche completes the next one starts. There's some tidying up which occurs with every tranche. As an (only one) example of "measuring the wrong thing", if you queue up a single task 1000 times which takes as its argument a unique timer instance it matters whether you queue them up one at a time as they finish running or queue them all up at once. #!/usr/bin/python3 # (c) 2022 Fred Morris, Tacoma WA USA. Apache 2.0 license. """Illustrating the tranche effect in asyncio.""" from time import time import asyncio N = 1000 class TimerInstance(object): def __init__(self, accumulator): self.accumulator = accumulator self.start = time() def stop(self): self.accumulator.cumulative += time() - self.start return class Timer(object): def __init__(self): self.cumulative = 0.0 return def timer(self): return TimerInstance(self) async def a_task(timer): timer.stop() return def main(): loop = asyncio.get_event_loop() timing = Timer() overall = time() for i in range(N): loop.run_until_complete( loop.create_task( a_task(timing.timer()) ) ) print('Sequential: {} Overall: {}'.format(timing.cumulative, time() - overall)) timing = Timer() overall = time() for i in range(N): loop.create_task( a_task(timing.timer()) ) loop.stop() loop.run_forever() print('Tranche: {} Overall: {}'.format(timing.cumulative, time() - overall)) if __name__ == '__main__': main() # ./tranche-demo.py Sequential: 0.02229022979736328 Overall: 0.04146838188171387 Tranche: 6.084041595458984 Overall: 0.012317180633544922
- dec0dedab0de 4y agoIt used to get brought up every time asyncio was mentioned on here. I either use multiprocessing, or celery. using async/await looks ugly, doesn't solve most problems I would want it to, and feels unstable. Maybe once development around it settles down I'll revisit, but I can't imagine wanting to use it in it's current form.
- jacob019 4y agoAfter using gevent for about a decade I have started using asyncio for new projects, just because it's in the standard library and has the official async blessing of the Python gods. Indeed it is way harder. I'm always coming up against little gotchas that take time to debug and fix. Part of me enjoys the challenge of learning something new and solving little puzzles. It's getting easier, especially as I build up a collection of in-house async libraries for various things. As for performance, it's not too bad for mostly io bound tasks, which is why one uses async in the first place. Some tight loop benchmarks for message passing with other processes show it to be about half the speed of gevent in my case, which is fine. It's nice to be able to deploy async microservices without installing gevent, and there's a certain value to the discipline that it imposes. I like how I am able to bring non-async code into the async world using threading. I imagine the performance would improve quite a bit with pypy, perhaps exceeding that of gevent. Gevent makes it so damn easy, I've been spoiled. I was disappointed when asyncio came out, as I would have preferred the ecosystem moved in the gevent direction instead; but I'm coming around. It's super annoying how the python ecosystem has been bifurcated with asyncio. You really have to choose one way or another at the beginning of a project and stick with it. And yeah, async programming (in Python) isn't really for CPU bound stuff. You might benefit from multiprocessing and just use asyncio to coordinate, which is what it excels at. PyPy can really help with CPU bound stuff too, if the code is mostly pure Python.
- ddorian43 4y agoJava with Project Loom will be like gevent.
- v3ss0n 4y agoYou are doing it wrong then, asyncio is never designed for and not useful for CPU bound tasks. Use multiprocessing for CPU bound tasks. In our system before asyncio, sqlalchemy commits and httpx requests are blocking The API calls and it takes 21 seconds every time, now only 1.2 seconds. We had optimized by making every IO (db.commit(),httpx.post) to run inside asyncio.create_task. So they are operating outside of the request without slowing it down. We had used aync since 2014 ( twisted, tornado), and officially having async await in python starting 3.7 was the best thing happened to python.