Best channel library for low-traffic scenarios

Hi,

in my use case, i have lots of channels across several processes, but most of them are low-traffic, i.e. the time between sends are usually somewhere between tens of milliseconds to several hours (possibly only once or never during the entire runtime of the program).

I have noticed some processes have a high CPU usage even when they don't really have anything to do but wait. I looked at it with firefox profiler and found lots of threads spending a great amount of time in crossbeam code. At first I thought this was some kind of profiling fluke or me using the tools wrong in some way, but it turns out apparently crossbeam makes heavy use of spinlocks, which of course is less than ideal for my use case where there's a large number of channels and most of them have no messages most of the time.

Without swapping out all channel implementations in my codebase against every available channels library and benchmarking that, can anyone recommend a channel implementation that would work better for my use case?

Bonus points if there's some kind of select! macro that can race such a low-traffic channel for a higher-traffic one for which i may continue to use crossbeam.

I'm not sure what you mean by processes here. AFAIK crossbeam channels can only be used within the same address space, not for IPC.

Does this mean your code is compiled to WASM and runs in the browser?

1 Like

I'm not sure what you mean by processes here

Sorry, that was unclear. I have several processes, each of which is multithreaded and uses several crossbeam channels internally to communicate between its threads. That's why I have a large number of channels, but I imagine the scenario would be similar if all of these channels were within the same process.

Does this mean your code is compiled to WASM and runs in the browser?

No, I used linux's perf tool and just view the results in Firefox profiler as recommended in the Rust Performance Book.

I'm somewhat skeptical here, but you might consider trying the chan crate. It's end-of-lifed and no longer maintained, but its implementation is, what I'd call, simplistic. (It does no spin locking.) So you might see if it's approach fixes things for you. If it does, then maybe there's something that crossbeam can improve.

1 Like

When using blocking operations, like crossbeam does, it’s generally not possible to mix different operations (unless they have common ground like all being flavors of "wait for this file handle to have data ready" and have been written to work together with some IO multiplexer), so you’re not going to find a select! that works with crossbeam channels other than crossbeam’s select!.

However, async Rust is completely different. One of its advantages is the intrinsic ability to do “heterogenous select”, which lets an async task wait for any combination of async operations. And well-behaved async functions always suspend until there is work to do, rather than spinning.

If you would like to try using async purely for channels, this doesn’t require you to rewrite your code; you can just locally use

pollster::block_on(select! {
    ...
})

pollster is a minimal async executor, select! can be gotten from futures-util (though its version is arguably less ergonomic without good reason), and if you want a MPSC/MPMC channel like crossbeam that supports async (and blocking), check out flume — though I should note that I personally chose flume for its features, and have not evaluated its performance. (It does, however, default to not using spinlocks, so may be of interest.)


To be clear, I am not saying that you should use async to improve your application; rather, that if you want heterogenous select, you almost certainly must use async to get it. However, if your application has “lots of channels”, then it might well benefit from async by allowing fewer threads to be used to receive those channels.

3 Likes