lazy_init 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -2
- data/README.md +26 -8
- data/benchmarks/benchmark.rb +616 -145
- data/benchmarks/benchmark_performance.rb +3 -66
- data/benchmarks/benchmark_threads.rb +83 -89
- data/lazy_init.gemspec +5 -5
- data/lib/lazy_init/class_methods.rb +565 -242
- data/lib/lazy_init/instance_methods.rb +90 -66
- data/lib/lazy_init/ruby_capabilities.rb +39 -0
- data/lib/lazy_init/version.rb +1 -1
- data/lib/lazy_init.rb +1 -0
- metadata +19 -18
@@ -84,91 +84,109 @@ module LazyInit
|
|
84
84
|
def lazy_once(max_entries: nil, ttl: nil, &block)
|
85
85
|
raise ArgumentError, 'Block is required' unless block
|
86
86
|
|
87
|
-
#
|
87
|
+
# Apply global configuration defaults
|
88
88
|
max_entries ||= LazyInit.configuration.max_lazy_once_entries
|
89
89
|
ttl ||= LazyInit.configuration.lazy_once_ttl
|
90
90
|
|
91
|
-
#
|
92
|
-
|
93
|
-
|
91
|
+
# Use simplified version ONLY if no advanced features needed
|
92
|
+
if LazyInit::RubyCapabilities::IMPROVED_EVAL_PERFORMANCE &&
|
93
|
+
max_entries.nil? && ttl.nil?
|
94
94
|
|
95
|
-
|
96
|
-
|
95
|
+
# Fast path for Ruby 3+ with no TTL/limits
|
96
|
+
location_key = caller_locations(1, 1).first.lineno
|
97
|
+
|
98
|
+
@lazy_once_simple ||= {}
|
99
|
+
return @lazy_once_simple[location_key] if @lazy_once_simple.key?(location_key)
|
100
|
+
|
101
|
+
result = block.call
|
102
|
+
@lazy_once_simple[location_key] = result
|
103
|
+
result
|
104
|
+
else
|
105
|
+
# Use existing full implementation for TTL/limits/statistics
|
106
|
+
# (existing lazy_once code with all features)
|
107
|
+
|
108
|
+
# Generate cache key from caller location for automatic memoization
|
109
|
+
call_location = caller_locations(1, 1).first
|
110
|
+
location_key = "#{call_location.path}:#{call_location.lineno}"
|
111
|
+
|
112
|
+
# Ensure thread-safe cache initialization
|
113
|
+
@lazy_once_mutex ||= Mutex.new
|
114
|
+
|
115
|
+
# Fast path: check cache outside mutex for performance
|
116
|
+
if @lazy_once_cache&.key?(location_key)
|
117
|
+
cached_entry = @lazy_once_cache[location_key]
|
97
118
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
119
|
+
# Handle TTL expiration if configured
|
120
|
+
if ttl && Time.now - cached_entry[:created_at] > ttl
|
121
|
+
@lazy_once_mutex.synchronize do
|
122
|
+
# Double-check TTL after acquiring lock
|
123
|
+
if @lazy_once_cache&.key?(location_key)
|
124
|
+
cached_entry = @lazy_once_cache[location_key]
|
125
|
+
if Time.now - cached_entry[:created_at] > ttl
|
126
|
+
@lazy_once_cache.delete(location_key)
|
127
|
+
else
|
128
|
+
# Entry is still valid, update access tracking and return
|
129
|
+
cached_entry[:access_count] += 1
|
130
|
+
cached_entry[:last_accessed] = Time.now if ttl
|
131
|
+
return cached_entry[:value]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
else
|
136
|
+
# Cache hit: update access tracking in thread-safe manner
|
137
|
+
@lazy_once_mutex.synchronize do
|
138
|
+
if @lazy_once_cache&.key?(location_key)
|
139
|
+
cached_entry = @lazy_once_cache[location_key]
|
112
140
|
cached_entry[:access_count] += 1
|
113
141
|
cached_entry[:last_accessed] = Time.now if ttl
|
114
142
|
return cached_entry[:value]
|
115
143
|
end
|
116
144
|
end
|
117
145
|
end
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
146
|
+
end
|
147
|
+
|
148
|
+
# Slow path: compute value and cache result
|
149
|
+
@lazy_once_mutex.synchronize do
|
150
|
+
# Double-check pattern
|
151
|
+
if @lazy_once_cache&.key?(location_key)
|
152
|
+
cached_entry = @lazy_once_cache[location_key]
|
153
|
+
|
154
|
+
# Verify TTL hasn't expired while we waited for the lock
|
155
|
+
if ttl && Time.now - cached_entry[:created_at] > ttl
|
156
|
+
@lazy_once_cache.delete(location_key)
|
157
|
+
else
|
123
158
|
cached_entry[:access_count] += 1
|
124
159
|
cached_entry[:last_accessed] = Time.now if ttl
|
125
160
|
return cached_entry[:value]
|
126
161
|
end
|
127
162
|
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
# slow path: compute value and cache result
|
132
|
-
@lazy_once_mutex.synchronize do
|
133
|
-
# double-check pattern: another thread might have computed while we waited
|
134
|
-
if @lazy_once_cache&.key?(location_key)
|
135
|
-
cached_entry = @lazy_once_cache[location_key]
|
136
|
-
|
137
|
-
# verify TTL hasn't expired while we waited for the lock
|
138
|
-
if ttl && Time.now - cached_entry[:created_at] > ttl
|
139
|
-
@lazy_once_cache.delete(location_key)
|
140
|
-
else
|
141
|
-
cached_entry[:access_count] += 1
|
142
|
-
cached_entry[:last_accessed] = Time.now if ttl
|
143
|
-
return cached_entry[:value]
|
144
|
-
end
|
145
|
-
end
|
146
163
|
|
147
|
-
|
148
|
-
|
164
|
+
# Initialize cache storage if this is the first lazy_once call
|
165
|
+
@lazy_once_cache ||= {}
|
149
166
|
|
150
|
-
|
151
|
-
|
167
|
+
# Perform LRU cleanup if cache is getting too large
|
168
|
+
cleanup_lazy_once_cache_simple!(max_entries) if @lazy_once_cache.size >= max_entries
|
152
169
|
|
153
|
-
|
154
|
-
|
155
|
-
|
170
|
+
# Compute the value and store in cache
|
171
|
+
begin
|
172
|
+
computed_value = block.call
|
156
173
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
174
|
+
# Create cache entry with metadata
|
175
|
+
cache_entry = {
|
176
|
+
value: computed_value,
|
177
|
+
access_count: 1
|
178
|
+
}
|
162
179
|
|
163
|
-
|
164
|
-
|
165
|
-
|
180
|
+
# Add optional metadata only when features are actually used
|
181
|
+
cache_entry[:created_at] = Time.now if ttl
|
182
|
+
cache_entry[:last_accessed] = Time.now if ttl
|
166
183
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
184
|
+
@lazy_once_cache[location_key] = cache_entry
|
185
|
+
computed_value
|
186
|
+
rescue StandardError => e
|
187
|
+
# Don't cache exceptions to keep implementation simple
|
188
|
+
raise
|
189
|
+
end
|
172
190
|
end
|
173
191
|
end
|
174
192
|
end
|
@@ -232,14 +250,20 @@ module LazyInit
|
|
232
250
|
def lazy_once_statistics
|
233
251
|
@lazy_once_mutex ||= Mutex.new
|
234
252
|
@lazy_once_mutex.synchronize do
|
253
|
+
simple_cache = @lazy_once_simple || {}
|
254
|
+
complex_cache = @lazy_once_cache || {}
|
255
|
+
|
256
|
+
total_entries = simple_cache.size + complex_cache.size
|
257
|
+
total_accesses = complex_cache.values.sum { |entry| entry[:access_count] || 1 } + simple_cache.size
|
258
|
+
|
235
259
|
# return empty stats if no cache exists yet
|
236
260
|
unless @lazy_once_cache
|
237
261
|
return {
|
238
|
-
total_entries:
|
239
|
-
computed_entries:
|
262
|
+
total_entries: total_entries,
|
263
|
+
computed_entries: total_entries,
|
240
264
|
oldest_entry: nil,
|
241
265
|
newest_entry: nil,
|
242
|
-
total_accesses:
|
266
|
+
total_accesses: total_accesses,
|
243
267
|
average_accesses: 0
|
244
268
|
}
|
245
269
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LazyInit
|
4
|
+
# Detects Ruby version capabilities for performance optimizations.
|
5
|
+
#
|
6
|
+
# This module automatically detects which Ruby version features are available
|
7
|
+
# and enables appropriate optimizations without requiring configuration.
|
8
|
+
# All detection is done at load time for zero runtime overhead.
|
9
|
+
#
|
10
|
+
# @since 0.2.0
|
11
|
+
module RubyCapabilities
|
12
|
+
# Currently used features
|
13
|
+
# Ruby 3.0+ introduces significant performance improvements
|
14
|
+
RUBY_3_PLUS = (RUBY_VERSION.split('.').map(&:to_i) <=> [3, 0, 0]) >= 0
|
15
|
+
|
16
|
+
# Improved eval performance in Ruby 3.0+
|
17
|
+
# Ruby 3+ has significantly better eval performance than define_method for generated code
|
18
|
+
IMPROVED_EVAL_PERFORMANCE = RUBY_3_PLUS
|
19
|
+
|
20
|
+
# Future optimization opportunities:
|
21
|
+
# RUBY_3_2_PLUS = (RUBY_VERSION.split('.').map(&:to_i) <=> [3, 2, 0]) >= 0
|
22
|
+
# OBJECT_SHAPES_AVAILABLE = RUBY_3_2_PLUS # Faster ivar access
|
23
|
+
# MN_SCHEDULER_AVAILABLE = RUBY_3_2_PLUS # Better thread coordination
|
24
|
+
# YJIT_AVAILABLE = RUBY_3_PLUS && !!defined?(RubyVM::YJIT)
|
25
|
+
# IMPROVED_MUTEX_PERFORMANCE = RUBY_3_PLUS
|
26
|
+
# FIBER_SCHEDULER_AVAILABLE = RUBY_3_PLUS && !!defined?(Fiber.set_scheduler)
|
27
|
+
|
28
|
+
# Debug information for troubleshooting (only in development)
|
29
|
+
unless defined?(Rails) && Rails.env.production?
|
30
|
+
def self.report_capabilities
|
31
|
+
{
|
32
|
+
ruby_version: RUBY_VERSION,
|
33
|
+
ruby_3_plus: RUBY_3_PLUS,
|
34
|
+
improved_eval: IMPROVED_EVAL_PERFORMANCE
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/lazy_init/version.rb
CHANGED
data/lib/lazy_init.rb
CHANGED
metadata
CHANGED
@@ -1,85 +1,85 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lazy_init
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Konstanty Koszewski
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-07-
|
11
|
+
date: 2025-07-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: benchmark-ips
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.10'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.10'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '13.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '13.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: '3.12'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: '3.12'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rubocop
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 1.50.2
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 1.50.2
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: yard
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '0.9'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '0.9'
|
83
83
|
description: Provides thread-safe lazy initialization with clean, Ruby-idiomatic API.
|
84
84
|
Eliminates race conditions in lazy attribute initialization while maintaining performance.
|
85
85
|
email:
|
@@ -109,6 +109,7 @@ files:
|
|
109
109
|
- lib/lazy_init/errors.rb
|
110
110
|
- lib/lazy_init/instance_methods.rb
|
111
111
|
- lib/lazy_init/lazy_value.rb
|
112
|
+
- lib/lazy_init/ruby_capabilities.rb
|
112
113
|
- lib/lazy_init/version.rb
|
113
114
|
homepage: https://github.com/N3BCKN/lazy_init
|
114
115
|
licenses:
|
@@ -118,7 +119,7 @@ metadata:
|
|
118
119
|
source_code_uri: https://github.com/N3BCKN/lazy_init
|
119
120
|
changelog_uri: https://github.com/N3BCKN/lazy_init/blob/main/CHANGELOG.md
|
120
121
|
bug_tracker_uri: https://github.com/N3BCKN/lazy_init/issues
|
121
|
-
documentation_uri: https://rubydoc.info/gems/lazy_init
|
122
|
+
documentation_uri: https://rubydoc.info/gems/lazy_init/0.2.0
|
122
123
|
post_install_message:
|
123
124
|
rdoc_options: []
|
124
125
|
require_paths:
|