Live data from Hacker News

Zero-cost futures in Rust

aturon.github.io

311–320 of 348 posts

Re: Zero-cost futures in Rust

#311
post #310

Earlier quoted context omitted.

It would require higher order control flow analysis like k-CFA, which would certainly fail to produce a bounded stack size on any nontrivial program. The futures library is the control flow analysis. Because it uses the type system instead of higher order control flow analysis, it actually achieves precision.

Are you sure about that? First, many higher-order constructs boil down to simple first-order control flow after partial evaluation (see e.g. AnyDSL https://anydsl.github.io/#publications ). Inferring stack size bounds in the presence of higher-order programming features is also possible, and was implemented (as the "static region optimization") in MLkit. But the main point is that code in a high-level programming lan…

You can't get around page fault blocking with user-space M:N threading. It will be approximate until there is a way to do that.

Re: Zero-cost futures in Rust

#312

Earlier quoted context omitted.

Are libmill and Go's approach the same? I.e. Holding concurrent state in multiple stack frames? What causes that to have degraded performance? Is this new approach different because instead of stack frames you just have dynamically allocated callback state?

libmill only uses a single kernel thread so it's a little bit simpler here than Go, but otherwise they should be comparable. Regarding stack vs. dynamically allocated state (in a future) I'm not convinced what's better. Yes, the allocated stack most likely has an overhead. But at least the data will be stored there in linear fashion and accessing the state will be super cheap once the stack is loaded. In case of futu…

With Rust zero cost futures, only one memory allocation is done for the entire chain of futures. It's not doing an allocation for every callback.

This makes it comparable to using a stack. In an M:N solution, the stack is usually (but not always) allocated similarly (e.g. using malloc).

Re: Zero-cost futures in Rust

#313

Earlier quoted context omitted.

> Being able to express your code in a sequential manor and still gain the performance offered by implicit fiber,goroutine,w/e scheduling is pretty awesome. You don't gain as much performance. On Linux, you don't actually gain that much if anything over 1:1 threading. Most of the benefits of goroutines actually comes from the small stacks, which don't have anything to do with M:N and 1:1 to begin with—they're a featu…

> Why would you write networking code in C in 2016, when there are better alternatives available (like this one)? As someone who's done a bit of game development in the past I can easily say that you'll fall flat on your face in some fields if you don't write your own protocols. There are simply no protocols that operate well in every use case and there are also use-cases without standardized protocols. For extremely…

I believe pcwalton agrees that the ability to write one's own networking code is important. The quoted line is simply questioning why you'd write such code in C (which, if you're a game developer, means that "platform support" is a perfectly valid answer).

Re: Zero-cost futures in Rust

#314

Earlier quoted context omitted.

Fair enough, but kernel stacks are 8K. 10K user + kernel size is a far cry from the 2MB default pthread stack size people usually talk about when they talk about 1:1. I don't know of any benchmark comparing Go vs. a 1:1 implementation with 2K pthread stack sizes, but I would be surprised if the performance difference is large at all.

> Fair enough, but kernel stacks are 8K. And getting even cheaper than that, with the effort to make kernel stacks use virtual memory. As I understand it, once kernel stacks use virtual memory, they'll start out at a single 4k page.

Is there a writeup somewhere of the progress towards this, or a prospective timeline?

Re: Zero-cost futures in Rust

#315
post #289

Earlier quoted context omitted.

Gankro gave an example, but here's a better way to think about associated types: Think about them the same way you think of a method. Methods are "associated functions". If you implement a trait on a type, it can only have one version of a trait method, not two. Similarly, it can have one associated type, not multiple. With a generic trait the trait itself is generic; there are multiple "versions" of this trait so yo…

> Of course, Rust doesn't forbid having it the other way around -- we could make Output a generic parameter too, and have overloaded functions which can have different output types for the same input (and need type annotations to choose). But we don't want FnOnce to work that way, so we don't have it like that. Can you please explain why FnOnce shouldn't work this way?

Because overloading the return type (for the same inputs) is pretty unusual :)

Rust has the machinery to deal with this -- you'd need type annotations -- it just gets annoying.

Specific functions in Rust do get "overloaded" by return type by returning a generic parameter (e.g. the collect() method on iterators), but in general functions are expected to work without needing type annotations on return.

I mean, allowing this kind of use of FnOnce isn't a bad idea. I don't see major issues with it, just minor ones. That's not the choice that was made during the design. I'm sure if we looked at the original design rfcs this point will have come up somewhere :)

I wasn't involved in that decision, so I'm not aware of the exact reasoning.

Re: Zero-cost futures in Rust

#316

Earlier quoted context omitted.

> From my understanding, golang does not expose an asyncio interface, but this doesn't meant that golang runtime doesnt perform io operations asynchronously. This is also true for an OS kernel.

I don't understand the point your are making

The blocking (i.e. synchronous) syscalls for IO are doing asynchronous things internally, they just wait for the event to finish before returning. The kernel can even do similar scheduling things to a language runtime, e.g. when a thread does a call to a blocking read on a socket, the thread can be switched out until the socket actually has data, allowing other code to run on that core.

Re: Zero-cost futures in Rust

#317

Earlier quoted context omitted.

And I can't believe I missed this, but the other way is to make the struct generic over a type implementing Future.

Does this work though? How do you create an instance of the struct without an instance of the Future?

    trait Future {}
    
    struct DynamicFoo {
        text:String,
        validating: Option>
    }
    
    struct StaticFoo {
        text:String,
        validating: Option
    }
These are the two options. In the first, Box stores a "trait object", that is, a tuple of (data ptr, vtable ptr). In the second, there will be a struct for each type used to generate a StaticFoo.

    impl Future for i32 {}
    impl Future for f64 {}
    
    fn main() {
        let d = DynamicFoo {
            text: String::from("foo"),
            validating: Some(Box::new(5) as Box),
        };
        
        let s1 = StaticFoo {
            text: String::from("foo"),
            validating: Some(5),
        };
        
        let s2 = StaticFoo {
            text: String::from("foo"),
            validating: Some(5.0),
        };
    }
Works just fine.

Re: Zero-cost futures in Rust

#318
post #260

Earlier quoted context omitted.

If the kernel scheduler completely hands control of the core over to the program, doesn't that mean you can only run one or two programs on the entire machine at once without running out of cores? Surely the kernel still schedules other threads on that core too.

Yes, that's rather the point: we're talking about highly-multicore server machines (e.g. 16/32 cores, or perhaps far more) entirely dedicated to running your extremely-concurrent application. You want all but one or two of those cores just running the app and nothing else. You leave one or two cores for the "control plane" or "supervisor"—the OS—to schedule all the rest of its tasks on. It's a lot like a machine runn…

Is this something Erlang supports, or is it something that you get merely because you've pinned threads to a specific core?

I know with cgroups in Linux you can pin processes to cores pretty easily. Just curious how Erlang makes this easier.

Re: Zero-cost futures in Rust

#319

Earlier quoted context omitted.

> Being able to express your code in a sequential manor and still gain the performance offered by implicit fiber,goroutine,w/e scheduling is pretty awesome. You don't gain as much performance. On Linux, you don't actually gain that much if anything over 1:1 threading. Most of the benefits of goroutines actually comes from the small stacks, which don't have anything to do with M:N and 1:1 to begin with—they're a featu…

> Why would you write networking code in C in 2016, when there are better alternatives available (like this one)? As someone who's done a bit of game development in the past I can easily say that you'll fall flat on your face in some fields if you don't write your own protocols. There are simply no protocols that operate well in every use case and there are also use-cases without standardized protocols. For extremely…

Sure, there are valid reasons to write your own network code. The question is whether you should write it in C. :)

Re: Zero-cost futures in Rust

#320
post #316

Earlier quoted context omitted.

I don't understand the point your are making

The blocking (i.e. synchronous) syscalls for IO are doing asynchronous things internally, they just wait for the event to finish before returning. The kernel can even do similar scheduling things to a language runtime, e.g. when a thread does a call to a blocking read on a socket, the thread can be switched out until the socket actually has data, allowing other code to run on that core.

I am still unclear on how does that correlate to the orignal discussion. The point we were debating is whether or not Golang does async io. I am not sure why the kernel behavior is important here
Post reply on HN