Successfully reported this slideshow.
Your SlideShare is downloading. ×

Rust Is Safe. But Is It Fast?

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Upcoming SlideShare
Whoops! I Rewrote It in Rust
Whoops! I Rewrote It in Rust
Loading in …3
×

Check these out next

1 of 24 Ad

Rust Is Safe. But Is It Fast?

Download to read offline

Rust promises developers the execution speed of non-managed languages like C++, with the safety guarantees of managed languages like Go. Its fast rise in popularity shows this promise has been largely upheld.

However, the situation is a bit muddier for the newer asynchronous extensions. This talk will explore some of the pitfalls that users may face while developing asynchronous Rust applications that have direct consequences in their ability to hit that sweet low p99. We will see how the Glommio asynchronous executor tries to deal with some of those problems, and what the future holds.

Rust promises developers the execution speed of non-managed languages like C++, with the safety guarantees of managed languages like Go. Its fast rise in popularity shows this promise has been largely upheld.

However, the situation is a bit muddier for the newer asynchronous extensions. This talk will explore some of the pitfalls that users may face while developing asynchronous Rust applications that have direct consequences in their ability to hit that sweet low p99. We will see how the Glommio asynchronous executor tries to deal with some of those problems, and what the future holds.

Advertisement
Advertisement

More Related Content

Slideshows for you (20)

Similar to Rust Is Safe. But Is It Fast? (20)

Advertisement

More from ScyllaDB (20)

Recently uploaded (20)

Advertisement

Rust Is Safe. But Is It Fast?

  1. 1. Brought to you by Rust Is Safe. But Is It Fast? Glauber Costa Author and Maintainer of Glommio Founder/CEO ChiselStrike
  2. 2. Glauber Costa ■ Author and maintainer of Glommio Rust async executor. ■ Veteran of low-latency high performance systems. ● Linux Kernel, ScyllaDB, Datadog. ■ Founder/CEO ChiselStrike.
  3. 3. Why Rust? ■ A … less powerful C++? ■ “All” pointers are smart pointers. ● Exceptions explicitly use unsafe keyword.
  4. 4. Lifetimes ■ RAII rules define minimum lifetime. ■ Borrow checker enforce lifetimes for references. ● Live as-long-as. Leaks still possible. ● But no use-after-free! let a = Object::new(); let b = &a; drop(a); use(b); // impossible
  5. 5. Trait system ■ Similar to C++ concepts. ■ Specify “trait bounds”. fn function<T>(arg: T) { println!(“value: {}”, arg); } fn function<T: Display>(arg: T) { println!(“value: {}”, arg); } Fails Succeeds
  6. 6. Special lifetimes ■ static: ● As a reference: “entire lifetime of the program”. ● As a trait bound: “never becomes invalid”. ■ owned data as well as static references. fn background<T: ‘static>(foo: T) { … } let a = T::new(); background(a); // Ok! background(&a); // Fails!
  7. 7. Dealing with references in static contexts ■ Clone trait bound. ● Think shared pointers. ● Each user extends the life of the data: memory safe. ● Remember: leaks still possible. fn background<T: ‘static + Clone>(foo: &T) { let f = foo.clone(); // Ok, new object is created } let a = T::new(); background(a); // Ok! background(&a); // Ok too!
  8. 8. Dealing with multiple threads ■ Send trait bound. ● Types that can be transferred across thread boundaries. ● Nothing said about access. ● Reference counted pointer (Rc): NOT Send. ■ Special “Atomic Rc” (Arc) is Send.
  9. 9. Async Rust ■ Implemented through the Trait system. pub trait Future { type Output; // Return value pub fn poll( self: Pin<&mut Self>, cx: &mut Context<'_> // state ) -> Poll<Self::Output>; // Ready<T> or Pending
  10. 10. Driving Futures ■ No standard way, bring your own. ■ Tokio, async-std, Glommio. ■ “Simple” event loop calling poll continuously. ● But in practice: I/O, channels, timers, core types, etc.
  11. 11. Rust basic Futures ■ Akin to co-routines. ■ No parallelism between x and y. let x = foo.await; let y = bar.await; x + y
  12. 12. Rust basic Futures ■ select-like multi-future possible. ■ More common, spawn tasks. let x = spawn(async move { // code });
  13. 13. Tokio spawn ■ Can be executed in any thread (Send). ■ Indeterminate lifetime (static). ■ Scoping harder than it seems. ● See https://itnext.io/async-rust-history-strikes-back-d69aced6760 pub fn spawn<T>(future: T) -> JoinHandle<T::Output> where T: Future + Send + 'static, T::Output: Send + 'static,
  14. 14. Glommio spawn ■ Nothing is Send. ■ Internal state machine is almost entirely free of atomics. pub fn local<T>(future: T) -> JoinHandle<T::Output> where T: Future + 'static, T::Output: 'static,
  15. 15. Glommio x-thread wake Glommio Executor Task State Foreign Executor Clone: Atomic reference count mpsc lockless queue Wake: push to queue
  16. 16. Results https://github.com/fujita/greeter - “minimum” baseline removed for clarity
  17. 17. Unfortunate reality ■ Swapping executors kind of ok. ● Lack of timer trait is a problem, though. ■ But tokio and async-std depend heavily on Send. ● Not possible to inherit traits. ● In practice many libraries do too.
  18. 18. The challenge with File I/O ■ Tokio File I/O uses a thread-pool. ■ Newer Optane thread-pool results underwhelming. ■ linux-aio hard to get right. ■ Enters io_uring. https://www.scylladb.com/2020/05/05/how-io_uring-and-ebpf-will-revolutionize-programming-in-linux/
  19. 19. Glommio File I/O ■ io_uring natively. ■ Incentivizes Direct I/O, but works with buffered I/O too. ■ Positional reads: ● read_at(pos, sz) -> ReadResult; ● io_uring pre-mapped registered buffers. ■ Stream Writer, Stream Reader: ● AsyncWrite, AsyncRead.
  20. 20. Newer developments ■ Tokio recently announced tokio-uring. ■ Similar design, looking forward!
  21. 21. Buffer lifetimes ■ Hard to zero-copy. ■ Infamous Rust “cancellation problem”. ● Tasks can die at any time, kernel keeps writing to the buffer. impl AsyncRead for DmaStreamReader { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], // not guaranteed stable. ) -> Poll<io::Result<usize>> {
  22. 22. AsyncBufRead ? ■ Better, as it returns a buffer. ■ Higher-level functions will likely still copy. impl AsyncBufRead for StreamReader { fn poll_fill_buf<'a>( mut self: Pin<&'a mut Self>, cx: &mut Context<'_>, ) -> Poll<io::Result<&'a [u8]>>
  23. 23. Summary ■ Reference Count explosion limits scalability. ● Possible to avoid with TPC executor. ● But currently hard to swap executors. ■ File I/O performance a concern. ● io_uring increased adoption bridges that gap. ● tokio-uring exciting news. ■ Still hard to do real zero-copy I/O. ● Async workgroup has good proposals for this.
  24. 24. Brought to you by Glauber Costa glauber@chiselstrike.com @glcst

×