6 ms·
Disruptor-rs: better latency and throughput than crossbeam
- deleted 2y ago[deleted]
- bengikboy 2y ago[flagged]
- bluejekyll 2y agoThis is really cool to see. Is anyone potentially working on an integration with this an Tokio to bring these performance benefits to the async ecosystem? Or maybe I should ask first, would it make sense to look at this as a foundational library for the multi-thread async frameworks in Rust?
- nXqd 2y agoTokio focuses on being high throughput as default, since they mostly use yield_now backoff strategy. It should work with most application. For latency sensitive application, it tends to have different purpose which mainly trade off CPU and RAM usage for higher low latency ( first ) and throughput later.
- nicholassm83 2y agoI agree, the disruptor is more about low latency. And the cost is very high: a 100% utilized core. This is a great trade-off if you can make money by being faster such as in e-trading.
- lordnacho 2y agoSuppose I have trading system built on Tokio. How would I go about using this instead? What parts need replacing? Actually looking at the code a bit, it seems like you could replace the select statements with the various handlers, and hook up some threads to them. It would indeed cook your CPU but that's ok for certain use cases.
- nicholassm83 2y agoI would love to give you a good answer but I've been working on low latency trading systems for a decade so I have never used async/actors/fibers/etc. I would think it implies a rewrite as async is fundamentally baked into your code if you use Tokio.
- lordnacho 2y agoDepends on what "fundamental" means. If we're talking about how stuff is scheduled, then yes of course you're right. Either we suspend stuff and take a hit on when to continue, or we hot-loop and latency is minimized at the cost of cooking a CPU. But there's a bunch of stuff that isn't that part of the trading system, though. All the code that deals with the format of the incoming exchange might still be useful somehow. All the internal messages as well might just have the same format. The logic of putting events on some sort of queue for some other worker (task/thread) to do seems pretty similar to me. You are just handling the messages immediately rather than waking up a thread for it, and that seems to be the tradeoff.
- _3u10 2y agoThese libs are more about hot paths / cache coherency and allowing single CPU processing (no cache coherency issues / lock contention) than anything else. That is where the performance comes from, referred to as "mechanical sympathy" in the original LMAX paper. Originally computers were expensive, and lots of users wanted to share a system, so a lot of OS thought went into this, LMAX flips the script on this, computers are cheap, and you want the computer doing one thing as fast as possible, which isn't a good fit for modern OS's that have been designed around the exact opposite idea. This is also why bare metal is many times faster than VMs in practice, because you aren't sharing someone else's computer with a bunch of other programs polluting the cache.
- lordnacho 2y agoYeah, I agree. But the ideas of mechanical sympathy carry over into more than one kind of design. You can still be thinking about caches and branch prediction while writing things in async. It's just the awareness of it that allows you to make the tradeoffs you care about.
- _3u10 2y agoHigh throughput networking does the same thing, it polls the network adapter rather than waiting for interrupts. The cost is not high, it's much less expensive to have a CPU operating more efficiently than not processing anything because its syncing caches / context switching to handle an interrupt. These libraries are for busy systems, not systems waiting 30 minutes for the next request to come in. Basically, in an under utilized system most of the time you poll there is nothing wasting CPU for the poll, in an high throughput system when you poll there is almost ALWAYS data ready to be read, so interrupts are less efficient when utilization is high.
- nine_k 2y agoRunning half the cores of an industrial Xeon or Zen under 100% load implies very serious cooling. I suspect that running them all at 100% load for hours is just infeasible without e.g. water cooling.
- wmf 2y agoNah, it will just clock down. Server CPUs are designed to support all cores at 100% utilization indefinitely. Of course you can get different numbers if you invent a nonstandard definition of utilization.
- nine_k 2y agoOf course server CPUs can run all cores at 100% indefinitely, as long as the cooling can handle it. With 300W to 400W TDP (Xeon Sapphire 9200) and two CPUs per typical 2U case, cooling is a real challenge, hence my mention of water cooling.
- wmf 2y agoI disagree. Air cooling 1 KW per U is a commodity now. It's nothing special. (Whether your data center can handle it is another topic.)
- kprotty 2y agoTokio's focus is on low tail-latencies for networking applications (as mentioned). But it doesn't employs yield_now for waiting on a concurrent condition to occur, even as a backoff strategy, as that fundamentally kills tail-latency under the average OS scheduler.
- pca006132 2y agoProbably doesn't make sense. Busy wait is fast when you can dedicate a core to the task, but this means that you cannot have many tasks in parallel with a small set of physical cores. When you oversubscribe, performance will quickly degrade. Tokio and other libraries such as pthread allows thread to wait for something and wake up that particular thread when the event occurs. This is what allows scheduler to schedule many tasks to a very small set of cores without running useless instructions checking for status. For foundational library, I think you want things that are composable, and low latency stuff are not that composable IMO. Not saying that they are bad, but low latency is something that requires global effort in your system, and using such library without being aware of these limitations will likely cause more harm than good.
- andrepd 2y agoHow would you? These are completely at odds: async is about suspending tasks that are waiting for something so that you can do other stuff in the meantime, and low-latency is about spinning a core at 100% to start working as fast as possible when the stuff you're waiting for arrives. You can't do both x)
- _3u10 2y agoNo, not really, this is for synchronous processing, the events get overwritten so by the time you async handler fires you're processing an item that has mutated. What you're looking for is io_uring on Linux or IOCP on Windows, I don't think osx has something similar, maybe kqueue.
- karmakaze 2y agoI played around with the original (Java) LMAX disruptor which was an interesting and different way to achieve latency/throughput. Didn't find a whitepaper--here's some references[0] which includes a Martin Fowler post[1]. [0] https://github.com/LMAX-Exchange/disruptor/wiki/Blogs-And-Articles https://github.com/LMAX-Exchange/disruptor/wiki/Blogs-And-Ar... [1] https://martinfowler.com/articles/lmax.html https://martinfowler.com/articles/lmax.html
- temporarely 2y agohere you go: Disruptor, Thompson, Farley, et al 2011 https://lmax-exchange.github.io/disruptor/files/Disruptor-1.0.pdf https://lmax-exchange.github.io/disruptor/files/Disruptor-1....
- alchemist1e9 2y agoIs there anything specific to Rust that this library does which modern C++ can’t match in performance? I’d be very interested to understand if there is.
- slashdev 2y agoNo, there shouldn’t be. Rust is not magic and you can compile both with llvm (clang++). If you specify that the pointers don’t alias, and don’t use any language sugar that adds overhead on either side, the performance will be very similar.
- nicholassm83 2y agoI agree. The Rust implementation even needs to use a few unsafe blocks (to work with UnsafeCells internally) but is mostly safe code. Other than that you can achieve the same in C++. But I think the real benefit is that you can write the rest of your code in safe Rust.
- alchemist1e9 2y agoUnless the rest of your code is already in C++ and you’re interested in this new better disrupter implementation, that’s probably a common situation for people interested in this topic. Any recommendations for those in that situation? perhaps existing C++ implementations already match this idk.
- bluejekyll 2y agoWhile you’re not explicitly saying this, C++ in Rust’s terms, is all unsafe. In a multi-threading context like this, that’s even more important.
- nicholassm83 2y agoI'm trying to be polite. :-) And there is a lot of great C++ code and developers out there - especially in the e-trading/HFT space.
- BrokrnAlgorithm 2y agoIs there also a decent c++ implementation of the disruptor out there?
- msaltz 2y agoHere’s one I’ve actually used/played with (though never measured performance of): https://github.com/lewissbaker/disruptorplus https://github.com/lewissbaker/disruptorplus And here’s one I saw linked on HN recently: https://github.com/0burak/imperial_hft/tree/main/distuptor https://github.com/0burak/imperial_hft/tree/main/distuptor
- LtdJorge 2y agoSo nice, that I was just reading about the disruptor, since I had an idea of using ring buffers with atomic operations to back Rust channels with lower latency for intra-thread communication without locks, and now I see this. Gonna take a read!