News for hackers. Stuff that matters.
Re:How Rust optimizes async/await by jnordwick on Monday August 19, 2019 @01:15PM (#20739615)
epoll io loop performs better for most network io though and is simplier to manage when you have to start dealing with out of band issues (like efficient hearbeats - every time I've had a conversation without how to move some of the heartbeat code over to async it comes down to just accepting it isn't going to be as efficient as my c++ implementation and either strain heavily of accept over publication).

Last time I saw there was still a couple extra allocations going on too in the compiler (I was told they were being worked on) and basically the default executor, tokio, wasn't very efficient at changing events in the queue (requiring a an extra self signal to get the job done).

I'd be interesting to see how little cost these are, because there is defintely a cost to the generator implementation. Yes, if I wrong a generator to do this, I couldn't write it better, but I wouldn't write a generator (and that would be a very odd definition of zero-cost there anything can be called zero cost even GC as long as it is implemented well - well, that depends on if rustc saves unnecessary state).

> Additionally, it should allow miri to validate the unsafe code inside our generators, checking for uses of unsafe that result in undefined behavior.

This is really useful as a lot of the buffer handing code needs to use unsafe for efficienty issues. And the enums sharing values is nice too - hopefully the extra pointer derferences can be optimized out.

I do worry though about all this state sitting on the head and ruining cache locality on function calls though.

Re:How Rust optimizes async/await by pcwalton on Monday August 19, 2019 @01:21PM (#20739669)
If you want to use epoll directly in Rust, go right ahead. Nothing is stopping you. You don't have to switch to C++ to use epoll.

It's pretty clear that most people don't want to write and maintain that kind of code, though—that's what async/await is for. Personally, I hate writing state machines.

Re:How Rust optimizes async/await by jnordwick on Monday August 19, 2019 @02:04PM (#20740125)
They're not very well done. Rust kind of skipped the whole non-blocking thing and went straight to tokio, then had to backtrack a little to mio+tokio, then when mio is only half way jupmed to async/await. (mio was originall tokio rolled into it - it was the whole thing and you had to use mios sockets and mios timers on the mio event loop - tokio was then split out - metal io, it was not - this was because there wasnt a non-blocking interface in rust yet).

Async is deinitely a distraction. Rust should have stabilized other pieces more before jumping to yet another paradigm. It is gaining this collection of half-implemented things. I'm guessing generators are going to the the next move before async/await is even in the print book.

Rust has moved from zero-cost abstraction to "an fairly efficient implemention of everything you can think of".

Re:How Rust optimizes async/await by pcwalton on Monday August 19, 2019 @02:26PM (#20740332)
Rust didn't implement async/await for fun. The team implemented it because it was perhaps the #1 requested feature from users, including me.

You can see it here on HN. What's the #1 comment that always appears on Rust async threads? Invariably it's "why not just use goroutines? That kind of code is so much easier to write!" Now for various reasons M:N threading like in Go doesn't work in Rust. What does work, and offers most of the ergonomics of threads/goroutines, is async/await. That's why the team has prioritized it.

There are people who care less about ergonomics than optimizing every CPU cycle for socket servers. For those people, the focus on getting the ergonomics right before microoptimization of mio and tokio might be frustrating to see. But the overwhelming majority of users just want easy-to-use async I/O. Async/await is an enormous improvement in readability and maintainability over hand-written state machines. Rust would be doing a disservice to its users if it focused on microoptimization of tokio instead.

[dead]
Re:How Rust optimizes async/await by pcwalton on Monday August 19, 2019 @02:48PM (#20740559)
If you think that Rust doesn't care about addressing the needs of C++ users, I'm not sure what to tell you.

It's not like C++ only focuses on features with zero overhead. The zero-overhead principle means that you don't pay for what you don't use. It doesn't mean that compiler developers are forbidden from working on any language or library feature that consumes more than zero cycles at runtime.

[dead]
Re:How Rust optimizes async/await by kbenson on Monday August 19, 2019 @04:30PM (#20741500)
I think the issue is that the zero-cost abstraction terminology is used in two ways often. First, to mean a feature you can use and costs nothing because the compiler will change it into a more common form for you (such as functional programming approaches where it will take multiple filters and map passes and turn it into a single loop), and second, to mean that features added that do add overhead, only do so when you use them (as in, there's no runtime to manage M:N threading unless you include a crate that does so, because developers not using that feature shouldn't have to pay for it).

I think the terminology here is looser than it should be, and these are not really the same thing, even though I've seen zero-cost abstractions used to describe them both. It causes confusion sometimes, as I believe it is doing here.

Re:How Rust optimizes async/await by quietbritishjim on Tuesday August 20, 2019 @09:37AM (#20746849)
Sure, and there are a few hooks for one in the C++ standard library e.g. std::declare_reachable [1], although it doesn't include an actual implementation. Only mark and sweep is feasible, not mark and compact, for obvious reasons [2]. Here's a mention of garbage collection Stroustrop's old C++11 FAQ [3]

[1] https://en.cppreference.com/w/cpp/memory/gc/declare_reachabl...

[2] https://herbsutter.com/2011/10/25/garbage-collection-synopsi...

[3] https://web.archive.org/web/20120228143039/http://www2.resea...

Re:How Rust optimizes async/await by pcwalton on Monday August 19, 2019 @03:06PM (#20740757)
Sure. It's called Rc and Arc.
Re:How Rust optimizes async/await by jnordwick on Tuesday August 20, 2019 @02:17AM (#20744256)
Don't be intentionally obtuse. You know I mean something more like mark and sweep
Re:How Rust optimizes async/await by hexane360 on Monday August 19, 2019 @09:40PM (#20743177)
Re:How Rust optimizes async/await by pcwalton on Monday August 19, 2019 @09:56PM (#20743250)
Reference counting is a form of garbage collection, despite the fact that it does not detect cycles without extra work. This has been the correct terminology in the memory management literature for decades.
Re:How Rust optimizes async/await by nemothekid on Monday August 19, 2019 @09:27PM (#20743134)
Is shared_ptr a zero cost abstraction?
Re:How Rust optimizes async/await by kam on Monday August 19, 2019 @10:13PM (#20743320)
Mostly, with the caveat that in single threaded usage, you're paying for the unnecessary overhead of atomic ops on the reference count.

Rust has `Rc`, which is not atomic but `!Send` so the type system ensures that it stays within a thread, as well as the atomic `Arc`.

Re:How Rust optimizes async/await by saghm on Monday August 19, 2019 @02:11PM (#20740174)
> Rust kind of skipped the whole non-blocking thing and went straight to tokio, then had to backtrack a little to mio+tokio, then when mio is only half way jupmed to async/await.

Not sure exactly what you mean here; mio predates tokio by a couple years, and has been possible to use standalone since before tokio ever became a thing.

Re:How Rust optimizes async/await by devit on Monday August 19, 2019 @02:23PM (#20740302)
Early versions of mio had a bunch of non-essential things like a timer wheel built-in, that were later removed.
Re:How Rust optimizes async/await by rapsey on Tuesday August 20, 2019 @01:18AM (#20744035)
What does tokio have to do with that?
Re:How Rust optimizes async/await by SomeOldThrow on Monday August 19, 2019 @02:12PM (#20740184)
This strikes me as needlessly hostile without contributing anything.
Re:How Rust optimizes async/await by rapsey on Monday August 19, 2019 @01:19PM (#20739651)
Honestly I don’t get it why simply using mio (epoll, kqueue, iocp wrapper) is so unpopular.
Re:How Rust optimizes async/await by oconnor663 on Monday August 19, 2019 @01:33PM (#20739785)
See these examples of how async/await simplifies looping and error handling: https://docs.rs/dtolnay/0.0.3/dtolnay/macro._01__await_a_min.... Await syntax makes it much easier to compose asynchronous code from different libraries, in the same way that ordinary function calls compose synchronous code. Talking to epoll/mio directly is certainly possible, but it's hard to make two different libraries work well together if they both need to talk to a single epoll handle.
Re:How Rust optimizes async/await by rapsey on Monday August 19, 2019 @01:37PM (#20739832)
Yeah but you’re talking about things not even stable. Old tokio with closures is terrible imo.
Re:How Rust optimizes async/await by steveklabnik on Monday August 19, 2019 @06:35PM (#20742422)
It’s extremely close to being stable; it just missed a release train, because there’s some last minute polish that needs to be done. It will be on its way soon!
Re:How Rust optimizes async/await by jnordwick on Monday August 19, 2019 @02:07PM (#20740145)
everybody always points to the 5 line basics, never to how complex async/await gets in a real system with timers, tuneouts, multiple errors types that need to be handled in different ways, heartbeats, side channel info, etc... It isn't better at that point.

Cool, you can write a 5 line echo server easier, but good luck with a high performance server handling important transactions.

(If there is documentation on how to do some of these or good example code, please point me to it, because my last attempt at getting send/rcv heartbeats efficiently didn't work.)

Re:How Rust optimizes async/await by jcranmer on Monday August 19, 2019 @02:42PM (#20740499)
The networking implementations I've had to do were all made a lot easier moving to async/await from hand-written state machines. It's a reduction in LOC terms of 6x or so, and the logic is much, much easier to follow.

State machines are viable if your transition function is such that effectively every combination of (state, input) leads to a different state. If the transition function is mostly describing "go to the (single) next state or error," then you're essentially asking the user to do a lot of bookkeeping that compilers are good at and users are bad at.

Re:How Rust optimizes async/await by littlestymaar on Monday August 19, 2019 @02:52PM (#20740599)
Having ported a full peer-to-peer engine in JavaScript from old-school callbacks to async/await, I strongly disagree with your comment.

Async/await really shines when the complexity arises.

Re:How Rust optimizes async/await by pcwalton on Monday August 19, 2019 @02:33PM (#20740408)
People are doing a lot more with async/await than five-line echo servers. They're converting entire codebases and seeing dramatic reductions in noise.
Re:How Rust optimizes async/await by leshow on Monday August 19, 2019 @04:02PM (#20741277)
Probably because you're just looking at examples; that's selection bias. The combinators/manual implementing Future approach to async IO gets even worse at scale, async/await is infinitely easier. If you don't like it, nobody is forcing you to use it.
Re:How Rust optimizes async/await by pcwalton on Monday August 19, 2019 @01:24PM (#20739705)
Because most people don't like writing state machines by hand.
Re:How Rust optimizes async/await by rapsey on Monday August 19, 2019 @01:35PM (#20739810)
Pre-await Tokio code looks way worse to me.
Re:How Rust optimizes async/await by jcranmer on Monday August 19, 2019 @03:08PM (#20740770)
A lot of that, I suspect, is that Rust's borrow checker makes specifying ownership correctly for callback chains extremely painful and unergonomic.

In my experience, closure-based async programming is only sufficiently painless in managed languages to be worth using. When you're dealing with manual memory management (or, rather, smart pointer memory management), you spend a lot more time trying to make the captures hold onto stuff.

Re:How Rust optimizes async/await by tmandry on Monday August 19, 2019 @03:50PM (#20741165)
I'd agree with this, and emphasize the point that this stuff is really tricky to get right without GC. Fighting the borrow checker is somewhat expected when you're dealing with this level of inherent complexity in your memory management.

One of the key reasons for shipping async/await is that it erases almost all of this difficulty and lets you write straight-line code again.

Re:How Rust optimizes async/await by carllerche on Tuesday August 20, 2019 @12:39AM (#20743894)
> tokio, wasn't very efficient at changing events in the queue (requiring a an extra self signal to get the job done).

Could you expand on that?