4 ms·
Crossfire: High-performance lockless spsc/mpsc/mpmc channels for Rust
- AceJohnny2 11mo agoBecause I had to look it up: SPSC = Single Producer Single Consumer MPMC = Multiple Producer Multiple Consumer (I'll let you deduce the other abbreviations)
- endorphine 11mo agoS = Single M = Multi --- C = Consumer P = Producer
- wartywhoa23 11mo agoThey could have named that 1:1, 1:n, n:m, n:1, but deemed it too old and unfashionable.
- surajrmal 11mo agoWhich side is producer and which is consumer? I don't think fashion has anything to do with it.
- wartywhoa23 11mo agoHow do you read "X:Y": "X to Y" or "Y to X"? And how do you treat "to" - as direction from an origin to a destination or vice versa? As in "I send it to you".
- dietr1ch 11mo agoIf anyone reads it as to:from, they're wrong. We read left to right, so <producer>:<consumer> is the natural order.
- enricozb 11mo agoWhen reading this project's wiki [0], it mentions that Kanal (another channel implementation) uses an optimization that "makes [the] async API not cancellation-safe". I wonder if this is the same / related issue to the recent HN thread on "future lock" [1]. I hadn't heard of this cancellation safety issue prior to that other HN thread. [0]: https://github.com/frostyplanet/crossfire-rs/wiki#kanal https://github.com/frostyplanet/crossfire-rs/wiki#kanal [1]: https://news.ycombinator.com/item?id=45774086 https://news.ycombinator.com/item?id=45774086
- paholg 11mo agoFuturelock is not about cancellation safety (cancellation is actually one solution to futurelock), though the related issues that are linked in that post are.
- andrepd 11mo agoCancellation safety is another thing entirely, but one about which there's also an oxide RFD https://rfd.shared.oxide.computer/rfd/400 https://rfd.shared.oxide.computer/rfd/400
- truth_seeker 11mo agoFor Java/JVM : https://github.com/JCTools/JCTools https://github.com/JCTools/JCTools
- Hamuko 11mo agoI feel like testing if it'd be faster than tokio::sync::mpsc in my project, but in the context of a websocket client, the performance of just using tokio is already pretty good. Existing CPU usage is already negligible (under a minute of CPU time in >110 hours real time).
- efskap 11mo agoUsing stuff like this, does it make sense to use Rust in a golang-style where instead of async and its function colouring, you spawn coroutines and synchronize over channels?
- thrance 11mo agoIt doesn't matter if you use channels or mutexes to communicate between tasks, you still need your function to be async to spawn it as a coroutine. Your only choice is between coroutines (async tasks spawned on an executor) or regular OS threads. Channels work with both, the rule of thumb is to use async when your workload is IO-bound, and threads when it is compute-bound. Then, it's up to you whether you communicate by sharing memory or share memory by communicating.
- surajrmal 11mo agoIt does matter. Using channels makes control flow much more difficult to understand, but allows you to avoid wrapping everything in its own mutex (or RefCell) and local reasoning is easier to understand. There is also a difference in latency and cpu utilization, both of which still matter in io bound workloads. I honestly don't think it's one or the other but optimal usage is a mix of both depending on specifics of the use case. Channels are great for things that you want to be decoupled from each other but it needs to hit a certain level of abstraction/complexity before it's worth it. Even folks who write modern go try to avoid overusing channels. It's quite common to see go codebases with few or no channels.
- thrance 11mo agoI meant, when it comes to choosing between threads or async/await, it doesn't matter if you use channels or something else. Both can be used with them. My original comment wasn't very clear it seems. Of course it matters what synchronization primitives you choose, for the reasons you gave.
- nicoburns 11mo ago> Your only choice is between coroutines (async tasks spawned on an executor) or regular OS threads. Thats not true. There are stackgul coroutine libraries in Rust too. I believe there's one called "may". They are admittedly not that widely used, but they are available.
- reitzensteinm 11mo agoI'm a little nervous about the correctness of the memory orderings in this project, e.g. Two acquires back to back are unnecessary here. In general, fetch_sub and fetch_add should give enough guarantees for this file in Relaxed. https://github.com/frostyplanet/crossfire-rs/blob/master/src/channel.rs#L200 https://github.com/frostyplanet/crossfire-rs/blob/master/src... Congest is never written to with release, so the Acquire is never used to form a release chain: https://github.com/frostyplanet/crossfire-rs/blob/dd4a646ca9575c516164eab1c9e1dfb1406c29db/src/channel.rs#L213 https://github.com/frostyplanet/crossfire-rs/blob/dd4a646ca9... The queue appears to close the channel twice (once per rx/tx), which is discordant with the apparent care taken with the fencing. https://github.com/frostyplanet/crossfire-rs/blob/dd4a646ca9575c516164eab1c9e1dfb1406c29db/src/channel.rs#L231 https://github.com/frostyplanet/crossfire-rs/blob/dd4a646ca9... The author also suggests an incorrect optimization to Tokio here which suggests a lack of understanding of what the specific guarantees given are: https://github.com/tokio-rs/tokio/pull/7622 https://github.com/tokio-rs/tokio/pull/7622 The tests do not appear to simulate the queue in Loom, which would be a very, very good idea. This stuff is hard. I almost certainly made a mistake in what I've written above (edit: I did!). In practice, the queue is probably fine to use, but I wouldn't be shocked if there's a heisenbug lurking in this codebase that manifests something like: it all works fine now, but in the next LLVM version an optimization pass is added which breaks it on ARM in release mode, and after that the queue yields duplicate values in a busy loop every few million reads which is only triggered on Graviton processors. Or something. Like I said, this stuff is hard. I wrote a very detailed simulator for the Rust/C++ memory model, have implemented dozens of lockless algorithms, and I still make a mistake every time I go to write code. You need to simulate it with something like Loom to have any hope of a robust implementation. For anyone interested in learning about Rust's memory model, I can't recommend enough Rust Atomics and Locks: https://marabos.nl/atomics/ https://marabos.nl/atomics/
- embedding-shape 11mo ago> The tests do not appear to simulate the queue in Loom, which would be a very, very good idea. Loom is apparently this: https://github.com/tokio-rs/loom https://github.com/tokio-rs/loom I've used tokio a bit in the past, but wasn't aware of that tool at all, looks really useful and probably I'm not alone in never hearing about it before. Any tips&tricks or gotchas with it one should know beforehand?
- tontinton 11mo agoCan I select multiple receivers concurrently, similar to a select in Linux?