4 ms·
Unfortunately in 2023 c++20 coroutines are just barely starting to become usable. Compilers have bugs, especially with symmetric transfer, e.g. msvc doesn't sus
by snaury 3y ago
Unfortunately in 2023 c++20 coroutines are just barely starting to become usable. Compilers have bugs, especially with symmetric transfer, e.g. msvc doesn't suspend until await_suspend returns (which causes races and double-frees), gcc stops using tail calls in some modes (which can cause stack overflow), I ended up emulating symmetric transfer because you can't rely on it, and emulating it is of course slower. Compilers spill local variables from await_suspend to coroutine frames, which again causes races, which necessitates workarounds like marking await_suspend noinline (in a non-portable way). Having noinline anything in the code path disables heap allocation elision, so all coroutines start to allocate from the heap. When comparing a function call to a coroutine call there's a vast difference in performance. And you need to use coroutines all the way down, which really adds up.
Fibers have their own problems: you need to allocate stacks (and that may be expensive), you can't switch fibers between system threads (compilers cache thread local addresses, unfortunately with x86 linux tls often uses fs: segment addressing, so it may appear to work until it doesn't, and weirdly this problem existed in c++20 coroutines too until recent clang versions), widely used open source implementations (e.g. boost context / boost coroutine / boost fiber) are not even exception safe, because you need to save/restore exception globals when switching fibers, and nobody seems to care. :-/
One huge upside to fibers is that a function call is just a normal function call, and it's fast as a result. I wish c++ taken fibers more seriously and added steps for making them safe to use (portable way to handle global state, portable way to mark switching functions as invalidating tls addresses, etc.), we could have something similar to java virtual threads or go goroutines then.
- gpderetta 3y ago> exception safe, because you need to save/restore exception globals when switching fibers, Do you have more details about this issue? What exception globals? And for which ABI? You mean some sort of current pending exception when switching during unwinding? [I'm a huge fan of stackful coroutines and I wish were blessed by the standard]
- snaury 3y agoC++ ABI has some per-thread globals: the number that is returned from std::uncaught_exceptions(), and the chain of currently caught exceptions. For example in llvm this is available with a cxa_get_globals call: https://github.com/llvm/llvm-project/blob/b05f1d93469fbd645118739798e51f21a9e410de/libcxxabi/src/cxa_exception.h#L161 https://github.com/llvm/llvm-project/blob/b05f1d93469fbd6451... These need to be saved/restored when switching fibers, otherwise fiber switches from catch clauses (and destructors!) are unsafe, throw without argument may rethrow incorrect exception, code that commits/rollbacks based on uncaught exceptions counter will not work correctly, etc. One example I know where this save/restore is implemented is the userver framework, but it seems to be unexpectedly rare in fiber implementations last time I looked.
- gpderetta 3y agoThanks. It is unsurprising that most libraries punt on that. It seems that switching this state would easily dominate over the cost switching. IIRC on gcc the current exception is protected by a global mutex, at least until recently. It is another reason for having this built into the language/standard library so that it can be implemented optimally.
- indigoabstract 3y agoYes, in my (limited) use case with coroutines for a recursive descent generator I got it working by following this tutorial: https://www.scs.stanford.edu/~dm/blog/c++-coroutines.html https://www.scs.stanford.edu/~dm/blog/c++-coroutines.html but I was unpleasantly surprised by much extra code it required to make it run. The Tiny Fiber library in the article looks pretty hacky right now and the amount of x64/arm specific code might cause trouble with portability, but the concept itself is intriguing. Thank you for the details.