1

I'm trying to use Celluloid to process some .csv data asynchronously. I've read that using futures enables you to wait for a pool of actors to finish before the main thread terminates. I've looked at some examples that demonstrate this.

However, when I implement it in my example code, it turns out that using futures is not any faster than doing the processing synchronously. Can anyone see what I'm doing wrong?

require 'smarter_csv'
require 'celluloid/current'
require 'benchmark'

class ImportActor
  include Celluloid

  def process_row(row)
    100000.times {|n| n}
  end

end

def do_all_the_things_with_futures
  pool    = ImportActor.pool(size: 10)
  SmarterCSV.process("all_the_things.csv").map do |row|
    pool.future(:process_row,row)
  end.map(&:value)
end


def do_all_the_things_insync
  pool    = ImportActor.pool(size: 10)
  SmarterCSV.process("all_the_things.csv") do |row|
    pool.process_row(row)
  end
end

puts Benchmark.measure { do_all_the_things_with_futures}
puts Benchmark.measure { do_all_the_things_insync }

2.100000 0.030000 2.130000 ( 2.123381)

2.060000 0.020000 2.080000 ( 2.069357)

[Finished in 4.6s]

Erik Froese
  • 103
  • 7

1 Answers1

2

Are you using the standard ruby MRI interpreter?

If so, you won't get any speed-up for entirely CPU-bound tasks -- that is, tasks that aren't doing any I/O, but are entirely doing calculations in the CPU. Your 'test' task of 100000.times {|n| n} is indeed entirely CPU-bound.

The reason you won't get any speed-up through multi-threading for entirely CPU-bound tasks on MRI, is because the MRI interpreter has a "Global Interpreter Lock" (GIL), that prevents more than one of your CPU cores from being used at once by the ruby interpreter. Multi-threaded parallelism, like celluloid gives you, can speed up CPU work only by running different threads on different CPU cores simulataneously, on a multi-core system like most systems are these days.

But in MRI, that's not possible. This is a limitation of the ruby MRI interpreter.

If you install JRuby and run your test under JRuby, you should see speed-up.

If your task involved some I/O (like making a database query, or waiting on a remote HTTP API, or doing significant amounts of file reading or writing), you could also see some speed-up under MRI. The more proportional time your task spends doing I/O, the more speed-up. This is because even though MRI doesn't allow threads to execute simultaneously on more than one CPU core, a thread waiting on I/O can still be switched out and another thread switched in to do work. Whereas if you weren't using threads, the program would just be sitting around waiting on the I/O doing no work.

If you google for "ruby GIL" you can find more discussions of the issue.

If you are really doing CPU-intensive work that could benefit from multi-threaded parallelism in a way that will significantly help your program, consider switching to Jruby.

And if you really do need multi-threaded parallelism, an alternatives to using Celluloid is using Futures or Promises from the concurrent-ruby package. Concurrent-ruby is generally simpler internally and lighter-weight than Celluloid. However, writing multi-threaded code can be tricky regardless of which tool you use, and even if you use Celluloid or ruby-concurrent to give you better higher-level abstractions than working directly with threads, working with multi-threaded concurrency will require becoming familiar with some techniques for such and require some tricky debugging from time to time.

jrochkind
  • 22,799
  • 12
  • 59
  • 74
  • Adding `sleep 1` instead of my initial iteration did the trick. The async method finished 10 times faster. @jrochkind amazingly thorough answer. Cheers. – Erik Froese Oct 14 '15 at 01:58
  • Glad it was helpful; it's not hard to install jruby (with rvm if you use it, or I recommend ruby-build and chruby); if you try your original example under JRuby, you should see significant speed-up too! Not 10x unless you have 10 CPU cores on your machine, but significant. It would be interesting to try and compare. – jrochkind Oct 14 '15 at 12:10