Modern concurrency tools including agents, futures, promises, thread pools, supervisors, and more. Inspired by Erlang, Clojure, Scala, Go, Java, JavaScript, and classic concurrency patterns.
This patch makes it faster and keeps same-ish speed for truthy value.
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'benchmark-ips', require: 'benchmark/ips'
gem 'concurrent-ruby', require: 'concurrent'
end
concurrent_map = Concurrent::Map.new
concurrent_map[:nil] = nil
concurrent_map[:non_nil] = 1
patched_concurrent_map = Concurrent::Map.new
patched_concurrent_map.extend(Module.new do
def compute_if_absent(key)
if Concurrent::NULL != (stored_value = @backend.fetch(key, Concurrent::NULL))
stored_value
else
@write_lock.synchronize { super }
end
end
end)
patched_concurrent_map[:nil] = nil
patched_concurrent_map[:non_nil] = 1
Benchmark.ips do |x|
x.report("[Concurrent::Map] :nil") do
concurrent_map.compute_if_absent(:nil) { raise }
end
x.report("[Concurrent::Map] :non_nil") do
concurrent_map.compute_if_absent(:non_nil) { raise }
end
x.report("[Concurrent::Map with patch] :nil") do
patched_concurrent_map.compute_if_absent(:nil) { raise }
end
x.report("[Concurrent::Map with patch] :non_nil") do
patched_concurrent_map.compute_if_absent(:non_nil) { raise }
end
x.compare!
end
Warming up --------------------------------------
[Concurrent::Map] :nil
310.135k i/100ms
[Concurrent::Map] :non_nil
923.645k i/100ms
[Concurrent::Map with patch] :nil
928.247k i/100ms
[Concurrent::Map with patch] :non_nil
840.197k i/100ms
Calculating -------------------------------------
[Concurrent::Map] :nil
2.049M (±11.4%) i/s - 10.234M in 5.060933s
[Concurrent::Map] :non_nil
7.181M (± 7.5%) i/s - 36.022M in 5.048215s
[Concurrent::Map with patch] :nil
7.479M (± 5.0%) i/s - 38.058M in 5.103572s
[Concurrent::Map with patch] :non_nil
7.413M (± 6.9%) i/s - 36.969M in 5.013929s
Comparison:
[Concurrent::Map with patch] :nil: 7478557.1 i/s
[Concurrent::Map with patch] :non_nil: 7413277.4 i/s - same-ish: difference falls within error
[Concurrent::Map] :non_nil: 7181043.9 i/s - same-ish: difference falls within error
[Concurrent::Map] :nil: 2049352.8 i/s - 3.65x (± 0.00) slower
Currently,
compute_if_absent
with falsy value is much slower than truthy value because non-blocking path only considers truthy one. https://github.com/ruby-concurrency/concurrent-ruby/blob/f749b81cb6c6291640c0004b57e60dbc2b59a72b/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb#L22This patch makes it faster and keeps same-ish speed for truthy value.