|
|
Log in / Subscribe / Register

Rust Keyword Generics Progress Report: February 2023

Rust Keyword Generics Progress Report: February 2023

Posted Mar 8, 2023 21:45 UTC (Wed) by bartoc (guest, #124262)
In reply to: Rust Keyword Generics Progress Report: February 2023 by rrolls
Parent article: Rust Keyword Generics Progress Report: February 2023

The problem with "the ruby way" (fibres) is that you still need to rewrite the whole runtime to support them (since IO routines need to be taught how to switch tasks) and you don't really save any resources over just making a normal thread. At best you can stop allocating stacks (both the kernel stack and the user stack) for each task, but usually you just save the kernel stack. And if you _can_ eliminate both stacks that means your language / runtime heap allocates basically everything. The only other option is to get very, very, very clever often at the expense of some safety or adding limitations on the depth of coroutine invocations (I think zig takes this approach).

These sorts of runtimes also tend to be bug-prone because tasks can call out to libraries that are unenlightened and use things like thread local storage and get surprised when the values change out from under them as a task gets resumed on another "real" thread. This isn't a problem if you only have one "real" thread, but these sorts of systems usually want to use one thread per CPU.

Also, the performance advantages of fibre-like schemes over "just using a real thread" are not that pronounced anymore, they became popular in the days where most operating systems had "one big lock" around the whole scheduler, that's no longer true and so normal OS schedulers scale much better with large numbers of threads and cores now, making these sorts of N:M fibre schemes a little pointless.


to post comments

Rust Keyword Generics Progress Report: February 2023

Posted Mar 10, 2023 2:05 UTC (Fri) by Cyberax (✭ supporter ✭, #52523) [Link]

> At best you can stop allocating stacks (both the kernel stack and the user stack) for each task, but usually you just save the kernel stack. And if you _can_ eliminate both stacks that means your language / runtime heap allocates basically everything.

Rust's async (or JavaScript's, or Python's) is basically isomorphic to segmented stacks. You save your true stack in a linked list of heap-allocated objects, and the system/kernel stack is only borrowed to run coroutines. You only need a handful of real threads, and there is no problem with having millions of coroutines.

The problem is the speed. Go tried essentially this approach earlier in its life, and segmented stacks failed because they can cause unpredictable and horrible slowdowns when a tight loop crosses over the segmentation threshold.

Instead, now Go uses moveable and resizable stacks, which provide the best of both worlds. This is possible because Go can maintain an invariant that no pointer on the heap can point to an object on the stack. So the runtime can just use contiguous stacks, without any penalty for normal functions. At the same time, the minimum stack size can be very small (2kb for Go, it can go down further, but apparently this is the best compromise).

This kind of design is probably the best overall, but it's very hard to do without a rather intrusive runtime support.


Copyright © 2026, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds