News for hackers. Stuff that matters.
Re:Zero-cost futures in Rust by rdtsc on Thursday August 11, 2016 @02:04PM (#12270405)
As mentioned in the post, given Rust wants to operate in the same space as C, this approach makes sense. However from a higher level, building more complex concurrent systems, dealing with futures/deferred-s/promises and/or a central select/epoll/kqueue reactor loop gets daunting and doesn't mix with complex business rules.

Deferred based approach has been a round for many years. I experienced it by using Twisted (Python framework) for 5 or so years. And early on it was great However when we switched to using green threads, the logic and amount of code was greatly simplified.

So wondering if Rust provides any ability to add that kind of an N:M threading approach. Perhaps via an extension, macro or some other mechanism.

Note that in C, it being C such things can be done with some low level trickery. Here is a library that attempt that:

http://libmill.org/

And there were a few others before, but none have taken off enough to become mainstream.

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @03:19PM (#12270911)
> So wondering if Rust provides any ability to add that kind of an N:M threading approach. Perhaps via an extension, macro or some other mechanism.

I don't want M:N threading as Go implements it. It's a big loss of performance for marginal benefit over futures. In particular the libmill approach was tried in Rust and the results were far worse than 1:1.

However, assuming this takes off I would like to see async/await style syntactic sugar over futures down the road to make it easier to write code that looks blocking but actually isn't. Crucially, this sugar would maintain the zero-cost nature of futures. With that approach, we'd have the ergonomics of languages like Erlang and Go without the significant performance tax associated with M:N.

Re:Zero-cost futures in Rust by sebcat on Thursday August 11, 2016 @03:58PM (#12271162)
> I don't want M:N threading as Go implements it.

As someone who writes code for enterpricey businesses doing a lot of I/O bound stuff, golang style M:N threading is a godsend over Java's standard library, and other common platforms in that space. 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. With futures, there's still some language semantic overhead from the point of view of the developer.

In my problem space, M:N threading is simply worth it. And if I need anything better performing, I can still switch to C for specific use cases.

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @04:09PM (#12271244)
> 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 feature of GC.

As the blog post states, our end goal is to achieve the ergonomics of Go-style M:N without sacrificing nginx levels of performance. With this futures library, we've established the foundations. It would make no sense to give up before even trying.

> And if I need anything better performing, I can still switch to C for specific use cases.

Why would you write networking code in C in 2016, when there are better alternatives available (like this one)?

Rust's philosophy is to have both performance and ergonomics. It rejects the idea that optimal performance requires a cumbersome programming model. I'm not about to give up on that.

Re:Zero-cost futures in Rust by gravypod on Friday August 12, 2016 @09:06AM (#12275259)
> 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 high performance operations you NEED to use a custom protocol in some case to squeeze every bit of power out of your applications.

From a game development perspective you need to handle two types of content: content that isn't really ordered by time occurrence and content that needs to happen sequentially.

Because of this you can correctly optimize your networking stack for those needs and get your networking system really fast.

Some people have taken to using TCP with a JSON message passing system and that just doesn't work large scale. You'll have huge latency issues and with a big enough game you have to do some serious bit-packing to make it cost effective to host servers for.

Now I'm speaking generally and this is just from my experience but there is definitely still a reason to write your own network code.

Re:Zero-cost futures in Rust by kibwen on Friday August 12, 2016 @10:46AM (#12276092)
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 by pcwalton on Friday August 12, 2016 @11:53AM (#12276615)
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 by derefr on Thursday August 11, 2016 @04:27PM (#12271332)
> 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.

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @04:37PM (#12271400)
> 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.

I haven't seen a benchmark of huge numbers of native threads vs. a userland scheduler, but I have a hard time imagining that a userland scheduler will beat the kernel's scheduler. The kernel scheduler has a much more global picture of the system compared to userland.

[1]: https://github.com/rofl0r/musl/blob/d05aaedaabd4f5472c233dbb...

Re:Zero-cost futures in Rust by derefr on Thursday August 11, 2016 @04:46PM (#12271441)
> The kernel scheduler has a much more global picture of the system compared to userland.

In most of the comparisons I've seen (usually for Erlang), worst-case latency was the important factor, so interaction with the kernel scheduler was avoided as much as possible. In the Erlang runtime, you can pass a switch to cause the userland scheduler-threads to each get bound to a particular processor core, and to cause the kernel scheduler to avoid scheduling anything on those cores. Effectively, this partitions the processor into a set of cores the OS scheduler entirely manages, and a set of cores that the userland scheduler entirely manages.

Re:Zero-cost futures in Rust by eridius on Thursday August 11, 2016 @10:22PM (#12273061)
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.
Re:Zero-cost futures in Rust by derefr on Thursday August 11, 2016 @11:45PM (#12273284)
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 running a hypervisor with a single VM on it set to consume 100% of available resources—but actually slightly more efficient than that, since a guest can intelligently pin allocate cores to itself and pin its scheduler-threads to them and then just stop thinking about the pinning, while a hypervisor-host is stuck constantly thinking about whether its vCPUs-to-pCPU mapping is currently optimal, with what's basically a black box consuming those vCPUs.

Re:Zero-cost futures in Rust by bluejekyll on Friday August 12, 2016 @11:53AM (#12276610)
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 by derefr on Friday August 12, 2016 @03:25PM (#12278127)
If you mean that in terms of "can you ask Erlang itself to do this for you", then yes: http://erlang.org/doc/man/erl.html#+sbt

If you mean that in terms of "does the Erlang runtime intelligently take advantage of the fact that its schedulers are pinned to cores to do things you don't get from plain OS-level pinning", I'm not sure.

I think it might, though. This is my impression from reading, a year or so back, the same docs I just linked; you can read them for yourself and form your own opinion if you think this sounds crazy:

It seems like ERTS (the Erlang runtime: BEAM VM + associated processes like epmd and heart) has a pool of "async IO" threads, separate from the regular scheduler threads, that just get blocking syscalls scheduled onto them. Erlang will, if-and-only-if it knows it has pinned schedulers, attempt to "pair" async IO threads with scheduler threads, so that Erlang processes that cause syscalls schedule those syscalls onto "their" async IO threads, and the completion events can go directly back to the scheduler-thread that should contain the Erlang process that wants to unblock in response to them.†

In the default case, if you don't tell ERTS any different, it'll assume you've got one (UMA) CPU with N cores, and will try to pin async IO threads to the same cores as their paired scheduler-threads. This has context-switching overhead, but not much, since 1. the async IO thread is mostly doing kernel select() polling and racing to sleep, and 2. the two threads are in a producer-consumer relationship, like a Unix pipeline, where both can progress independently without needing to synchronize.

If you want, though, you can further optimize by feeding ERTS a CPU map, describing how the cores in your machine are grouped into CPU packages, and how the CPU packages are further grouped into NUMA memory-access groups. ERTS will then attempt to schedule its async IO threads onto a separate core of the same CPU package, or if not possible, the same NUMA group* as the scheduler-thread, to decrease IPC memory-barrier flushing overhead. (The IPC message is still forced to dump from a given core's cache-lines into the CPU or to NUMA local memory, but it doesn't have to go all the way to main memory.)

ERTS will also, when fed a CPU map, penalize the choice in its scheduling algorithm to move an Erlang process to a different CPU package or NUMA group. (It will still do it, but only if it has no other choice.)

---

† This is in contrast to a runtime without "native" green-threads, like the JVM, where even if you've got an async IO pool, it just sees an opaque pool of runtime threads and sends its completion events to one at random, and then it's the job of a framework like Quasar to take time out of the job of each of its JVM runtime threads to catch those messages and route them to a scheduler running on one of said runtime threads.

The same is true of an HVM hypervisor: without both OS support (paravirtualization) plus core pinning inside each VM, a hardware interrupt will just "arrive at" the same pCPU that asked to be interrupted, even if the vCPU that was scheduled on that pCPU when it made the hypercall is now somewhere else. This is why SR-IOV is so important: it effectively gives VMs their own named channels for hardware to address messages to, so they don't get delayed by misdelivery.

Re:Zero-cost futures in Rust by DblPlusUngood on Thursday August 11, 2016 @05:12PM (#12271582)
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 by pcwalton on Thursday August 11, 2016 @05:44PM (#12271801)
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.

Re:Zero-cost futures in Rust by JoshTriplett on Thursday August 11, 2016 @05:48PM (#12271828)
> 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.

Re:Zero-cost futures in Rust by kibwen on Friday August 12, 2016 @10:48AM (#12276112)
Is there a writeup somewhere of the progress towards this, or a prospective timeline?
Re:Zero-cost futures in Rust by sciurus on Friday August 12, 2016 @02:25PM (#12277703)
Re:Zero-cost futures in Rust by ansible on Thursday August 11, 2016 @05:09PM (#12271551)
I haven't seen a benchmark of huge numbers of native threads vs. a userland scheduler, but I have a hard time imagining that a userland scheduler will beat the kernel's scheduler. The kernel scheduler has a much more global picture of the system compared to userland.

Doesn't using kernel threads imply lots of context switches? Doesn't that tend to be expensive in terms of time on modern architectures?

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @05:31PM (#12271702)
> Doesn't using kernel threads imply lots of context switches?

No, it's actually fewer context switches. That's because each I/O completion event goes straight from the kernel to the code that was waiting on it (1 context switch), not from the kernel to the userland dispatcher to the code that was waiting on it (2 context switches).

Re:Zero-cost futures in Rust by wbl on Thursday August 11, 2016 @08:16PM (#12272683)
This is not quite right. The userland dispatcher to run next thread is not a context switch: the TLB doesn't need to get flushed for instance. Furthermore many userland threads can be woken at once, and don't incur context switches when they suspend, prompting the next runnable thread to run.

If you're willing to put in a lot of compiler work userland thread switching is a function call, literally. I don't 1:1 threads with small stack are going to compete with that.

Re:Zero-cost futures in Rust by naasking on Thursday August 11, 2016 @10:28PM (#12273075)
The real cost of a true "context switch" is the transition from user level to kernel level, which takes thousands of cycles. This cost isn't incurred on a userland context switch, so those costs aren't comparable.
Re:Zero-cost futures in Rust by kijiki on Thursday August 11, 2016 @11:15PM (#12273198)
Thousands of cycles for a SYSCALL/SYSRET on a reasonably modern Intel/AMD CPU? I think you should try to measure that.
Re:Zero-cost futures in Rust by DblPlusUngood on Friday August 12, 2016 @12:32PM (#12276923)
"Thousands of cycles" is definitely not true on modern CPUs. SYSENTER/SYSEXIT takes 150 cycles on my Xeon X3460.
Re:Zero-cost futures in Rust by derefr on Friday August 12, 2016 @04:33PM (#12278591)
Given the kind of "cloud"-backed startups most of HN are working on, I think the more practical measurement would be the overhead of a ring 0/3 separation in a VM, plus hypercalls, plus a ring 0/3 separation in the hypervisor (for paravirtualized syscalls); averaged against a ring 0/3 separation in a VM, plus SR-IOV virtualization (for HVM-backed syscalls.)

Yeah, this doesn't apply for context-switches that happen because of plain old pre-emption, but it does happen if the context-switch is because e.g. a network packet wants to arrive at another process in your VM than the one that's currently running.

I would imagine that there's a reason bare-metal IaaS providers have a business model. :)

Re:Zero-cost futures in Rust by naasking on Friday August 12, 2016 @02:02PM (#12277549)
My knowledge of current timings are admittedly a little out of date since my microkernel days are behind me. Even the 150 cycles another poster suggested is still a significant cost over a userland context switch.
Re:Zero-cost futures in Rust by anonymousDan on Thursday August 11, 2016 @06:05PM (#12271953)
What if you have async system calls (e.g. FlexSC)?
Re:Zero-cost futures in Rust by openasocket on Thursday August 11, 2016 @05:34PM (#12271723)
Well, one advantage the Go runtime has is the green threads are cooperatively scheduled, so switches are a lot more lightweight.
Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @05:38PM (#12271748)
> Well, one advantage the Go runtime has is the green threads are cooperatively scheduled, so switches are a lot more lightweight.

Switches occur on timeouts (which involve a round trip through the kernel), on I/O events (which also involve a round trip through the kernel), or on goroutine message passing. So in every case except goroutine message sends, the switches don't actually save you a trip through the kernel.

Re:Zero-cost futures in Rust by saynsedit on Thursday August 11, 2016 @08:00PM (#12272620)
System call context switching is cheaper than and different from thread/scheduler context switching.
Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @08:23PM (#12272713)
OK, but that's irrelevant in this context because Go has to context switch either way.
Re:Zero-cost futures in Rust by zeeboo on Friday August 12, 2016 @12:00AM (#12273320)
It can also switch on memory allocations and function calls as well. I'd expect a large fraction of the context switches come from those events and don't go through the kernel.
Re:Zero-cost futures in Rust by gpderetta on Thursday August 11, 2016 @05:44PM (#12271793)
Except when the IO doesn't go though the kernel in the first place of course.
Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @05:48PM (#12271832)
What I/O doesn't go through the kernel?
Re:Zero-cost futures in Rust by Matthias247 on Friday August 12, 2016 @05:53AM (#12274355)
For I/O you can buffer inside the application whether the respective file is readable/writeable or not. If it's not readable you can directly switch to another goroutine without invoking a syscall. The scheduler will later get notified by select/epoll that the file is readable/writeable, adjust the buffered status and reschedule the goroutine.

But ok, for reading you might often have a buffered state of readable while in reality it's not and you only get this information through EAGAIN on read, so you won't avoid the syscall there. For writing you most likely always would.

Re:Zero-cost futures in Rust by gpderetta on Thursday August 11, 2016 @06:11PM (#12271995)
For the example if you are using kernel bypass of the network stack, which is increasingly common in high throughput/low latency applications.
Re:Zero-cost futures in Rust by haimez on Thursday August 11, 2016 @10:03PM (#12273003)
If you're going to bypass the kernel network stack for performance, you're definitely not going to write the rest of your code in go. The garbage collector, goroutine scheduler, and conservative compile time optimizations will totally undermine the end goal.

If you're in that world, you're using C, C++, or That if you're feeling dangerous.

Re:Zero-cost futures in Rust by pjmlp on Friday August 12, 2016 @03:02AM (#12273793)
The JVM is used a lot in Fintech.

Of course it isn't the OpenJDK, rather specialized JVMs like Azul or they code using C style coding with the GC out of the out paths, having profiled the applications with tools like Java Mission Control.

These are the customers driving Oracles effort for value types, AOT compilation and JNI replacement.

Why not just use C and C++, one might ask?

In spite of all these tricks and required knowledge, salaries are lower than for C and C++ developers and overall project costs are anyway lower due to shorter development times for the other tools not available in C and C++.

Hence why Fintech nowadays is looking into languages like Pony, because they don't want to wait for Java 10, if possible.

So an area where Rust might eventually earn some friends.

Re:Zero-cost futures in Rust by kasey_junk on Thursday August 11, 2016 @11:01PM (#12273159)
I've done kernel bypass work on the JVM.
Re:Zero-cost futures in Rust by gpderetta on Friday August 12, 2016 @09:37AM (#12275486)
FWIW, I wasn't thinking bout go.
Re:Zero-cost futures in Rust by saosebastiao on Thursday August 11, 2016 @07:23PM (#12272419)
Is anybody doing kernel bypass networking in go?
Re:Zero-cost futures in Rust by tomp on Thursday August 11, 2016 @07:45PM (#12272532)
Couldn't you switch on timeout using some kind of CPU counter, e.g. RDTSC?
Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @08:37PM (#12272774)
Then you're polling the system clock all the time, which is a large unnecessary throughput loss.
Re:Zero-cost futures in Rust by tomp on Friday August 12, 2016 @06:13AM (#12274402)
But that's just one instruction, highly paralelisable (I assume), as opposed to going through the kernel... Anyways, I haven't measured it, it's just an idea I had!
Re:Zero-cost futures in Rust by lightcatcher on Thursday August 11, 2016 @05:31PM (#12271701)
> Why would you write networking code in C in 2016, when there are better alternatives available (like this one)?

Support for kernel bypass networking libraries like ibverbs, DPDK (has an old unmaintained Rust wrapper) and other IO kernel bypass libraries such as SPDK and IOAT.

If Rust supported these libraries, I'd much prefer the future based Rust code to a massive event loop in C.

Re:Zero-cost futures in Rust by benlwalker on Friday August 12, 2016 @03:05AM (#12273807)
I'm one of the authors of SPDK (which includes an NVMe driver and an IOAT driver). If the community wants to add rust bindings to those two components I'd be very supportive.
Re:Zero-cost futures in Rust by JoshTriplett on Thursday August 11, 2016 @05:46PM (#12271812)
The article mentioned that the authors would love to help make futures-based wrappers for C libraries. You might ask if they could help with wrappers for ibverbs, DPDK, SPDK, and IOAT.
Re:Zero-cost futures in Rust by bluejekyll on Thursday August 11, 2016 @06:21PM (#12272058)
It's all open-source, this is an area that if it matters this much to you, you could help support. The Rust community is hugely welcoming to people coming on and helping.
Re:Zero-cost futures in Rust by apendleton on Thursday August 11, 2016 @04:37PM (#12271405)
> In my problem space, M:N threading is simply worth it. And if I need anything better performing, I can still switch to C for specific use cases.

It maybe sounds like you should just use Go, which (as you point out) makes exactly those tradeoffs. I think Rust is aiming for a different market segment, with customers that want to make a different set of tradeoffs. Seems ideal to have both options. In particular, I don't think switching to C will ever be something Rust will want to require of its users.

Re:Zero-cost futures in Rust by steveklabnik on Thursday August 11, 2016 @04:07PM (#12271235)
Well, one of the problems with M:N threading is that the "switch into C for that part" gets a lot more expensive, negating the speed increase.
Re:Zero-cost futures in Rust by serge2k on Thursday August 11, 2016 @08:09PM (#12272653)
> I can still switch to C for specific use cases.

So use go and switch to rust for specific use cases.

Re:Zero-cost futures in Rust by rdtsc on Thursday August 11, 2016 @04:24PM (#12271319)
> sugar over futures down the road to make it easier to write code that looks blocking but actually isn't.

Yes! That is basically what I am after.

Currently using Erlang so having to give up cheap isolated processes and message passing to do concurrency would be hard. But I understand that is a different level of abstraction as well (a VM, multi-machine clusters, etc vs almost raw C speed).

But in general bringing Rust and Erlang together seems like a swell idea. Both are focused on safety, which I like. Been keeping my eye on https://github.com/hansihe/Rustler

Re:Zero-cost futures in Rust by yawaramin on Thursday August 11, 2016 @07:53PM (#12272586)
> ... I would like to see async/await style syntactic sugar over futures down the road to make it easier to write code that looks blocking but actually isn't....

Would a Scala-style 'for' comprehension achieve the same thing while also being more generally applicable? Scala achieves this by using instance methods 'map', 'flatMap' and 'withFilter'. Personally I think Rust should just lift the sugar wholesale :-)

Re:Zero-cost futures in Rust by stshine on Friday August 12, 2016 @02:19AM (#12273675)
Simply comparing M:N threading with futures is not correct. In my experience using futures in Javascript, it does not support control flow at all. The goroutine's equivalent is async/await, which transform the cps of asynchronous I/O back to sync form, but it may impose more overheads than M:N threading since it stores the stack structure in its parameter.
Re:Zero-cost futures in Rust by saynsedit on Thursday August 11, 2016 @03:44PM (#12271074)
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?

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @04:02PM (#12271194)
> Are libmill and Go's approach the same? I.e. Holding concurrent state in multiple stack frames? What causes that to have degraded performance?

In the M:N approach you have to allocate stack space for each goroutine that you spawn. This requires that you either know the size of the stack up front (generally not possible without being conservative and requesting a large allocation) or that you start small and grow (resulting in a lot of memory traffic and pauses in the growth case, and much harder to do in C).

By contrast, with the zero-cost futures approach we statically know exactly how much per-goroutine size we will ever need, and we can allocate precisely that amount. Furthermore, we only save the data that's absolutely needed across blocking calls. This results in much smaller per-connection state, and as a result it's quicker to allocate.

It's the difference between static and dynamic control flow. Full M:N requires us to give up static knowledge of what a goroutine will do and try to do the best we can at runtime. With futures, we have a lot more static knowledge, and as a result we can optimize more aggressively.

Re:Zero-cost futures in Rust by mindslight on Thursday August 11, 2016 @08:30PM (#12272742)
> This requires that you either know the size of the stack up front ... generally not possible without being conservative

Well, you are writing the compiler. Sure you'd be up against the halting problem, but relatively few functions are (non-tail) recursive.

Perhaps the unwieldiness of a large stack is better attributed to the feature of unbounded recursion (and FFI into "uncharted territory") than the feature of green threads.

I appreciate that Rust has already been down the road of lightweight threads. This statement just struck me as an assumption that deserved to be questioned.

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @08:41PM (#12272783)
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.

Re:Zero-cost futures in Rust by DannyBee on Friday August 12, 2016 @02:31PM (#12277759)
"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."

You can actually do a pretty good job without k-CFA.

First, and i know you know this, when you say "fail to produce a bounded stack size", you really mean "a reasonable bounded stack size".

A bounded stack size of easy, and does not require context sensitive analysis: The stack size is just max sum of stack sizes of of meet over all paths in an acyclic graph :)

The cycles, you either can statically calculate the recurrence count or you can't.

Bounded heap size is pretty much impossible, but stack size is pretty easy.

Recursion is also not hard. You form strongly connected components. You try to prove how often the component is cycled. If you can, victory. If you can't, you can bound it unless it's truly dynamic.

More to the point, here's a paper on doing it with ADA in GCC in 2009 (IE with sticks and fire):

http://www.adacore.com/uploads/technical-papers/Stack_Analys...

Note they are within 2% on most cases, and can detect whether it's statically knowable, bounded, or dynamically changing.

Re:Zero-cost futures in Rust by fmap on Friday August 12, 2016 @10:10AM (#12275749)
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 language looks very different from C or even Rust code. Sparks in Haskell, or processes in Erlang, or even individual goroutines in Go, are typically executing comparatively tiny programs. If the call graph of the program in question is acyclic, then it should be easy to get a bound on the maximum stack size.

To be clear, I agree with you that a 1:1 threading model is the better design for Rust, simply because it's the only design where you can guarantee zero overhead and don't need a complicated runtime. What I don't agree with is that the M:N threading model is inherently inferior. Especially for a high-level programming language, I would assume that you can reduce the overhead significantly through static analysis and a good implementation.

Re:Zero-cost futures in Rust by saynsedit on Friday August 12, 2016 @10:30AM (#12275939)
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 by mindslight on Thursday August 11, 2016 @09:11PM (#12272867)
I was thinking of a much lighter analysis, based on some optional restrictions. If a function:

- is not recursive

- does not call function pointers (ie trait objects)

- allocates only fixed-sized objects on the stack

- only calls functions with known stack requirements

then its stack requirement should be known, no? It feels like with these requirements, one can still write many programs (threads, really). And if one goes beyond the restrictions, then they just pay the cost of having to guess a large stack size.

Stack size analysis would be helpful for other applications as well, like embedded platforms.

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @11:55PM (#12273310)
No program beyond the most trivial will meet these requirements. The moment you call any function indirectly you lose.
Re:Zero-cost futures in Rust by mindslight on Friday August 12, 2016 @12:04AM (#12273331)
Yes, but indirectly means "&Fn", but not "&F where F: Fn".

And a whole program doesn't need to conform, only individual threads. Presumably the ones you want to make a lot of.

(And if a little dynamicism was required, its expense could be paid for at the use, by creating a fresh necessary-sized stack at that point. But I'm probably opening up old split-stack wounds, sorry)

Re:Zero-cost futures in Rust by jroesch on Friday August 12, 2016 @01:06PM (#12277156)
The problem with an optimization like this is that the current approach always gives you a guarantee on runtime cost. Heuristic based optimizations make performance harder to ensure, not to mention recovering performance when you fall outside the valid subset much more difficult. You can obviously provide another static analysis to warn you about violations, but this seems is more complex, and less flexible then the current approach.
Re:Zero-cost futures in Rust by mindslight on Friday August 12, 2016 @02:29PM (#12277741)
Actually the "current approach" gives you no bound on the stack size. You guess, hope, and test (or do this analysis manually).

While your general objection is good to always keep in mind, it is a tradeoff. There is no perfect solution when you're up against the halting problem (unless you're proposing to change the language to only bounded recursion).

What I'm arguing for here is for more than the single problem the OP is solving, so it's not a case of choosing one or the other and calling it a day.

Re:Zero-cost futures in Rust by Matthias247 on Friday August 12, 2016 @06:09AM (#12274390)
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 futures that hold dynamically allocated data the state might be spread over much more memory locations, so it might be slower to access. I however haven't any more scientific data on this.

In my real-world applications I'm getting about the same performance from a Go based and a boost asio (uses no futures but dynamically allocated callback closures for storing state) networking application. However the programming style is completely different.

Re:Zero-cost futures in Rust by saynsedit on Friday August 12, 2016 @10:35AM (#12275985)
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 by ngrilly on Saturday August 13, 2016 @02:00PM (#12282357)
> With that approach, we'd have the ergonomics of languages like Erlang and Go without the significant performance tax associated with M:N.

Can you quantify the "performance tax associated with M:N"?

Re:Zero-cost futures in Rust by Thaxll on Thursday August 11, 2016 @04:40PM (#12271415)
What is the "significant performance tax" in Go? Do you have any numbers about this implementation?

When I look at the benchmark of your minihttp vs Go fasthttp, Rust is supposed to be 3x time faster without any GC and yet minihttp pulls 2M req/sec VS 1.5 for Go which is a small margin when you know how Go works and is GC.

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @04:48PM (#12271446)
> What is the "significant performance tax" in Go?

I explain in this sibling comment: https://news.ycombinator.com/item?id=12271194

> Do you have any numbers about this implementation?

See the blog post.

Re:Zero-cost futures in Rust by Thaxll on Thursday August 11, 2016 @04:55PM (#12271476)
I did edit my first question after seeing the numbers.
Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @05:07PM (#12271536)
Arguing about whether a 30% difference is "significant" isn't really interesting.
Re:Zero-cost futures in Rust by Thaxll on Thursday August 11, 2016 @05:46PM (#12271811)
You're talking about using a language where you have to manually manage the memory, is significantly harder to use than Go, have less built-in concurrent patterns, inferior standard library, so yes 30% seems a very small benefit.

I didn't had look into the benchmark but my guess is that it's waiting on the I/O so any language with good concurrent model will have the same numbers.

The benchmark would have been more interesting if it was more CPU intensive, that would have definitely favored Rust.

it's not about Rust Vs Go, it's just that 30% seems very low when you know how fast Rust is vs Go in some scenarios.

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @05:50PM (#12271846)
> You're talking about using a language where you have to manually manage the memory, is significantly harder to use than Go, have less built-in concurrent patterns, inferior standard library so yes 30% seems a very small as a benefit.

Naturally I disagree with all of those items, but I'm also equally uninterested in starting a Rust vs. Go war.

Re:Zero-cost futures in Rust by stuartd on Thursday August 11, 2016 @04:12PM (#12271268)
> ergonomics of languages like Erlang and Go

My brain read that as "ergolangmics"

Re:Zero-cost futures in Rust by davidgrenier on Thursday August 11, 2016 @03:09PM (#12270849)
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 informal CSP yet which seems to be the reason why Go and Clojure have picked it as well for their concurrency model.

See David Nolen's excellent blog posts on the matter:

http://swannodette.github.io/2013/07/12/communicating-sequen... http://swannodette.github.io/2013/08/17/comparative

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @03:24PM (#12270942)
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 and then layer zero-cost abstractions on top to achieve the right ergonomics.
Re:Zero-cost futures in Rust by jholman on Thursday August 11, 2016 @05:16PM (#12271608)
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 by pcwalton on Thursday August 11, 2016 @05:30PM (#12271692)
> 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.

Re:Zero-cost futures in Rust by gpderetta on Thursday August 11, 2016 @05:30PM (#12271696)
This is currently hotly debated in the C++ committee. Some people want shallow C#, python style generators, while other want proper stackful coroutines a-la Lua (full disclosure: I'm on this group). A third group is trying to mediate and trying to come up with an hybrid stackful model that can be optimized as well the async/await model at least in some cases (I.e. full cps transform and fallback to a cactus stack when invoking non transformable functions).
Re:Zero-cost futures in Rust by wahern on Saturday August 13, 2016 @01:30AM (#12280399)
I've been writing async I/O networking software for about 15 years now. Early on most of that was in C, now it's split about 50/50 between C and Lua. Most of my C I/O code is still in C because I prefer my libraries to be reuseable outside of Lua or any particular event loop, and they often are. Lua's coroutines are usually higher up the stack, juggling more abstract state; and I use them for more than asynchronous I/O or asynchronous tasks.

The thing about async/await is that in a language like C, I can already accomplish much of that with tricks like Duff's Device and macros. It has its limitations, but IME they're not much more onerous than the limitations of async/await, especially in the context of a language lacking GC. I have to manually keep state off the stack (or otherwise copy/restore it), but you do that anyhow when you don't have lexical closures and GC, and often even when you do.

The beautiful thing about coroutines in Lua is that it's based on a threading model, but not one bound to the C stack or a kernel thread, which are completely orthogonal concerns left to the application to deal with or not deal with. And it does this while preserving functions as first-class objects. Neither callers nor callees need to know anything about coroutines. That kind of composability makes coroutines useful and convenient for many more things than simulating green threading or managing CPU parallelism. Among other things, it means I can mix-and-match functional and imperative styles according to the problem, and not whether it will be convenient to then make use of coroutines. It means that I have a single, natural call stack--not an implicit stack and an explicit stack. async/await and futures unify your data stack, but you're still manually managing the call stack through syntactic devices or otherwise formalized calling conventions. However heavily sugared, it will hinder the design of your software no less than if you had to manually manage the data stack, too.

Coroutines that aren't stackful aren't nearly as powerful in terms of problem solving. Without them being stackful, it's a horribly leaky abstraction for non-trivial uses. Most people would agree that the C preprocessor is a mess, and that functions as first-class objects are powerful. So modern languages strive to create templating systems that allow you to construct _real_ functions that are indistinguishable from any another function. But then they introduce monstrosities like futures or async/await, that beautiful symmetry is broken. It's like bringing back C's macro preprocessor--now you have regular functions and these weird things with different syntactic and control follow semantics, whether you wanted it or not. The decision is no longer yours, which means you're bending to the language's deficiencies.

Why even bother with such half-baked solutions? In almost every case it's utterly transparent that these solutions exist for the benefit of the compiler and runtime author, usually because of intentional or unintentional technical debt--a direct or indirect dependency on the C or kernel stack. For C++ it's understandably a difficult dilemma, but for every other language it's a total cop-out.

Then these solutions are sold to the public by prettifying the implementations with fancy terminology and beguiling examples showing how they can be used to implement async I/O or parallel CPU jobs. But few, if any, language features are so narrowly tailored to such specific use cases. Why? Because languages are supposed to provide simple building blocks that compose as seamlessly as possible at a much higher level of abstraction than, e.g., a slightly nicer way to implement HTTP long-polling servers. Such contrivances are as removed from the basic simplicity of the function as C's macros are from first-class functions. In both cases you can implement solutions for a certain subset of problems that superficially look convenient and nice; but in the real world the limitations become swiftly apparent, and you realize a lot of effort was spent in design and implementation for little real-world gain.

With Lua's coroutines, I can implement a futures pattern easily when it's appropriate, and it will be more powerful because the futures themselves can make use of coroutines both internally and externally. But in my use of coroutines in Lua futures are rarely the most natural design pattern. Sometime you want a full-blown CPS solution, sometimes you simply want to be able to arbitrarily swap producer/consumer control flow, for example in a lexer. Often you want a mixture of all of these. Coroutines--stackful coroutines--provide all that and more, seamlessly.

Futures only look nice and elegant in contrast to event loop oriented, callback-style programming. But that's a really, really low bar. Please aim higher, people!

Re:Zero-cost futures in Rust by domenicd on Saturday August 13, 2016 @09:43AM (#12281469)
> Why even bother with such half-baked solutions? In almost every case it's utterly transparent that these solutions exist for the benefit of the compiler and runtime author, usually because of intentional or unintentional technical debt--a direct or indirect dependency on the C or kernel stack.

There is a significant faction of language designers that disagree, and think that keeping coroutines shallow is important for developers writing and reading the code. This post from Dave Herman<(involved in JavaScript and Rust) sums it up: http://calculist.org/blog/2011/12/14/why-coroutines-wont-wor.... (The comment from Tom van Cutsem is also a good rephrasing: http://disq.us/p/9jcee9.) Note that the argument is not as applicable in languages with racy multithreading (like C/C++ or Java).

I don't think it's necessarily a knockout argument, but it at least helps me sleep with what we've chosen for JavaScript.

Re:Zero-cost futures in Rust by gpderetta on Saturday August 13, 2016 @02:57PM (#12282608)
I never understood this argument, it feels to me a post hoc rationalisation. Yes, with toplevel only yield you know all suspension points, but doesn't really buy you anything, as calling an unknown function can potentially mutate any object possibly by invoking user callbacks or other generators. If the function behaviour is well documented, then whether it is a suspension point would be as well.
Re:Zero-cost futures in Rust by domenicd on Sunday August 14, 2016 @07:18PM (#12287772)
The difference is that when you call a function, you can easily know what will happen: the function will execute. You can use your knowledge about the function you are calling (and the functions it calls, etc.) to ensure it does not violate any invariants you set up.

Whereas, if you have an implicit yield point that goes back to the event loop, the event loop can run arbitrary other tasks---not ones you can locally reason about or predict, but simply those that are ready to be executed.

Re:Zero-cost futures in Rust by gpderetta on Monday August 15, 2016 @10:51AM (#12290794)
But if you know what a function does and call you would also know whether it is an implicit yield point or not, right?

Also, I don't know about JS, but many event loops can be reentered recursively, so even with top level only yield all bets are off.

Re:Zero-cost futures in Rust by wahern on Monday August 15, 2016 @05:35PM (#12293661)
Was that a correct link? I feel like it only re-iterates my points.

My point is that thinking about coroutines in the context of green threading is totally the wrong way to think about it. That you can implement something approximating green threads with coroutines is a testament to the power of coroutines, but it's hardly the defining the feature for them.

And coroutines are not sufficient to implement green threading. You still need a way to have multiple outstanding I/O requests. That could have been done with other mechanisms. User code could wrap such mechanisms with coroutines and fashion an event loop if they desired, and no doubt most would have done that. But by leaving that up to the application people could experiment with patterns for addressing concerns regarding concurrency. And I would also note that concurrency problems related to order of operations hardly go away with futures, the preferred solution in JavaScript, or async/await. Stackful coroutines can theoretically be worse when callees can yield from any expression, but don't forget that the real problem is shared mutable state, which you're passing or otherwise making available in equal measures for each option. For that and other reasons the distinction with futures and async/await is, I think, not very meaningful.

For similar reasons, Rust's failed experiment with green threading is not an argument against the practicality of stackful coroutines. Quite the contrary--it's an example of why it's more important to focus on the abstraction and preserving the power of that abstraction than to tailor the solution for specific scenarios. Rust could easily have had stackful coroutines with zero performance cost and negligible cost to the language design. But instead they focused on coroutines as a roundabout way to ease the burden of async I/O, and, worse, they tried to refactor Rust's entire standard I/O library to work with that green threading model. It was a destined for failure, and for good reason.

When discussing coroutines, my favorite example is something like libexpat. libexpat was early on in the history of XML the most widely used XML parser. But it was a push parser. Push parsers are easier to implement and often more performant, but they're more difficult to use. All of those qualities stem from push parsers literally and figuratively pushing the burden onto the application for solving issues related to state management and buffering.

You couldn't easily refactor libexpat into a pull parser because token production relied on automatic variable stack state and internal stack structures. You'd have to copy and buffer everything it produced. No wonder so many people either forked libexpat or just reinvented the wheel.

If C had stackful coroutines, it would be _trivial_ to make libexpat a pull parser. Heck, trivial undersells how simple and elegant it would be. In that context coroutines could have provided the best of every and all worlds, for both libexapt developer(s) and direct and indirect users.

The narrative around coroutines has been poisoned by this narrow focus on async I/O and similar contemporary problems, and conflation and equivocation with fibers, kernel threads, and the low-level details of platforms' C ABIs. It's created lost opportunities for providing stronger language abstractions.

Perl 6 committed a similar sin, IMO. MoarVM technically can support stackful coroutines, but it doesn't because they're only implemented to support Perl's gather/take control structure. That coroutines could have easily been used to implement gather/take entirely within application code, but not vice-versa, should have been a strong hint that coroutines were the stronger abstraction, and gather/take should have been defined as standard API utilizing proper stackful coroutines.

Note to future language designers: coroutines are not about async I/O. Coroutines are not about green threads. Coroutines are not about map/reduce. Don't conflate the means with the ends. Stackful coroutines can be used to implement all of those and more because they're the better abstraction. Lua's stackful coroutines can be used to easily implement green-threading like async I/O, or non-trivial map/reduce patterns, not because the Lua authors bent over backwards to make that possible, but because they preserved their abstractive power; because they modified both the language language design and implementation so stackful coroutines didn't come with needless caveats.

Re:Zero-cost futures in Rust by soulbadguy on Thursday August 11, 2016 @05:25PM (#12271659)
> 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 by pcwalton on Thursday August 11, 2016 @05:35PM (#12271730)
> although i think the approach used by C++ coroutine is better

How?

> 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.

It's more like "everything is synchronous" in the Go model. Semantically, Go doesn't have async I/O at all. It has a userspace M:N implementation of synchronous I/O. You can get the same effect in any other language by just not using async I/O.

> Not having to decide(and design) upfront which part of your computation is async and which is not is what makes go so attractives

That doesn't make sense to me. If async I/O is important in your app, why not just make your entire app use async I/O?

Re:Zero-cost futures in Rust by skybrian on Thursday August 11, 2016 @06:10PM (#12271988)
Defaults matter. If some people use async I/O and others don't then you get a mess when they want to share reusable libraries. It's similar to the mess you get when there is more than one string type.

I think the "what color is your function" problem could be mostly solved by making async/await the default function type - that is, most callback functions should be allowed to suspend execution by waiting on a Future.

Then you could have special-purpose "atomic" functions that are occasionally useful for pure functional code.

(Unfortunately, the default has to be the opposite in browser-based languages due to performance concerns.)

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @06:22PM (#12272064)
> (Unfortunately, the default has to be the opposite in browser-based languages due to performance concerns.)

Also in Rust. Most apps that aren't servers don't want async I/O, and it causes a lot of problems when you need high-performance FFI. For example, in Servo async-everywhere would be a big problem for WebRender, which needs to be able to call OpenGL extremely quickly.

> Defaults matter. If some people use async I/O and others don't then you get a mess when they want to share reusable libraries. It's similar to the mess you get when there is more than one string type.

Given that having both is necessary for Rust (maybe a necessary evil), I think the right approach is to make jumping from one mode to the other painless. For sync-to-async, it needs to be easy to block on the result; for async-to-sync, it needs to be easy and fast to offload to a thread pool. If we can make it really easy to switch from one mode to the other, then most of the really hairy problems go away.

Re:Zero-cost futures in Rust by zeeboo on Thursday August 11, 2016 @08:05PM (#12272644)
Easy and error prone.

Sometimes libraries pretend to be async and accidentally are sync. Consider some library that in the normal case just does some pure computation, but logs to syslog or something on some error condition. If you use that library in an async context, it could work fine most of the time, until you hit some unexpected situation where it happens to make a network request to some syslog daemon and blocks up your worker thread. The same thing can happen with mutexes, or many other common blocking operations.

It's also the case that often async libraries depend on some sync library and so they have their own worker pool. You can easily have many libraries with their own worker pools all using more resources then they need.

You also have to worry about if your functions do any of these transformations under the hood. For example, if you have some async worker that delegates some sync task to the worker pool, and that sync task happens to use some async function and blocks on it, and that async function ALSO has a sync task and attempts to delegate it to a worker pool, and that worker pool is bounded, then you have just opened yourself up to a deadlock under high load that you probably won't find under normal operation.

On top of all that, debugging is usually much harder in these environments because you have to inspect the runtime memory state that depends on the specific internal details of the libraries being used instead of a bunch of stacks. It's extremely hard to diagnose deadlocks or stalls in these environments. It's non-trivial to provide a good debugging experience that doesn't cause extra load in production environments.

These issues are all real things that I have hit in production with Twisted. A static type system could help all these things, but I think it requires buy in from every library you might use, transitively.

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @08:35PM (#12272766)
> It's also the case that often async libraries depend on some sync library and so they have their own worker pool. You can easily have many libraries with their own worker pools all using more resources then they need.

The Rust story will not be complete without a canonical single implementation of a thread pool that everybody doing async I/O uses for blocking tasks.

> For example, if you have some async worker that delegates some sync task to the worker pool, and that sync task happens to use some async function and blocks on it, and that async function ALSO has a sync task and attempts to delegate it to a worker pool, and that worker pool is bounded

I think the solution here is "don't have strictly bounded worker pools". This is what cgo does, I believe.

> It's non-trivial to provide a good debugging experience that doesn't cause extra load in production environments.

But this is the exact same problem that any M:N system will have. So I don't see any special problem for Rust's system here.

Re:Zero-cost futures in Rust by zeeboo on Thursday August 11, 2016 @09:44PM (#12272953)
I hope your optimism that a single kind of thread pool will service all applications is well founded. It seems like people would want to specialize them much like they want to specialize their memory allocators. The Rust team has a really great track record of innovation and technical excellence so I look forward to the design that will accomodate that and hope the ecosystem buys off on it.

Go does limit the number of threads and will crash the process if it goes over. It's also very rare to have CGo call back into Go multiple times versus libraries juggling adapters between async and sync in my experience. It's also easy to have your library have a limiter on the number of CGo calls you make, but less easy to limit the number of tasks you throw into a thread pool because you don't have the option to block. (edit: I think you can just store the closure on the thread pool and schedule it eventually at the cost of writing your own scheduler and perhaps requiring allocations?) I have a feeling that a similar crashing solution won't work in the Rust community, and what to do when the limits are hit will be punted upstream. My main point is that there are many subtle details in solving the "colored function" problem.

I don't think all M:N systems have the debuggability problem becausd the runtime has a single canonical representation of what is running: the stack traces. Since the entire ecosystem bought into the runtime, you don't have any fracturing of representations. If you're optimisitc that the entire ecosystem will buy into whatever mechanism you have to do async work, then this can be solved, but I believe that's already not the case (people hand code state machines) and is unlikely to stay the case as time goes on.

Re:Zero-cost futures in Rust by Matthias247 on Friday August 12, 2016 @06:26AM (#12274444)
> Given that having both is necessary for Rust (maybe a necessary evil), I think the right approach is to make jumping from one mode to the other painless. For sync-to-async, it needs to be easy to block on the result; for async-to-sync, it needs to be easy and fast to offload to a thread pool. If we can make it really easy to switch from one mode to the other, then most of the really hairy problems go away.

That really sounds like the Task<T> type from C# TPL, which can also be used in sync and async environments and was probably also designed to be a generic solution. While it basically works there's a bigger number of pitfalls associated with that model. E.g. you can synchronously block on .Result from some tasks (that will be fulfilled by other threads), but not from others (which would be fulfilled by the same thread, because that causes a deadlock). In the Task+Multithreading world there's also always the question where continuations (attached with .ContinueWith, .then, ...) run. E.g. synchronously in the thread that fulfills the promise, asynchronously in a new eventloop iteration (but for which EventLoop?), in an explicitly specified scheduler, etc. C# uses TaskScheduler and SynchronizationContext variables for that. But as they are only partially known and even behave somewhat different for await Task and Task.ContinueWith there's quite some room for confusion.

Re:Zero-cost futures in Rust by bmurphy1976 on Thursday August 11, 2016 @08:06PM (#12272646)
> Also in Rust. Most apps that aren't servers don't want async I/O, and it causes a lot of problems when you need high-performance FFI. For example, in Servo async-everywhere would be a big problem for WebRender, which needs to be able to call OpenGL extremely quickly.

I don't understand why this is the case. Since async/await allows the compiler to transform the code into a state machine, why would it be not be able to optimize this?

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @08:36PM (#12272768)
Because async-everywhere usually means pushing blocking FFI calls over to a thread pool, which would be unacceptably slow for e.g. OpenGL.
Re:Zero-cost futures in Rust by bmurphy1976 on Thursday August 11, 2016 @09:12PM (#12272873)
"Usually" is a funny word, but I see what you are saying.

I guess if you are in a single-threaded event loop scenario (ala nodejs) you could get away with it somewhat, but as soon as you introduced multiple threads of execution all bets are off.

That's unfortunate.

We have a fairly large codebase written in .NET. We wouldn't mind porting it to CoreCLR, but we'd have to migrate everything to async/await. The red/blue nature of method signatures makes this look like almost a complete rewrite. Given the difficulty of this migration so far, and the nature of the change, it's certainly caused us to explore other options and we've already rewritten a significant chunk of the code in Go.

Moving a large codebase from a single color model to a dual color model really sucks. I hope Rust can lock this down sooner rather than later otherwise a lot of people are going to feel pain down the road.

The good news is you have a good static type system. I cannot even begin to imagine migrating a Python codebase to async/await...

Re:Zero-cost futures in Rust by soulbadguy on Friday August 12, 2016 @12:53PM (#12277068)
> How ? In term of allocation : When the future uses some variable present on the current function stack you have two options

1 - Waiting for the future to complete before exiting the current function (which essentially is blocking)

2 - Allocating the closure on th heap (allocation + deallocation)

In a language with coroutine support , we have a third alternative. Instead of block or allocating memory, it's possible to just suspend the current function (no heap allocation necessary, no blocking), and resume when the future completes.

In term of context switching speed : the cost of moving the state machine is essentially the cost of a double dispatch (probably double dispatch plus virtual function call), switching coroutines is closer to the cost of a function call ( i think it's cheaper than a normal function call, but tha becomes too technical)

Re:Zero-cost futures in Rust by kbenson on Friday August 12, 2016 @07:45PM (#12279522)
I just watched (mostly) the CppCon talk you posted elsewhere. The coroutine approach is really interesting, but I'm confused as to how it's different. According to a source I found[1], the way coroutines are implemented is that a new stack is created on the heap and it moves back and forth between that. Isn't that the same case here? Is the compiler level implementation(as opposed to boost, as in the linked reference) different in some way?

1: http://stackoverflow.com/questions/121757/how-do-you-impleme...

Re:Zero-cost futures in Rust by zzzcpan on Thursday August 11, 2016 @07:36PM (#12272482)
> The main advantage of the go model is that both asynchronous and synchronous operations are identical

There is a bit of misunderstanding on your part. There are no asynchronous operations in that go model, everything is synchronous. There is no event loop underneath, despite what some people claim. And this is absolutely not an advantage in a shared memory environment. Instead it forces you to do synchronization to access memory, while different-looking asynchronous operations do not precisely because they are different-looking, otherwise it would be impossible to know what is running when and you would have to think about synchronization too.

It is not a secret that shared memory multithreading, while useful for parallelism, is the worst possible model for concurrency and this includes all flavors of coroutines as well.

Re:Zero-cost futures in Rust by soulbadguy on Thursday August 11, 2016 @11:18PM (#12273213)
> There are no asynchronous operations in that go model, everything is synchronous.

You are the second person saying this, and i have to admit i am really confused by this statement. From my understanding, golang does not expose an asyncio interface, but this doesn't meant that golang runtime doesnt perform io operations asynchronously. So golang expose async operation through a synchronious interface, which is what most language construct (C# async/await, F# async monad etc..) try to emulate.

From https://morsmachine.dk/go-scheduler , when a goroutine performs a blocking operation, the local scheduler the remaining goroutine are migrated to another OS thread, allowing them to continue their execution while the blocking operation completes asynchroniously.

Re:Zero-cost futures in Rust by pcwalton on Thursday August 11, 2016 @11:57PM (#12273315)
> 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.

Re:Zero-cost futures in Rust by soulbadguy on Friday August 12, 2016 @07:45AM (#12274787)
I don't understand the point your are making
Re:Zero-cost futures in Rust by dbaupp on Friday August 12, 2016 @11:27AM (#12276434)
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 by soulbadguy on Friday August 12, 2016 @12:22PM (#12276842)
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
Re:Zero-cost futures in Rust by dbaupp on Friday August 12, 2016 @01:25PM (#12277290)
The Go programming model is synchronous: the code executes an IO operation and that thread of execution halts until the operation completes. The Go runtime is implemented using asynchronous IO internally, and manages scheduling between threads itself, but that is an implementation detail.

This is exactly the same as using normal blocking operations with OS threads. That programming model is synchronous: the code executes an IO operation and that thread of execution halts until the operation completes. The kernel is implemented using asynchronous IO internally, and manages scheduling between threads itself, but that is an implementation detail.

The original point is Go's programming model is the same as OS-level blocking IO, the fact the runtime is implemented in user-space on top of async IO is an implementation detail that doesn't change the surface behaviour of the code. One could substitute OS threads and normal OS blocking calls for goroutines and the runtime's IO operations and code would behave essentially identically, just possibly with different performance characteristics.

Re:Zero-cost futures in Rust by namelezz on Thursday August 11, 2016 @03:27PM (#12270970)
> I'm not aware of anyone offering an alternative superior to an informal CSP yet which seems to be the reason why Go and Clojure have picked it as well for their concurrency model.

What about Quasar [0] on the JVM?

0 - http://docs.paralleluniverse.co/quasar/

Re:Zero-cost futures in Rust by davidgrenier on Thursday August 11, 2016 @11:52PM (#12273306)
But Quasar does seem to offer go-like channels? I'm unclear what kind of combinators are provided but those could be implemented.
Re:Zero-cost futures in Rust by Matthias247 on Friday August 12, 2016 @06:33AM (#12274473)
It does offer channels, see the linked Docu.

For combinators I don't think those are really needed in the CSP environments because you can do most transformations with normal function calls and normal control flow contructs (loops, if, ...)

Re:Zero-cost futures in Rust by steveklabnik on Thursday August 11, 2016 @02:08PM (#12270425)
Yes, this is the lowest layer of the story. For actually writing an app, writing something higher-level makes sense: consider the relationship between raw futures and Finagle, for example. You have to build out the lower layers before you can add the upper ones, though.

Rust doesn't natively supply anything like green threads, as they have too heavy of requirements. But there are librarys that can implement them, since Rust is so low-level: https://crates.io/crates/mioco as an example.

Re:Zero-cost futures in Rust by int_19h on Thursday August 11, 2016 @03:58PM (#12271166)
Dealing with futures is fairly straightforward if the language offers some syntactic sugar for continuations (such as C#'s async/await that has been spreading around for some time now). Why doesn't it mix well with complex business rules?
Re:Zero-cost futures in Rust by davidgrenier on Thursday August 11, 2016 @10:08PM (#12273025)
I'm not sure I would go as far as saying they can't. I'm quite comfortable with F#'s async computation expression and like them a lot.

You get a strange sense however when playing with Hopac (which btw has an analog sugar to F#'s async which works just fine) and going through the content in Reppy's book that the model is strictly more composable. I'd invite you to try it although I don't have a sense for the C# combinators.