Slide 1

Slide 1 text

Concurrency in a New Way… that Kinda Works. Ruby Ractors I love questions. Have a question? Ask it!

Slide 2

Slide 2 text

What Is a Ractor? • Before we can answer that, we have to discuss the GIL (a.k.a. GVL) - the lock that keeps us from running Ruby code in multiple threads at once.

Slide 3

Slide 3 text

What's a GVL? • The Global VM Lock is a lock that Ruby has to acquire before running Ruby code. There is only one lock*. Thus, "Global." * Until Ruby 3.0… My GVL… Mine!

Slide 4

Slide 4 text

Want to Do More Than One Thing? Not In This Process, Mister. • Ruby can do multiple things at once. Not everything grabs the GVL. • "Free" activities include waiting on the database or network, plus many calculations performed by C Native Extensions. • Other than that, you get one activity per process.

Slide 5

Slide 5 text

Ruby Threads? Weird. • Ruby threads aren't like other languages on purpose. Matz wants threads to break only between lines of Ruby. • That requires a GVL. Matz is frequently quoted as hating threads and wishing he hadn't added them to Ruby. I'm not kidding.

Slide 6

Slide 6 text

Why Hate Threads? • Thread sync is hard to learn and hard even for the pros. • How to avoid sync issues while allowing concurrency? • Erlang-style copying; most objects belong to one thread. • And that's how Ractors work.

Slide 7

Slide 7 text

Yeah, But How? • One GVL (now Global Ractor Lock) per Ractor - every thread lives in a specific parent Ractor • Never use Ractors? You get the old behaviour. • Replace Threads with Ractors? You get full concurrency. • But how do they sync?

Slide 8

Slide 8 text

Sync, Sanc, Sunc • Each Ruby object now belongs to a Ractor - can't touch other Ractors' stuff. • Can pass objs via channels, like in Go.

Slide 9

Slide 9 text

Annoying But Optional • This is hard for shared global state (e.g. Random.) A lot of libraries will need restructuring. For now, you can't use them from non-primary Ractors*. • Never add a Ractor? You can ignore all of this. * "Is Rails going to support Ractors now?" Not soon. Any idea how much shared global state Rails has?

Slide 10

Slide 10 text

What's the API? Ractors get values and return them: ractor1 << :item # Send item in ret_val = ractor1.take # Get item out # Inside ractor next_item = Ractor.recv Ractor.yield :my_return_val

Slide 11

Slide 11 text

Moar Code # A simple Ractor that checks whether numbers from N to N+99 are prime # and returns an integer bit-vector of the true/false results Ractor.new(pipe) do |pipe| while n = pipe.take bools = (n..(n+99)).map { |nn| nn.prime? } p_int = bools.inject(0) { |total, b| total * 2 + (b ? 1 : 0) } Ractor.yield [n, p_int ] end end This is the same benchmark code I use later, though I wrap it in slightly different Ractor read/write access patterns.

Slide 12

Slide 12 text

How Fast Are They? • The whole point of Ractors is speed. • They can use all your cores, not just one. • Ractors can use far less memory than processes.

Slide 13

Slide 13 text

Theoretical Speed • At best, N Ractors are as fast as N processes* (max speed at 100% CPU all cores.) • Ractors have implementation limits that slow them down. • With no memory limits, right now: Processes > Ractors > Threads in nearly all cases. * if memory isn't an important limit

Slide 14

Slide 14 text

Practical Speed I wrote some Ractor benchmarks. They run… With occasional hangs. How's the speed on a 4-vCPU Linode? Not good. There are test benchmarks where Ractors work as claimed but I have trouble duplicating those results. Here's a small benchmark (5 workers, 1,000 msgs/worker) with several Ractor access patterns.

Slide 15

Slide 15 text

Which Code? Not so good. "Single" is the single-pass version. All calculation means threads are slower than single. Multiple processes ("fork") is fast b/c no GVL. All Ractor code does worse than threading, not better.

Slide 16

Slide 16 text

Maybe Setup is Bad? But perhaps the startup/setup time is bad and ractors get better with more messages? Here we try 10 workers and 10,000 msgs/ worker (I skip "single" here, it's about the same as "thread.") Basically: not that much worse than threads, but never better than threads.

Slide 17

Slide 17 text

Takeaways • Ractors are currently hard to use. There might be some simple way to make this fast. But I tried a lot of things with no luck. • Ractors hang on reasonable-looking use cases and you can't use a lot of Ruby standard library code.

Slide 18

Slide 18 text

Takeaways • If you use Ractors, try to start from core benchmark code. That's how I got this far. • On a Mac they're even less stable. • The little printed warning about Ractors being experimental? They mean it.

Slide 19

Slide 19 text

Questions?