Live data from Hacker News

Local async executors and why they should be the default

maciej.codes

231–240 of 327 posts

Re: Local async executors and why they should be the default

#231
It's a frustrating area. As I've mentioned before, I'm writing a high-performance metaverse client in Rust, something which has many of the problems of both a web browser and a MMO. If you want to have a good looking metaverse, it takes a gamer PC multiple CPUs and a good GPU to deal with the content flood. (This is why Meta's Horizon looks so bad.) Now you have to use all that hardware effectively.

So what I'm writing uses threads. About 20 of them. They're doing different things at different priorities towards a coordinated goal. This is different from the two usual use cases - multiple servers running in the same address space, and parallel computation of array-type data.

Concurrency problems so far:

- Single-thread async is simple. Multi-thread async is complicated. Multi-thread async plus other threads not managed by the async system isn't used enough to be well supported.

- Rust is good at preventing race conditions, but it doesn't yet have a static deadlock analyzer. It needs one.

- Different threads at different priorities do work in both Linux and Windows, but not all that well. With enough low-priority compute-bound threads to keep all CPUs busy, high-priority threads do not get serviced in a timely manner. I was spoiled by previous work on QNX, which, being a true real-time operating system, takes thread priorities very seriously. On QNX, compute-bound background work has almost no effect on the high-priority stuff. Linux just doesn't work well at 100% CPU utilization. Unblocking a lock does not wake up a higher priority waiting thread immediately. This can delay high-priority threads unnecessarily.

- The WGPU crowd has spent a year getting their locking sorted out so that you can load content into GPU memory while the GPU is rendering something else. It's a standard feature of Vulkan graphics that you can do this, but it has to be supported at all levels above Vulkan too. For me, that's WGPU and Rend3. That stack is close enough to ready to test, but not ready for prime time yet.

- There's no way to cancel a pending HTTP request made with "ureq". "reqwest" supports that, but you have to bring in all the async and Tokio stuff, which means you now have multi-thread async plus other threads. This is only a problem for what I'm doing when the user closes the window, and the program needs to exit quickly and cleanly. I'm getting a 5-10 second stall at exit because of this.

- Crossbeam-channel is not "fair"; it's possible to starve out some requests. Parking-lot is fair, but doesn't have thread poisoning, which means that clean shutdowns after a panic are hard.

- Running under Wine with 100% CPU utilization with multiple threads results in futex congestion in Wine's memory allocation library, and performance drops by over 99%, with all CPUs stuck in spinlocks. The program is still running correctly, but at about 0.5 frames per second instead of 60 FPS. Bug reported and recognized by the Wine crew, but it's hard to fix. I can make this happen under gdb running my own code and see all those threads in the spinlocks, so I was able to file a good bug report. But I haven't generated a simple test case. It's a Wine-only problem; doesn't affect Microsoft Windows.

So that's life in a heavily threaded world.

Individual CPUs have not become much faster in over a decade. Everybody has been stuck at 3-4 GHz for a long time now. CPUs with many cores are widely available in everything from phones to game consoles. To use modern hardware effectively, you need threading.

Re: Local async executors and why they should be the default

#232

Earlier quoted context omitted.

> any of those languages (and plenty of others) will work fine Well, C won't. Any large C code needs a decade or two of bug-fixing before it is safe, and that only happens if the developers are competent and wiling to fix it.

What do you think this comment added to the discussion?

I am not your parent commenter but to me they added the nuance of "not all programming languages are good and we should stop pretending otherwise". And "we don't actually have as much choice as we think".

Re: Local async executors and why they should be the default

#233

Earlier quoted context omitted.

What do you think this comment added to the discussion?

I am not your parent commenter but to me they added the nuance of "not all programming languages are good and we should stop pretending otherwise". And "we don't actually have as much choice as we think".

Does "a fully negative outcome" sound like "all languages are good"?

Re: Local async executors and why they should be the default

#234

Earlier quoted context omitted.

I am not your parent commenter but to me they added the nuance of "not all programming languages are good and we should stop pretending otherwise". And "we don't actually have as much choice as we think".

Does "a fully negative outcome" sound like "all languages are good"?

Alright, you confused me. I was simply saying that this (your words):

> any of those languages (and plenty of others) will work fine.

...is not necessarily true. I've tried a good amount of languages in my life and their killer apps lose steam very fast when you try doing commercial code with them. For the rest of discussion, well, I didn't understand its point, so not participating there.

Re: Local async executors and why they should be the default

#235

Earlier quoted context omitted.

What do you think this comment added to the discussion?

I am not your parent commenter but to me they added the nuance of "not all programming languages are good and we should stop pretending otherwise". And "we don't actually have as much choice as we think".

Well, thanks. I was going for that first part (with the added detail that C is one of the languages being discussed on the context).

But I don't really agree with that second statement. People seem to not be aware of how much choice we have, and focus only on a few possibilities that are not even all of them good.

Re: Local async executors and why they should be the default

#236
post #142

Earlier quoted context omitted.

Async in its current incarnation did not come from node. Eg Haskell had async/await in 1999. https://softwareengineering.stackexchange.com/questions/3774... Node wasn't released until 10 years later.

And I have code still in production written in the "Perl Object Environment", one of several async frameworks for Perl, written before Node even existed. Also before Node existed, I had written code in the Twisted framework for Python. Node did not invent async by any means and I know it in the strongest possible sense, having used it before Node existed. But those libraries, in use for over a decade, did not make ev…

Please keep preaching. Every time I've investigated async I've just asked "why is it so unnecessarily complicated?", but I don't have enough background to know if I'm just missing something.

Re: Local async executors and why they should be the default

#237

Earlier quoted context omitted.

I am not your parent commenter but to me they added the nuance of "not all programming languages are good and we should stop pretending otherwise". And "we don't actually have as much choice as we think".

Well, thanks. I was going for that first part (with the added detail that C is one of the languages being discussed on the context). But I don't really agree with that second statement. People seem to not be aware of how much choice we have, and focus only on a few possibilities that are not even all of them good.

I suppose such a discussion could spiral into semantics so I'll only say this:

Yes, in theory we have a lot of choice but in practice you start a project and you need a good ORM / data-mapper, you need a good HTTP/Web/WebSockets framework, you need good DB migration solution, you need SSO for 5 services, and you need 20+ more things and when you start eliminating stuff there are barely any programming languages left. If even 20 are left at the end of such an evaluation that would be still pretty good (but in my practice you usually end up with maximum 7-8 viable choices).

You don't have to agree with me on this, or I with you -- it's my empirical observation which is prone to bias and filter bubbles (of course).

Re: Local async executors and why they should be the default

#238

Earlier quoted context omitted.

I'd like to think that I've worked on some "real projects" and I've never run into these issues tbh. It's just hard for me to wrap my mind around. Like I said, I get wanting more libraries (although I think they exist? A local pool exists and you can spawn tasks in it, using tokio) but I'm just not seeing a fundamental problem. I found the code you wrote very confusing so I can see why, when you come back to it, you…

> but I'm just not seeing a fundamental problem. Well, I mentioned it a few times in this thread: things are mixed in non-intuitive ways -- async/await, tokio, and various crates (including StreamExt which was not at all obvious and it took me a while to finally find mentioned in a few blog posts and GitHub gists). It just introduces friction and nowadays I am more averse to tech that requires more homework. Admitted…

> Would you write it differently and if so, how?

It's really hard to say because I don't fully know your use case. Maybe your way was the right way but I think I probably would have done something differently. Maybe a priority queue of jobs with a static pool of workers.

Re: Local async executors and why they should be the default

#239

Earlier quoted context omitted.

Yes, I suspect you're right. I do not like building giant monoliths with internal task systems. I prefer using async/await on the inside and microservices with rpcs or, ideally, streams for communication. I think perhaps people are trying to push too much into a single process?

> I think perhaps people are trying to push too much into a single process? I'll immediately spin that right back at you: people are trying to do too much via too many OS processes. ¯\_(ツ)_/¯ I like my single-OS-process apps that can distribute work internally (via the aforementioned Erlang "green threads"; they are not exactly that but close), or Rust's tokio, or Golang's goroutines and channels and WaitGroups. I su…

I don't think there's a meaningful difference between an Erlang actor and an operating system process other than the upfront memory allocation size.

The salient different to me is the supervisory structures. In Erlang you must compose them, in operating systems the kernel does that for you, or you move the responsibility into a distributed queue, etc.

As for scale, I don't really know how to reconcile "I'm fine just paying another 20 dollars" with "I'm worried about how my kernel will schedule my processes".

Re: Local async executors and why they should be the default

#240
post #63

Earlier quoted context omitted.

Look at the extremely popular async_trait macro crate: https://crates.io/crates/async-trait . What it does under the hood is to box everything. See https://github.com/dtolnay/async-trait#explanation You would think that you should be able to desugar async traits to something that does not need to box. But this is not possible with stable rust. There is an effort to make this possible. See for example this discussion…

Yes, it currently needs to box. But again, Sync is just a symptom of the real issues here (async fns have unpronouncable return types, opaque return types (impl trait) isn't supported in traits, and we don't have a good syntax for expressing constraints on opaque return types). Getting rid of Sync wouldn't fix any of those issues.

You don't get weird higher-ranked lifetime errors when you use non Sync futures with hand-rolled async traits.

I am not saying that this is the reason to use local futures. But if you have decided that local futures are the way to go anyway, it's a nice benefit.

Post reply on HN