Fibers are still useful for some things

I wanted to comment a bit on this article by Raymond Chen, which states that fibers aren't useful for much anymore, with an example where I feel they still are.

I've found cooperative multi-(threading|tasking) (eg fibers) very useful in writing emulators. I wrote an article on this with code examples here.

One C++ thread on its own is inherently serial. When you have to do multiple discrete things at a time with very coarse synchronization, eg video encoding, then preemptive multi-threading makes sense (eg threads.) And when you have really simple functions to sync/wait or run/yield, then stackless coroutines are very elegant.

But when you want to model three CPU interpreters, a video renderer, and an audio generator, each of which needs to stay synchronized with the other when they communicate tens of millions of times a second, the overhead of using critical sections / semaphores / mutexes / atomic locks / etc grossly outweighs the cost of just doing everything on one thread. In my own testing, just a simple pathological test of two threads that take turns incrementing the same counter back and forth can only do this, and nothing else, a few hundreds thousand times a second. But an emulator needs to do this tens of millions of times a second, eg when two emulated CPUs share the same RAM.

Stackless coroutines and state machines are the staple in emulators for this reason, but trying to pack an entire Motorola 68000 into a single function is pretty much near-impossible. Once you split it up dozens of functions, and need a given function to yield that is four levels deep into the call stack, you realize how poorly state machines / stackless coroutines scale. I don't know of any emulators that model below instruction cycle synchronization as a result, and the clock cycles that make up each instruction cycle's bus accesses are just unsynchronized in such emulators.

If you check my link above, you'll see how drastic the code simplifications can be with cooperative threads: it hides all of the stack frame / state machine maintenance inside the native stack.

All of the above said, the #1 major detractor to me for them, which I have never been able to solve, is serialization (eg save states in emulator parlance.) There is just no portable or sane way to store that a given cooperative thread is halfway through a function four levels deep, and then be able to restore a given thread to that point later on demand.

Solving every problem (millions of synchronizations per second, hiding state machine overhead, suspending multiple call levels into a function/thread, serializing a thread) at once is an unsolved problem in computer science. Every technique has its uses and tradeoffs, including cooperative threads (fibers.)


Popular posts from this blog

Hello, I'm byuu

bsnes Frame Advance Input Latency

Summers and Bug-Catching