My first question on SO, but I've lurked for a long time now so you'll have to forgive me if I've broken any rules or posted a rubbish question.
I'm trying to get a better understanding of threading and I decided to test MRI and see how it performs in general.
Given the following code (and output), why are the threaded operations so much slower than the non-threaded variant?
class Benchmarker
def self.go
puts '----------Benchmark Start----------'
start_t = Time.now
yield
end_t = Time.now
puts "Operation Took: #{end_t - start_t} seconds"
puts '----------Benchmark End------------'
end
end
# using mutex
puts 'Benchmark 1 (threaded, mutex):'
Benchmarker.go do
array = []
mutex = Mutex.new
5000.times.map do
Thread.new do
mutex.synchronize do
1000.times do
array << nil
end
end
end
end.each(&:join)
puts array.size
end
# using threads
puts 'Benchmark 2 (threaded, no mutex):'
Benchmarker.go do
array = []
5000.times.map do
Thread.new do
1000.times do
array << nil
end
end
end.each(&:join)
puts array.size
end
# no threads
puts 'Benchmark 3 (no threads):'
Benchmarker.go do
array = []
5000.times.map do
1000.times do
array << nil
end
end
puts array.size
end
Benchmark 1 (threaded, mutex):
----------Benchmark Start----------
5000000
Operation Took: 3.373886 seconds
----------Benchmark End------------
Benchmark 2 (threaded, no mutex):
----------Benchmark Start----------
5000000
Operation Took: 5.040501 seconds
----------Benchmark End------------
Benchmark 3 (no threads):
----------Benchmark Start----------
5000000
Operation Took: 0.454665 seconds
----------Benchmark End------------
Thanks in advance.
Once you hit a high amount of threads (5000), the amount of overhead for switching between threads by the scheduler far outweighs the amount of work that each thread actually does. Typically you want 30-50 threads max.
Try lowering the amount of threads and proportionally increasing the amount of work that each does:
20.times.map do
Thread.new do
250000.times do
array << nil
end
end
end.each(&:join)
and you should see far more comparable results.
Note you will probably see the lower bound Time(threaded) >= Time(non-threaded)
- that is the Threaded version's time can't be lower than single-threaded version. This is because of the MRI's GIL which allows only one thread to execute at a time (they can never run in parallel). Some ruby implementations such as JRuby allow parallel execution of threads.