Live data from Hacker News

Zero-cost futures in Rust

aturon.github.io

71–80 of 348 posts

Re: Zero-cost futures in Rust

#71

TLDR; I’ve claimed a few times that our futures library provides a zero-cost abstraction, in that it compiles to something very close to the state machine code you’d write by hand. To make that a bit more concrete: - None of the future combinators impose any allocation. When we do things like chain uses of and_then, not only are we not allocating, we are in fact building up a big enum that represents the state machin…

Generating state machine out of async fluent APIs was enlightening.

How are are handling errors ? - One Error state with the context ?

Re: Zero-cost futures in Rust

#72
post #45

Earlier quoted context omitted.

that sounds great but I guess that will make the compilation time bigger.

Yeah, using closures in zero-cost abstractions (like iterator or now future adaptors) does make compilation slow. LLVM isn't used to this kind of code, and takes time optimizing it. However, now that we have MIR we can optimize this on the Rust side first, before the type info has been lost. This was one of the reasons MIR was added :)

[deleted]

Re: Zero-cost futures in Rust

#73
post #33

I dabbled with rust in the past and was really fascinated with it, but haven't played around lately. One thing caught my eye in the post: fn get_row(id: i32) -> impl Future ; That return type looks odd to me. What does it mean to return an "impl", and is that a new feature in rust, or just something advanced that I missed in my exploration before?

This is an exciting upcoming feature in Rust, which you can read more about in a couple places: - http://aturon.github.io/blog/2015/09/28/impl-trait/ - https://github.com/rust-lang/rfcs/pull/1522 This feature allows you to return any struct that implements the trait, without having to type the name of the struct (which can sometimes be quite big). It also means that clients only know what traits are implemented; the…

So, existentially quantified types?

Re: Zero-cost futures in Rust

#74
post #45

Earlier quoted context omitted.

that sounds great but I guess that will make the compilation time bigger.

Yeah, using closures in zero-cost abstractions (like iterator or now future adaptors) does make compilation slow. LLVM isn't used to this kind of code, and takes time optimizing it. However, now that we have MIR we can optimize this on the Rust side first, before the type info has been lost. This was one of the reasons MIR was added :)

I'm... curious about why you think LLVM isn't used to the sort of code that results from these sort of zero-cost abstractions: C++ code is also very aggressive about templates that inline away to nothing, and modern C++ especially has many similar abstractions to Rust (cf. closures, and the range proposal). Rust isn't special in that particular regard.

Maybe you mean LLVM is very low-level and thus it sees everything in the abstractions, which obviously takes time to sort through (and is especially wasteful to do multiple times for different monomorphisations). Thus, using MIR to optimise at a higher level can simplify code more efficiently before it hits LLVM.

Re: Zero-cost futures in Rust

#75

Earlier quoted context omitted.

That is new syntax which has been accepted as a language change, but has not actually landed in the compiler yet. It was shown this way because it's much easier to understand, but is the same thing as it would be in the real code. https://github.com/rust-lang/rfcs/blob/master/text/1522-cons... is an explanation of the feature in detail, but it boils down to "I will return you some kind of thing that implements the Fu…

Does the need for this arise, in some sense, because Rust doesn't offer classical inheritance and so no mechanism to indicate co/contra variance?

I'm not sure variance is at all related to this particular feature (maybe you could expand on your thinking?), but I also don't think the classical inheritance techniques for solving this problem are appropriate. I assume you're referring to, say, having an Iterator or Future base class and returning that. This unfortunately imposes costs like virtual calls and allocations, and is in fact already possible in Rust via trait objects (a function can return Box>, which is an allocation containing the data along with a vtable of virtual function pointers to manipulate that data). The impl trait feature is designed to allow returning closures and other unnameable/complicated types with no unnecessary cost, it's the same as returning a struct in C without compromising on programmer niceties.

Re: Zero-cost futures in Rust

#76

I'm rather surprised by the benchmark; I would expect the Go benchmark to be faster than Java (and the fact that it isn't may indicate some improvements that can be done to fasthttp by learning from rapidoid or minihttp). Then again, the difference isn't that much, so it just could be implementation details that would require a total refactor to fix.

You may find this makes somewhere more sense to think of it as ~5.3 microseconds per request for fasthttp vs. ~4.8 microseconds for Java vs. ~4.3 for Rust. It's 40 microseconds or so for the standard lib Go. I'm just eyeballing the graph but this should be close enough (dominated by local CPU variances and such). Just as some people point out that "gallons per mile" is a more intuitively useful way of thinking, I think that at this scale "overhead per request" is a better way of thinking about it.

I'm not generally a big fan of measuring the "ping time" response for web servers, but in this case I believe it is justified since we really are trying to establish that this future library is very fast. I fear, based on experience, that some developers well be looking at this and will sit there trying to choose fasthttp vs. rapidoid vs. minihttp based on this one graph, without considering what they really mean. Overhead-per-request I think makes it more clear that for the vast, vast majority of purposes, all of these, including Go and Node, are "way way faster than your code", and unless you know you're building a server where you seriously need to answer an API call at several hundred thousand responses per second, all of these are "fast enough" and the real criteria for choosing should be "everything else".

(One last edit... remember, it's "milli/micro/nano". Micro seems to be forgotten since it seems like many things that we care about fit into nano- or milli-. It's quite a challenge in the web world to get your request out in under a millisecond usually, so .040 milliseconds added as HTTP overhead is rarely the problem.)

Re: Zero-cost futures in Rust

#77
post #63

Earlier quoted context omitted.

> 2 - no allocation : , does'nt the lambda closure need to be allocated somewhere ? I expect it is embedded in the state machine structure. In Rust (and C++) lambdas/closures are "value types".

In C++ lambdas have limited size (about 3 machine words). If it's context is larger than that, the data may be allocated on heap (if compiler can't optimize that allocation out).

I though in c++ the lambda get created on the stack like any other object.

Re: Zero-cost futures in Rust

#78
How does the zero-cost abstraction work?

Say we make a Future and then chain `.map(|x| x+1)` on a dynamic number of times (N). Presumably this requires storing at least N function pointers.

How can we store these N function pointers with zero cost? If it only takes one allocation, where does the N-1 future store its function pointers?

Re: Zero-cost futures in Rust

#79
post #33

Earlier quoted context omitted.

This is an exciting upcoming feature in Rust, which you can read more about in a couple places: - http://aturon.github.io/blog/2015/09/28/impl-trait/ - https://github.com/rust-lang/rfcs/pull/1522 This feature allows you to return any struct that implements the trait, without having to type the name of the struct (which can sometimes be quite big). It also means that clients only know what traits are implemented; the…

So, existentially quantified types?

Yes. However, there isn't full support of existential types in the horizon – only returning them from a function (and using them inside the caller, as the type inference allows this). This resolves some specific pain points of the current Rust experience.

There may or may not be some extensions (like storing them in fields of structs) in the future.

Note that Rust supported existentials before too, in the form of trait objects. But this was only possible behind a pointer and using virtual dispatch, so the performance story wasn't perfect. The impl Trait syntax is supported through monomorphization.

Re: Zero-cost futures in Rust

#80
post #69

TLDR; I’ve claimed a few times that our futures library provides a zero-cost abstraction, in that it compiles to something very close to the state machine code you’d write by hand. To make that a bit more concrete: - None of the future combinators impose any allocation. When we do things like chain uses of and_then, not only are we not allocating, we are in fact building up a big enum that represents the state machin…

Generating state machine out of async fluent APIs was enlightening. How are are handling errors ? - One Error state with the context ?

This is probably better answered by how Rust handles errors generally:

https://doc.rust-lang.org/book/error-handling.html

Suffice it to say, similar magic tricks have already been at work to make handling errors not soak up a lot of space, and it'd work well with something like this, I think.

Post reply on HN