Skip to content

Instantly share code, notes, and snippets.

@tenderlove
Created October 29, 2024 23:37
Show Gist options
  • Save tenderlove/ec49f514c5672e6c74cf11ce169c1cfd to your computer and use it in GitHub Desktop.
Save tenderlove/ec49f514c5672e6c74cf11ce169c1cfd to your computer and use it in GitHub Desktop.
##
# Test how long it takes to encrypt passwords using different concurrency
# primitives in Ruby.
#
# Output on my machine:
#
# $ ruby test-cpu.rb
# 10 iterations (no parallelism): 1.87 seconds
# 10 iterations (Async): 1.87 seconds
# 10 iterations (Thread): 0.32 seconds
#
# Running `BCrypt::Password.create` runs _faster_ with threads than it does with Fibers.
# Why? BCrypt is a C extension that does purely CPU bound work. The C extension
# releases the GVL which allows the system to schedule another thread.
#
# Async's scheduler only switches on IO blocking, so no CPU bound Fiber can
# run in parallel. Releasing the GVL doesn't allow the Fiber scheduler to
# schedule other work.
#
require "bcrypt"
require "async"
N = (ENV["N"] || 10).to_i
def measure
x = Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield
Process.clock_gettime(Process::CLOCK_MONOTONIC) - x
end
def work
BCrypt::Password.create('secret')
end
time = measure { N.times { work } }
puts "#{N} iterations (no parallelism): #{sprintf("%.2f", time)} seconds"
Async {
time = measure {
N.times.map { Async { work } }.map(&:wait)
}
puts "#{N} iterations (Async): #{sprintf("%.2f", time)} seconds"
}
time = measure {
N.times.map { Thread.new { work } }.map(&:join)
}
puts "#{N} iterations (Thread): #{sprintf("%.2f", time)} seconds"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment