lazy_init 0.1.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.
@@ -0,0 +1,381 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'benchmark/ips'
5
+ require_relative '../lib/lazy_init'
6
+
7
+ puts '=== DOGŁĘBNA ANALIZA BOTTLENECKÓW LAZYINIT ==='
8
+ puts "Ruby version: #{RUBY_VERSION}"
9
+
10
+ # === STEP 1: MICRO-BENCHMARK OF EACH ELEMENT ===
11
+
12
+ def micro_benchmark(description, &block)
13
+ puts "\n--- #{description} ---"
14
+
15
+ result = Benchmark.ips { |x| x.report('test', &block) }.entries.first.ips
16
+ puts " #{description}: #{format_ips(result)}"
17
+ result
18
+ end
19
+
20
+ def format_ips(ips)
21
+ if ips >= 1_000_000
22
+ "#{(ips / 1_000_000.0).round(2)}M i/s"
23
+ elsif ips >= 1_000
24
+ "#{(ips / 1_000.0).round(1)}K i/s"
25
+ else
26
+ "#{ips.round(0)} i/s"
27
+ end
28
+ end
29
+
30
+ puts "\n" + '=' * 80
31
+ puts 'STEP 1: ISOLATING INDIVIDUAL COMPONENTS'
32
+ puts '=' * 80
33
+
34
+ # 1.1 Direct instance variable access (baseline)
35
+ class DirectVar
36
+ def initialize
37
+ @value = 'computed_value'
38
+ end
39
+
40
+ def get_value
41
+ @value
42
+ end
43
+ end
44
+
45
+ direct = DirectVar.new
46
+ baseline = micro_benchmark('1.1 Direct @value access') { direct.get_value }
47
+
48
+ # 1.2 Manual ||= pattern
49
+ class ManualPattern
50
+ def get_value
51
+ @value ||= 'computed_value'
52
+ end
53
+ end
54
+
55
+ manual = ManualPattern.new
56
+ manual.get_value # warm up
57
+ manual_result = micro_benchmark('1.2 Manual @value ||= (warm)') { manual.get_value }
58
+
59
+ # 1.3 LazyValue object access (isolated)
60
+ lazy_value = LazyInit::LazyValue.new { 'computed_value' }
61
+ lazy_value.value # warm up
62
+ lazy_value_result = micro_benchmark('1.3 LazyValue.value (warm)') { lazy_value.value }
63
+
64
+ # 1.4 Method dispatch przez generated method
65
+ class GeneratedMethodTest
66
+ extend LazyInit
67
+ lazy_attr_reader :test_value do
68
+ 'computed_value'
69
+ end
70
+ end
71
+
72
+ generated = GeneratedMethodTest.new
73
+ generated.test_value # warm up
74
+ generated_result = micro_benchmark('1.4 Generated method (warm)') { generated.test_value }
75
+
76
+ puts "\n" + '=' * 50
77
+ puts 'STEP 1 ANALYSIS:'
78
+ puts '=' * 50
79
+ puts "Baseline (direct @var): #{format_ips(baseline)}"
80
+ puts "Manual ||=: #{format_ips(manual_result)} (#{(baseline / manual_result).round(2)}x slower)"
81
+ puts "LazyValue only: #{format_ips(lazy_value_result)} (#{(baseline / lazy_value_result).round(2)}x slower)"
82
+ puts "Generated method: #{format_ips(generated_result)} (#{(baseline / generated_result).round(2)}x slower)"
83
+
84
+ # === STEP 2: DECOMPOSITION OF GENERATED METHOD ===
85
+
86
+ puts "\n" + '=' * 80
87
+ puts 'STEP 2: DECOMPOSITION GENERATED METHOD CALL'
88
+ puts '=' * 80
89
+
90
+ class DecompositionTest
91
+ extend LazyInit
92
+
93
+ lazy_attr_reader :full_method do
94
+ 'computed_value'
95
+ end
96
+
97
+ def initialize
98
+ @manual_lazy_value = LazyInit::LazyValue.new { 'computed_value' }
99
+ @manual_lazy_value.value # warm up
100
+ end
101
+
102
+ def step1_config_lookup
103
+ self.class.lazy_initializers[:full_method]
104
+ end
105
+
106
+ def step2_dependency_check
107
+ config = self.class.lazy_initializers[:full_method]
108
+ config[:depends_on] # nil in this case
109
+ end
110
+
111
+ def step3_ivar_access
112
+ instance_variable_get(:@full_method_lazy_value)
113
+ end
114
+
115
+ def step4_lazy_value_call
116
+ @manual_lazy_value.value
117
+ end
118
+
119
+ def step5_full_manual_replication
120
+ config = self.class.lazy_initializers[:full_method]
121
+
122
+ # Skip dependency resolution if no dependencies
123
+ # if config[:depends_on]
124
+ # self.class.dependency_resolver.resolve_dependencies(name, self)
125
+ # end
126
+
127
+ ivar_name = :@full_method_lazy_value
128
+ lazy_value = instance_variable_get(ivar_name)
129
+
130
+ unless lazy_value
131
+ lazy_value = LazyInit::LazyValue.new(timeout: config[:timeout]) do
132
+ instance_eval(&config[:block])
133
+ end
134
+ instance_variable_set(ivar_name, lazy_value)
135
+ end
136
+
137
+ lazy_value.value
138
+ end
139
+ end
140
+
141
+ decomp = DecompositionTest.new
142
+ decomp.full_method # warm up
143
+
144
+ step1 = micro_benchmark('2.1 Config hash lookup') { decomp.step1_config_lookup }
145
+ step2 = micro_benchmark('2.2 Dependency check') { decomp.step2_dependency_check }
146
+ step3 = micro_benchmark('2.3 Instance var get') { decomp.step3_ivar_access }
147
+ step4 = micro_benchmark('2.4 LazyValue.value call') { decomp.step4_lazy_value_call }
148
+ step5 = micro_benchmark('2.5 Full manual replication') { decomp.step5_full_manual_replication }
149
+ full_method = micro_benchmark('2.6 Actual generated method') { decomp.full_method }
150
+
151
+ puts "\n" + '=' * 50
152
+ puts 'STEP 2 ANALYSIS:'
153
+ puts '=' * 50
154
+ puts "Config lookup: #{format_ips(step1)} (#{(baseline / step1).round(2)}x slower than baseline)"
155
+ puts "Dependency check: #{format_ips(step2)} (#{(baseline / step2).round(2)}x slower than baseline)"
156
+ puts "Instance var get: #{format_ips(step3)} (#{(baseline / step3).round(2)}x slower than baseline)"
157
+ puts "LazyValue.value: #{format_ips(step4)} (#{(baseline / step4).round(2)}x slower than baseline)"
158
+ puts "Manual replication: #{format_ips(step5)} (#{(baseline / step5).round(2)}x slower than baseline)"
159
+ puts "Generated method: #{format_ips(full_method)} (#{(baseline / full_method).round(2)}x slower than baseline)"
160
+
161
+ # === STEP 3: LAZYVALUE INTERNAL BOTTLENECKS ===
162
+
163
+ puts "\n" + '=' * 80
164
+ puts 'STEP 3: LAZYVALUE INTERNAL ANALYSIS'
165
+ puts '=' * 80
166
+
167
+ # Analiza LazyValue.value method
168
+ class LazyValueAnalysis
169
+ def initialize
170
+ @computed = true
171
+ @value = 'computed_value'
172
+ @mutex = Mutex.new
173
+ end
174
+
175
+ # Obecna implementacja (uproszczona)
176
+ def current_implementation
177
+ @mutex.synchronize do
178
+ return @value if @computed
179
+ # compute...
180
+ end
181
+ end
182
+
183
+ # Fast path with double-checked locking
184
+ def fast_path_implementation
185
+ return @value if @computed
186
+
187
+ @mutex.synchronize do
188
+ return @value if @computed
189
+ # compute...
190
+ end
191
+ end
192
+
193
+ # Ultra fast path (tylko check)
194
+ def ultra_fast_path
195
+ return @value if @computed
196
+ end
197
+
198
+ # Mutex overhead test
199
+ def mutex_overhead_test
200
+ @mutex.synchronize do
201
+ @value
202
+ end
203
+ end
204
+ end
205
+
206
+ lazy_analysis = LazyValueAnalysis.new
207
+
208
+ current = micro_benchmark('3.1 Current LazyValue impl') { lazy_analysis.current_implementation }
209
+ fast_path = micro_benchmark('3.2 Fast path impl') { lazy_analysis.fast_path_implementation }
210
+ ultra_fast = micro_benchmark('3.3 Ultra fast (just check)') { lazy_analysis.ultra_fast_path }
211
+ mutex_overhead = micro_benchmark('3.4 Mutex overhead') { lazy_analysis.mutex_overhead_test }
212
+
213
+ puts "\n" + '=' * 50
214
+ puts 'STEP 3 ANALYSIS:'
215
+ puts '=' * 50
216
+ puts "Current (always mutex): #{format_ips(current)} (#{(baseline / current).round(2)}x slower than baseline)"
217
+ puts "Fast path (skip mutex): #{format_ips(fast_path)} (#{(baseline / fast_path).round(2)}x slower than baseline)"
218
+ puts "Ultra fast (just check): #{format_ips(ultra_fast)} (#{(baseline / ultra_fast).round(2)}x slower than baseline)"
219
+ puts "Mutex overhead: #{format_ips(mutex_overhead)} (#{(baseline / mutex_overhead).round(2)}x slower than baseline)"
220
+
221
+ # === STEP 4: OPTIMIZED IMPLEMENTATIONS ===
222
+
223
+ puts "\n" + '=' * 80
224
+ puts 'STEP 4: OPTIMIZED IMPLEMENTATION PROTOTYPES'
225
+ puts '=' * 80
226
+
227
+ # Prototype 1: Cached config approach
228
+ class CachedConfigApproach
229
+ extend LazyInit
230
+
231
+ def self.lazy_attr_reader_optimized(name, &block)
232
+ config = { block: block, timeout: nil, depends_on: nil }
233
+
234
+ # Cache config in class for fast access
235
+ ivar_name = "@#{name}_lazy_value"
236
+
237
+ define_method(name) do
238
+ lazy_value = instance_variable_get(ivar_name)
239
+
240
+ unless lazy_value
241
+ lazy_value = LazyInit::LazyValue.new(&config[:block])
242
+ instance_variable_set(ivar_name, lazy_value)
243
+ end
244
+
245
+ lazy_value.value
246
+ end
247
+ end
248
+
249
+ lazy_attr_reader_optimized :optimized_value do
250
+ 'computed_value'
251
+ end
252
+ end
253
+
254
+ cached_config = CachedConfigApproach.new
255
+ cached_config.optimized_value # warm up
256
+ cached_result = micro_benchmark('4.1 Cached config approach') { cached_config.optimized_value }
257
+
258
+ # Prototype 2: Inline fast path
259
+ class InlineFastPath
260
+ def initialize
261
+ @value = 'computed_value'
262
+ @computed = true
263
+ end
264
+
265
+ def inline_optimized
266
+ # Inline the most common path
267
+ return @value if @computed
268
+
269
+ # Fallback to slow path (would be method call)
270
+ slow_path_computation
271
+ end
272
+
273
+ def slow_path_computation
274
+ # This would be the full LazyValue logic
275
+ @value
276
+ end
277
+ end
278
+
279
+ inline_test = InlineFastPath.new
280
+ inline_result = micro_benchmark('4.2 Inline fast path') { inline_test.inline_optimized }
281
+
282
+ # Prototype 3: Specialized generated methods
283
+ class SpecializedGenerated
284
+ def initialize
285
+ @test_value = 'computed_value'
286
+ @test_value_computed = true
287
+ end
288
+
289
+ # This simulates a highly optimized generated method
290
+ def specialized_test_value
291
+ return @test_value if @test_value_computed
292
+
293
+ # Slow path would go here
294
+ @test_value
295
+ end
296
+ end
297
+
298
+ specialized = SpecializedGenerated.new
299
+ specialized_result = micro_benchmark('4.3 Specialized generated') { specialized.specialized_test_value }
300
+
301
+ puts "\n" + '=' * 50
302
+ puts 'STEP 4 ANALYSIS:'
303
+ puts '=' * 50
304
+ puts "Cached config: #{format_ips(cached_result)} (#{(baseline / cached_result).round(2)}x slower than baseline)"
305
+ puts "Inline fast path: #{format_ips(inline_result)} (#{(baseline / inline_result).round(2)}x slower than baseline)"
306
+ puts "Specialized generated: #{format_ips(specialized_result)} (#{(baseline / specialized_result).round(2)}x slower than baseline)"
307
+
308
+ # === STEP 5: FINAL COMPARISON ===
309
+
310
+ puts "\n" + '=' * 80
311
+ puts 'COMPREHENSIVE BOTTLENECK ANALYSIS'
312
+ puts '=' * 80
313
+
314
+ puts "\nBASELINE COMPARISON:"
315
+ puts "Direct @var access: #{format_ips(baseline)} (1.0x - baseline)"
316
+ puts "Manual ||=: #{format_ips(manual_result)} (#{(baseline / manual_result).round(2)}x slower)"
317
+ puts ''
318
+ puts 'CURRENT LAZYINIT:'
319
+ puts "Generated method: #{format_ips(generated_result)} (#{(baseline / generated_result).round(2)}x slower)"
320
+ puts ''
321
+ puts 'COMPONENT BREAKDOWN:'
322
+ puts "Config lookup: #{format_ips(step1)} (#{(baseline / step1).round(2)}x slower)"
323
+ puts "LazyValue.value: #{format_ips(step4)} (#{(baseline / step4).round(2)}x slower)"
324
+ puts "Mutex (always sync): #{format_ips(current)} (#{(baseline / current).round(2)}x slower)"
325
+ puts ''
326
+ puts 'OPTIMIZATION POTENTIAL:'
327
+ puts "Fast path LazyValue: #{format_ips(fast_path)} (#{(baseline / fast_path).round(2)}x slower)"
328
+ puts "Ultra fast path: #{format_ips(ultra_fast)} (#{(baseline / ultra_fast).round(2)}x slower)"
329
+ puts "Inline optimization: #{format_ips(inline_result)} (#{(baseline / inline_result).round(2)}x slower)"
330
+ puts "Specialized method: #{format_ips(specialized_result)} (#{(baseline / specialized_result).round(2)}x slower)"
331
+
332
+ # === IDENTIFIED BOTTLENECKS ===
333
+
334
+ puts "\n" + '=' * 80
335
+ puts 'IDENTIFIED BOTTLENECKS (RANKED BY IMPACT)'
336
+ puts '=' * 80
337
+
338
+ bottlenecks = [
339
+ {
340
+ name: 'Config Hash Lookup',
341
+ impact: baseline / step1,
342
+ description: 'lazy_initializers[name] lookup w każdym call',
343
+ fix: 'Cache config at method generation time'
344
+ },
345
+ {
346
+ name: 'Mutex Synchronization',
347
+ impact: baseline / current,
348
+ description: 'Always synchronize, even for computed values',
349
+ fix: 'Fast path with double-checked locking'
350
+ },
351
+ {
352
+ name: 'LazyValue Object Overhead',
353
+ impact: baseline / step4,
354
+ description: 'Extra object layer adds method dispatch cost',
355
+ fix: 'Inline critical path in generated methods'
356
+ },
357
+ {
358
+ name: 'Instance Variable Access Pattern',
359
+ impact: baseline / step3,
360
+ description: 'instance_variable_get instead of direct @var',
361
+ fix: 'Use direct @var access in generated methods'
362
+ }
363
+ ]
364
+
365
+ bottlenecks.sort_by { |b| -b[:impact] }.each_with_index do |bottleneck, i|
366
+ puts "\n#{i + 1}. #{bottleneck[:name]} (#{bottleneck[:impact].round(2)}x impact)"
367
+ puts " Problem: #{bottleneck[:description]}"
368
+ puts " Fix: #{bottleneck[:fix]}"
369
+ end
370
+
371
+ theoretical_optimized = baseline / 1.5 # Theoretical 1.5x slower than baseline
372
+ current_gap = baseline / generated_result
373
+
374
+ puts "\nPERFORMANCE TARGETS:"
375
+ puts "Current performance: #{format_ips(generated_result)} (#{current_gap.round(2)}x slower than baseline)"
376
+ puts "Realistic target: #{format_ips(theoretical_optimized)} (1.5x slower than baseline)"
377
+ puts "Improvement needed: #{(generated_result / theoretical_optimized).round(2)}x faster"
378
+
379
+ puts "\n" + '=' * 80
380
+ puts 'ANALYSIS COMPLETED'
381
+ puts '=' * 80