Live data from Hacker News

Zero-cost futures in Rust

aturon.github.io

161–170 of 348 posts

Re: Zero-cost futures in Rust

#161

I'm confused by .map(|row| { json::encode(row) }) .map(|val| some_new_value(val)) Over .map(json::encode) .map(some_new_value) Is the explicit extra layer of lambda generally prefered in Rust over just passing the functions?

I wonder if it is information used by the compiler to do error checks. Keep in mind that one of the goals of Rust is to catch common errors in C like languages at compile time rather than run time.

Re: Zero-cost futures in Rust

#162
post #142

Earlier quoted context omitted.

> On Linux, you don't actually gain that much if anything over 1:1 threading. You use green threads instead of native threads because native threads have space overhead, not because they have time overhead. Attempting to spawn 100k OS threads will do strange things to most kernel scheduling algorithms; they're not optimized for that use-case.

> You use green threads instead of native threads because native threads have space overhead, not because they have time overhead. The main overhead of a thread, green or native, is the stack. The size of the stack is independent of whether you use native or green threads. Go's small stacks are actually made possible by its GC, not its choice of 1:1 or M:N. In musl, for example, you can have 2KB stacks [1] with 1:1.…

The space overhead is significantly worse for native threading in the presence of many threads since each thread needs both a user and kernel stack.

For M:N, there are N kernel stacks.

Re: Zero-cost futures in Rust

#163

Earlier quoted context omitted.

In other words futures are not as composable as one would hope. John Reppy's CML seems like a much better toolbox which gets composability without pretension. Vesa Karvonen (whom I understand has worked on the MLTon compiler) has offered an excellent delivery in Hopac for C# and F# complete with a slew of combinators: https://github.com/Hopac/Hopac I'm not aware of anyone offering an alternative superior to an inform…

I think Rust's approach here makes a lot more sense for the language than Go/CML style M:N would. You can recover most of the M:N ergonomics over time via async/await style syntax. But if your foundation is built on a (comparatively) slow "pthreads in userland" threading model, then you hit a performance ceiling that you can never break through. For a language like Rust, it makes sense to begin in the optimum place a…

I think there's little or no evidence that "you can recover most of the M:N ergonomics over time via async/await style syntax", despite a decade or so of attempts. I think there's an underlying semantic concern that seems unsugarable.

Munificent's http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y... expresses the problem eloquently.

None of that means you're wrong about async making "a lot more sense for the language", though.

Re: Zero-cost futures in Rust

#164

Earlier quoted context omitted.

Hm. I've heard arguments that C# or Java is slow for multiple reasons, but never because of the minuscule overhead of a virtual method dispatch when using objects behind interfaces (kinds similar to trait objects). It's interesting that this is seen as significant here. Are we dealing with much shorter timescales, or just being eager to optimise everything?

Virtual dispatch per se is not terribly slow, as long as the branch is predictable by the CPU. The problem is that virtual dispatch prevents the sort of aggressive inlining and interprocedural opimizatios that C++ compilers are known to do. C# and Java JITers get around that via runtime analysis and speculative inlining, but that is done at runtime and eats away some of the precious little time available for optimisa…

Put it this way:

Cost of a branch misprediction is 10s of cpu cycles. (1) Measured in gigahertz (10^9 cycles per second).

Time to turn around a web request is, if you're very lucky and have done the work, mainly about getting a value from an in-memory cache at multiple milliseconds (2). That's 1 / (10^3) seconds.

If you're not lucky, 10s or 100s of milliseconds to generate the response.

It seems that the second duration is best case around 10^6 times longer. I would not sweat the first one.

1) http://stackoverflow.com/a/289860/5599 2) http://synsem.com/MCD_Redis_EMS/

Re: Zero-cost futures in Rust

#165

Finally a nice async/io interface for rust, always felt that it was a big missing piece, couple of questions for peps familiar with async in other languages : 1 - Isn't the state machine approach the same as C#/.net async/await is using ? But the with the added convenience of the syntactic sugar ? 2 - no allocation : , does'nt the lambda closure need to be allocated somewhere ? 3 - I would have love some comparison (…

C++ programmer here; re 3: the C++ coroutine proposals (there are multiple) are completely orthogonal to futures.

The closest things in the standard to these rust futures, is of course std::future, which currently is very limited as it even lacks chaining and composition. The Concurrency TR (which eventually will be added to the standard, but sadly not for C++17) does provide these features (and more); multiple implementations have been are available for some time (boost, HPX, Folly, and some compiler vendors).

The issues with std::future is that is far from zero cost, as it requires allocation (the control block is dynamically allocated), type erasure (the callback type is not part of the future) and synchronisation (as the promise can be fulfilled from another thread). Efficient implementations of std::future are possible but will never be as fast as what is shown in this rust library.

On the other hand the SeaStar C++ library (I'm in no way involved, just a fan) already provides pretty much the same zero cost futures with a very similar implementation as it doesn't try to follow the standard design.

Re: Zero-cost futures in Rust

#166

Earlier quoted context omitted.

Virtual dispatch per se is not terribly slow, as long as the branch is predictable by the CPU. The problem is that virtual dispatch prevents the sort of aggressive inlining and interprocedural opimizatios that C++ compilers are known to do. C# and Java JITers get around that via runtime analysis and speculative inlining, but that is done at runtime and eats away some of the precious little time available for optimisa…

Put it this way: Cost of a branch misprediction is 10s of cpu cycles. (1) Measured in gigahertz (10^9 cycles per second). Time to turn around a web request is, if you're very lucky and have done the work, mainly about getting a value from an in-memory cache at multiple milliseconds (2). That's 1 / (10^3) seconds. If you're not lucky, 10s or 100s of milliseconds to generate the response. It seems that the second durat…

Contrary to popular belief, not all C++ programs (or rust FWIW) are web servers serving HTTP requests over the Internet.

Re: Zero-cost futures in Rust

#167

Earlier quoted context omitted.

In other words futures are not as composable as one would hope. John Reppy's CML seems like a much better toolbox which gets composability without pretension. Vesa Karvonen (whom I understand has worked on the MLTon compiler) has offered an excellent delivery in Hopac for C# and F# complete with a slew of combinators: https://github.com/Hopac/Hopac I'm not aware of anyone offering an alternative superior to an inform…

I think Rust's approach here makes a lot more sense for the language than Go/CML style M:N would. You can recover most of the M:N ergonomics over time via async/await style syntax. But if your foundation is built on a (comparatively) slow "pthreads in userland" threading model, then you hit a performance ceiling that you can never break through. For a language like Rust, it makes sense to begin in the optimum place a…

> You can recover most of the M:N ergonomics over time via async/await style syntax.

While i agree with the tradeoff made by rust (although i think the approach used by C++ coroutine is better), i don't think that having async/await syntax give you "most" of the ergonomics of the Go M:N model .

The main advantage of the go model is that both asynchronous and synchronous operations are identical, with async/await you still need to model the async operation and the sync operation with different types. Not having to decide(and design) upfront which part of your computation is async and which is not is what makes go so attractives

Re: Zero-cost futures in Rust

#168
Back when futures and promises were a new concept to most people, if someone asked me to explain why you would want to do such a thing, my favorite example was loading images in a web browser. You wouldn't want to load the same image four times just because it appears in four places on a page, would you? Yada yada promises etc etc.

Seeing articles like this makes me feel like a circle has finally been closed.

Re: Zero-cost futures in Rust

#169

Earlier quoted context omitted.

Put it this way: Cost of a branch misprediction is 10s of cpu cycles. (1) Measured in gigahertz (10^9 cycles per second). Time to turn around a web request is, if you're very lucky and have done the work, mainly about getting a value from an in-memory cache at multiple milliseconds (2). That's 1 / (10^3) seconds. If you're not lucky, 10s or 100s of milliseconds to generate the response. It seems that the second durat…

Contrary to popular belief, not all C++ programs (or rust FWIW) are web servers serving HTTP requests over the Internet.

Yep, that's why I'm asking about the use-cases in the grandparent comment.

Re: Zero-cost futures in Rust

#170

Earlier quoted context omitted.

I think Rust's approach here makes a lot more sense for the language than Go/CML style M:N would. You can recover most of the M:N ergonomics over time via async/await style syntax. But if your foundation is built on a (comparatively) slow "pthreads in userland" threading model, then you hit a performance ceiling that you can never break through. For a language like Rust, it makes sense to begin in the optimum place a…

I think there's little or no evidence that "you can recover most of the M:N ergonomics over time via async/await style syntax" , despite a decade or so of attempts. I think there's an underlying semantic concern that seems unsugarable. Munificent's http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y... expresses the problem eloquently. None of that means you're wrong about async making "a lot more sense for…

> Munificent's http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y.... expresses the problem eloquently.

I have several issues with that blog post.

1. The performance concerns are glossed over. The conclusion is "threads are superior", but that ignores the reason why we want async I/O in the first place: performance.

2. "What if the new place you want to call it is blue? You’ll have to turn it red." is false. There's a way to convert blocking code to async-friendly code: use a thread pool. This is in fact what cgo does internally—see the next point.

3. You always have red functions and blue functions if you have an FFI. But this isn't a big deal because synchronous functions are easily convertible to asynchronous functions (via a thread pool) and asynchronous functions are easily convertible into synchronous functions (via just blocking). So the supposedly big semantic problem just boils down into a question of making sure you remember to switch into the other mode when necessary (which you can always easily do without restructuring your program). This is something that the language can do for you automatically—proof is that Go does it!—and I would like to see type system or other static approaches to doing this in Rust.

Post reply on HN