logoalt Hacker News

vdombrtoday at 12:33 PM2 repliesview on HN

It’s more like goroutines or other lightweight concurrency mechanisms. If threads are OS-level concurrency primitives, fibers are scheduled within Ruby itself, which makes them much more efficient than threads.

In fact, I got the following results in HTTP benchmark tests:

Go:

* Latency under stable load: p95 0.25–5.32 ms, p99 2.20–9.92 ms * Memory: 23–31 MB RSS across HTTP scenarios

Ruby:

* Latency under stable load: p95 1.03–6.45 ms, p99 2.32–8.30 ms * Memory: 84–295 MB RSS, depending on the scenario

Fibers can also handle WebSockets better because WebSocket workloads involve more I/O waiting.

A typical Falcon setup uses N workers, one thread per worker, and many fibers. Since the fibers are cooperatively scheduled within a single thread, this avoids much of the context-switching overhead associated with OS threads. Multiple workers can still run in parallel across CPU cores.


Replies

cogman10today at 2:06 PM

To dig a bit into why fibers are more efficient than threads.

The OS has a rather opaque view on what happens in a thread. It doesn't know how the memory is used or what memory is efficient. The OS can block a thread when it runs into IO, but it doesn't know or care about what other threads in an application it can or should activate.

Fibers bring the threading into the application layer. While the application can't choose which and when a thread runs, it can make choices about which fibers to run. Further, the application knows intimate details about things like the stack of a given thread. When it goes to park a fiber, it knows just how much memory should be saved off so the fiber can resume and it doesn't have to save off all the memory allocated to a thread's stack. Further, because so many programs are stack based an application can pretty smartly save and reuse segments of the stack which are common amongst fibers. So, for example, if you spin off 1000 fibers at one location in code, the stack for those 1000 fibers will be identical right up until the fiber starts executing.

The main drawback of fibers is they can't implement things like fair scheduling. Applications have few ways to park a currently running fiber to let another one run if, for example, the app wants to make some progress on all the fibers alive. The app has to wait for the fiber to hit some sort of IO point in the code or insert explicit park checks (The JVM actually does this for GC purposes. It creates "safepoints" which application threads make a quick check to see if the JVM wants to start a GC). The OS has more power here, it can simply interrupt the thread and start running something else for a given quanta.

jerftoday at 3:07 PM

I'm unclear on the relevance of a comparison between Go and Ruby here. Ruby is radically slower than Go. It's down there with Python, if not a touch behind [1]. From the outside, I'd expect that it's possible that slight improvements in the context switching time will be dwarfed by the generally slow execution of Ruby itself; that is, if Ruby is going to take 100 microseconds to do something, whether it context switches in 1 microsecond (best-case OS thread cost) or .2 microseconds (best-case goroutine switch) is of somewhat less consequence then it is for a compiled langauge that can complete that task in 2 microseconds. That ratio of 50x is not just something I made up, it's about what you can expect in general. I'd need to see the actual Ruby benchmarks to come to any conclusions as to whether or not I'm right.

The other problem with this sort of benchmark, which is a mistake I also commonly see made by Node developers, is that the Ruby HTTP stack has significant native code in it, like: https://github.com/puma/puma/tree/main/ext/puma_http11 This is a good and proper thing that brings benefits to all involved; it's not like it's "cheating" or anything, it's a real performance benefit. But it does mean when you're benchmarking a simple HTTP server, you're benchmarking Ruby qua Ruby a lot less than you think you are, and so the relevance of such benchmarks to codebases that have actual Ruby in them will be less.

[1]: https://programming-language-benchmarks.vercel.app/python-vs...

show 2 replies