Skip to content

Commit 275f67f

Browse files
committed
Add benchmark and use single shared Proc instance
1 parent cb490cf commit 275f67f

3 files changed

Lines changed: 107 additions & 10 deletions

File tree

benchmark/indifferent_bench.rb

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# frozen_string_literal: true
2+
3+
require 'bundler/setup'
4+
require 'benchmark/ips'
5+
require 'hash_kit'
6+
7+
helper = HashKit::Helper.new
8+
9+
# ---------------------------------------------------------------------------
10+
# Hash builders – every benchmark iteration gets a fresh hash so that
11+
# default_proc assignment is always exercised (no short-circuit).
12+
# ---------------------------------------------------------------------------
13+
14+
def build_small_flat_hash
15+
{ 'name' => 'Alice', 'age' => 30, 'active' => true }
16+
end
17+
18+
def build_large_flat_hash(n = 100)
19+
(0...n).each_with_object({}) { |i, h| h["key_#{i}"] = "value_#{i}" }
20+
end
21+
22+
def build_nested_hash(depth = 5)
23+
hash = { 'leaf' => 'value' }
24+
depth.times { |i| hash = { "level_#{i}" => hash, "sibling_#{i}" => 'data' } }
25+
hash
26+
end
27+
28+
def build_hash_with_arrays
29+
{
30+
'users' => [
31+
{ 'name' => 'Alice', 'roles' => [{ 'id' => 1, 'name' => 'admin' }] },
32+
{ 'name' => 'Bob', 'roles' => [{ 'id' => 2, 'name' => 'editor' }] },
33+
{ 'name' => 'Carol', 'roles' => [{ 'id' => 3, 'name' => 'viewer' }] }
34+
],
35+
'meta' => { 'page' => 1, 'total' => 3 }
36+
}
37+
end
38+
39+
def build_large_nested_hash(width = 20, depth = 4)
40+
return (0...width).each_with_object({}) { |i, h| h["key_#{i}"] = "val_#{i}" } if depth == 0
41+
42+
(0...width).each_with_object({}) do |i, h|
43+
h["node_#{i}"] = build_large_nested_hash(width, depth - 1)
44+
end
45+
end
46+
47+
# ---------------------------------------------------------------------------
48+
# Benchmark suite
49+
# ---------------------------------------------------------------------------
50+
51+
puts 'HashKit::Helper#indifferent! benchmark'
52+
puts "Ruby #{RUBY_VERSION} / benchmark-ips #{Benchmark::IPS::VERSION}"
53+
puts '=' * 60
54+
55+
Benchmark.ips do |x|
56+
x.config(warmup: 2, time: 5)
57+
58+
# --- Scenario 1: small flat hash (3 keys) ---
59+
x.report('small flat hash (3 keys)') do
60+
helper.indifferent!(build_small_flat_hash)
61+
end
62+
63+
# --- Scenario 2: large flat hash (100 keys) ---
64+
x.report('large flat hash (100 keys)') do
65+
helper.indifferent!(build_large_flat_hash(100))
66+
end
67+
68+
# --- Scenario 3: deeply nested hash (5 levels) ---
69+
x.report('nested hash (depth=5)') do
70+
helper.indifferent!(build_nested_hash(5))
71+
end
72+
73+
# --- Scenario 4: hash containing arrays of hashes ---
74+
x.report('hash with arrays') do
75+
helper.indifferent!(build_hash_with_arrays)
76+
end
77+
78+
# --- Scenario 5: large nested hash (wide + deep) ---
79+
x.report('large nested (10x3)') do
80+
helper.indifferent!(build_large_nested_hash(10, 3))
81+
end
82+
83+
# --- Scenario 6: many sequential calls on small hashes ---
84+
x.report('50x sequential small hashes') do
85+
50.times { helper.indifferent!(build_small_flat_hash) }
86+
end
87+
88+
# --- Scenario 7: many sequential calls on medium hashes ---
89+
x.report('50x sequential nested hashes') do
90+
50.times { helper.indifferent!(build_nested_hash(3)) }
91+
end
92+
93+
x.compare!
94+
end

hash_kit.gemspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
lib = File.expand_path('../lib', __FILE__)
3+
lib = File.expand_path('lib', __dir__)
44
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
55
require 'hash_kit/version'
66

@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
2020
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
2121
spec.require_paths = ['lib']
2222

23+
spec.add_development_dependency 'benchmark-ips', '~> 2.0'
2324
spec.add_development_dependency 'bundler'
2425
spec.add_development_dependency 'pry'
2526
spec.add_development_dependency 'rake'

lib/hash_kit/helper.rb

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,24 @@
33
module HashKit
44
# Hash kit Helper class
55
class Helper
6+
INDIFFERENT_PROC = proc do |h, k|
7+
if h.key?(k.to_s)
8+
h[k.to_s]
9+
elsif h.key?(k.to_sym)
10+
h[k.to_sym]
11+
else
12+
nil
13+
end
14+
end
15+
616
# This method is called to make a hash allow indifferent access (it will
717
# accept both strings & symbols for a valid key).
818
def indifferent!(hash)
919
return unless hash.is_a?(Hash)
1020

1121
# Set the default proc to allow the key to be either string or symbol if
1222
# a matching key is found.
13-
hash.default_proc = proc do |h, k|
14-
if h.key?(k.to_s)
15-
h[k.to_s]
16-
elsif h.key?(k.to_sym)
17-
h[k.to_sym]
18-
else
19-
nil
20-
end
21-
end
23+
hash.default_proc = INDIFFERENT_PROC
2224

2325
# Recursively process any child hashes
2426
hash.each do |key,value|

0 commit comments

Comments
 (0)