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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +4 -0
- data/CHANGELOG.md +0 -0
- data/GEMFILE +5 -0
- data/LICENSE +21 -0
- data/RAKEFILE +43 -0
- data/README.md +765 -0
- data/benchmarks/benchmark.rb +796 -0
- data/benchmarks/benchmark_performance.rb +250 -0
- data/benchmarks/benchmark_threads.rb +433 -0
- data/benchmarks/bottleneck_searcher.rb +381 -0
- data/benchmarks/thread_safety_verification.rb +376 -0
- data/lazy_init.gemspec +40 -0
- data/lib/lazy_init/class_methods.rb +549 -0
- data/lib/lazy_init/configuration.rb +57 -0
- data/lib/lazy_init/dependency_resolver.rb +226 -0
- data/lib/lazy_init/errors.rb +23 -0
- data/lib/lazy_init/instance_methods.rb +291 -0
- data/lib/lazy_init/lazy_value.rb +167 -0
- data/lib/lazy_init/version.rb +5 -0
- data/lib/lazy_init.rb +47 -0
- metadata +140 -0
@@ -0,0 +1,796 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'benchmark/ips'
|
5
|
+
require_relative '../lib/lazy_init'
|
6
|
+
|
7
|
+
class LazyInitBenchmark
|
8
|
+
VERSION = '1.0.0'
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@results = {}
|
12
|
+
configure_lazy_init
|
13
|
+
puts header
|
14
|
+
end
|
15
|
+
|
16
|
+
def run_all
|
17
|
+
run_basic_patterns
|
18
|
+
run_computational_complexity
|
19
|
+
run_dependency_injection
|
20
|
+
# run_conditional_loading
|
21
|
+
run_class_level_shared
|
22
|
+
run_method_memoization
|
23
|
+
run_timeout_overhead
|
24
|
+
run_exception_handling
|
25
|
+
run_real_world_scenarios
|
26
|
+
|
27
|
+
print_summary
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def configure_lazy_init
|
33
|
+
LazyInit.configure do |config|
|
34
|
+
config.track_performance = false
|
35
|
+
config.debug = false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def header
|
40
|
+
<<~HEADER
|
41
|
+
===================================================================
|
42
|
+
LazyInit Performance Benchmark v#{VERSION}
|
43
|
+
===================================================================
|
44
|
+
Ruby: #{RUBY_VERSION} (#{RUBY_ENGINE})
|
45
|
+
Platform: #{RUBY_PLATFORM}
|
46
|
+
Time: #{Time.now}
|
47
|
+
===================================================================
|
48
|
+
HEADER
|
49
|
+
end
|
50
|
+
|
51
|
+
def benchmark_comparison(category, test_name, manual_impl, lazy_impl, warmup: true)
|
52
|
+
puts "\n--- #{test_name} ---"
|
53
|
+
|
54
|
+
# Warmup if requested
|
55
|
+
if warmup
|
56
|
+
manual_impl.call
|
57
|
+
lazy_impl.call
|
58
|
+
end
|
59
|
+
|
60
|
+
# Run benchmark
|
61
|
+
suite = Benchmark.ips do |x|
|
62
|
+
x.report('Manual', &manual_impl)
|
63
|
+
x.report('LazyInit', &lazy_impl)
|
64
|
+
x.compare!
|
65
|
+
end
|
66
|
+
|
67
|
+
# Extract results
|
68
|
+
manual_ips = suite.entries[0].ips
|
69
|
+
lazy_ips = suite.entries[1].ips
|
70
|
+
|
71
|
+
# Store results
|
72
|
+
store_result(category, test_name, manual_ips, lazy_ips)
|
73
|
+
|
74
|
+
# Print formatted results
|
75
|
+
puts "Manual: #{format_ips(manual_ips)}"
|
76
|
+
puts "LazyInit: #{format_ips(lazy_ips)}"
|
77
|
+
puts "Ratio: #{(manual_ips / lazy_ips).round(2)}x"
|
78
|
+
|
79
|
+
[manual_ips, lazy_ips]
|
80
|
+
end
|
81
|
+
|
82
|
+
def store_result(category, test_name, manual_ips, lazy_ips)
|
83
|
+
@results[category] ||= {}
|
84
|
+
@results[category][test_name] = {
|
85
|
+
manual: manual_ips,
|
86
|
+
lazy_init: lazy_ips,
|
87
|
+
ratio: (manual_ips / lazy_ips).round(2),
|
88
|
+
overhead_percent: ((lazy_ips / manual_ips - 1) * 100).round(1)
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def format_ips(ips)
|
93
|
+
case ips
|
94
|
+
when 0...1_000
|
95
|
+
"#{ips.round(0)} i/s"
|
96
|
+
when 1_000...1_000_000
|
97
|
+
"#{(ips / 1_000.0).round(1)}K i/s"
|
98
|
+
else
|
99
|
+
"#{(ips / 1_000_000.0).round(2)}M i/s"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# ========================================
|
104
|
+
# BENCHMARK SCENARIOS
|
105
|
+
# ========================================
|
106
|
+
|
107
|
+
def run_basic_patterns
|
108
|
+
puts "\n" + '=' * 60
|
109
|
+
puts '1. BASIC LAZY INITIALIZATION PATTERNS'
|
110
|
+
puts '=' * 60
|
111
|
+
|
112
|
+
# Hot path performance
|
113
|
+
manual_basic = create_manual_basic
|
114
|
+
lazy_basic = create_lazy_basic
|
115
|
+
|
116
|
+
benchmark_comparison(
|
117
|
+
'Basic Patterns',
|
118
|
+
'Hot Path (after initialization)',
|
119
|
+
-> { manual_basic.expensive_value },
|
120
|
+
-> { lazy_basic.expensive_value }
|
121
|
+
)
|
122
|
+
|
123
|
+
# Cold start performance
|
124
|
+
benchmark_comparison(
|
125
|
+
'Basic Patterns',
|
126
|
+
'Cold Start (new instances)',
|
127
|
+
-> { create_manual_basic.expensive_value },
|
128
|
+
-> { create_lazy_basic.expensive_value },
|
129
|
+
warmup: false
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
def run_computational_complexity
|
134
|
+
puts "\n" + '=' * 60
|
135
|
+
puts '2. COMPUTATIONAL COMPLEXITY SCENARIOS'
|
136
|
+
puts '=' * 60
|
137
|
+
|
138
|
+
# Light computation
|
139
|
+
manual_light = create_manual_light
|
140
|
+
lazy_light = create_lazy_light
|
141
|
+
|
142
|
+
benchmark_comparison(
|
143
|
+
'Computational Complexity',
|
144
|
+
'Lightweight (sum 1..10)',
|
145
|
+
-> { manual_light.light_computation },
|
146
|
+
-> { lazy_light.light_computation }
|
147
|
+
)
|
148
|
+
|
149
|
+
# Medium computation
|
150
|
+
manual_medium = create_manual_medium
|
151
|
+
lazy_medium = create_lazy_medium
|
152
|
+
|
153
|
+
benchmark_comparison(
|
154
|
+
'Computational Complexity',
|
155
|
+
'Medium (map+sum 1..1000)',
|
156
|
+
-> { manual_medium.medium_computation },
|
157
|
+
-> { lazy_medium.medium_computation }
|
158
|
+
)
|
159
|
+
|
160
|
+
# Heavy computation
|
161
|
+
manual_heavy = create_manual_heavy
|
162
|
+
lazy_heavy = create_lazy_heavy
|
163
|
+
|
164
|
+
benchmark_comparison(
|
165
|
+
'Computational Complexity',
|
166
|
+
'Heavy (filter+sqrt 1..10000)',
|
167
|
+
-> { manual_heavy.heavy_computation },
|
168
|
+
-> { lazy_heavy.heavy_computation }
|
169
|
+
)
|
170
|
+
end
|
171
|
+
|
172
|
+
def run_dependency_injection
|
173
|
+
puts "\n" + '=' * 60
|
174
|
+
puts '3. DEPENDENCY INJECTION PERFORMANCE'
|
175
|
+
puts '=' * 60
|
176
|
+
|
177
|
+
# Simple dependencies
|
178
|
+
manual_deps = create_manual_deps
|
179
|
+
lazy_deps = create_lazy_deps
|
180
|
+
|
181
|
+
benchmark_comparison(
|
182
|
+
'Dependency Injection',
|
183
|
+
'Simple Dependencies',
|
184
|
+
-> { manual_deps.database },
|
185
|
+
-> { lazy_deps.database }
|
186
|
+
)
|
187
|
+
|
188
|
+
# Complex dependencies
|
189
|
+
manual_complex = create_manual_complex_deps
|
190
|
+
lazy_complex = create_lazy_complex_deps
|
191
|
+
|
192
|
+
benchmark_comparison(
|
193
|
+
'Dependency Injection',
|
194
|
+
'Complex Dependencies',
|
195
|
+
-> { manual_complex.service },
|
196
|
+
-> { lazy_complex.service }
|
197
|
+
)
|
198
|
+
end
|
199
|
+
|
200
|
+
# def run_conditional_loading
|
201
|
+
# puts "\n" + "="*60
|
202
|
+
# puts "4. CONDITIONAL LOADING PERFORMANCE"
|
203
|
+
# puts "="*60
|
204
|
+
|
205
|
+
# # Condition true
|
206
|
+
# manual_true = create_manual_conditional(true)
|
207
|
+
# lazy_true = create_lazy_conditional(true)
|
208
|
+
|
209
|
+
# benchmark_comparison(
|
210
|
+
# "Conditional Loading",
|
211
|
+
# "Condition True",
|
212
|
+
# -> { manual_true.feature },
|
213
|
+
# -> { lazy_true.feature }
|
214
|
+
# )
|
215
|
+
|
216
|
+
# # Condition false
|
217
|
+
# manual_false = create_manual_conditional(false)
|
218
|
+
# lazy_false = create_lazy_conditional(false)
|
219
|
+
|
220
|
+
# benchmark_comparison(
|
221
|
+
# "Conditional Loading",
|
222
|
+
# "Condition False",
|
223
|
+
# -> { manual_false.feature },
|
224
|
+
# -> { lazy_false.feature }
|
225
|
+
# )
|
226
|
+
# end
|
227
|
+
|
228
|
+
def run_class_level_shared
|
229
|
+
puts "\n" + '=' * 60
|
230
|
+
puts '5. CLASS-LEVEL SHARED RESOURCES'
|
231
|
+
puts '=' * 60
|
232
|
+
|
233
|
+
manual_class = create_manual_class_var
|
234
|
+
lazy_class = create_lazy_class_var
|
235
|
+
|
236
|
+
benchmark_comparison(
|
237
|
+
'Class-Level Resources',
|
238
|
+
'Shared Resources',
|
239
|
+
-> { manual_class.shared_resource },
|
240
|
+
-> { lazy_class.shared_resource }
|
241
|
+
)
|
242
|
+
end
|
243
|
+
|
244
|
+
def run_method_memoization
|
245
|
+
puts "\n" + '=' * 60
|
246
|
+
puts '6. METHOD-LOCAL MEMOIZATION'
|
247
|
+
puts '=' * 60
|
248
|
+
|
249
|
+
manual_memo = create_manual_memo
|
250
|
+
lazy_memo = create_lazy_memo
|
251
|
+
|
252
|
+
benchmark_comparison(
|
253
|
+
'Method Memoization',
|
254
|
+
'Hot Path Memoization',
|
255
|
+
-> { manual_memo.expensive_calc(100) },
|
256
|
+
-> { lazy_memo.expensive_calc(100) }
|
257
|
+
)
|
258
|
+
end
|
259
|
+
|
260
|
+
def run_timeout_overhead
|
261
|
+
puts "\n" + '=' * 60
|
262
|
+
puts '7. TIMEOUT OVERHEAD'
|
263
|
+
puts '=' * 60
|
264
|
+
|
265
|
+
no_timeout = create_no_timeout
|
266
|
+
with_timeout = create_with_timeout
|
267
|
+
|
268
|
+
benchmark_comparison(
|
269
|
+
'Timeout Support',
|
270
|
+
'Timeout vs No Timeout',
|
271
|
+
-> { no_timeout.quick_operation },
|
272
|
+
-> { with_timeout.quick_operation }
|
273
|
+
)
|
274
|
+
end
|
275
|
+
|
276
|
+
def run_exception_handling
|
277
|
+
puts "\n" + '=' * 60
|
278
|
+
puts '8. EXCEPTION HANDLING OVERHEAD'
|
279
|
+
puts '=' * 60
|
280
|
+
|
281
|
+
manual_exception = create_manual_exception
|
282
|
+
lazy_exception = create_lazy_exception
|
283
|
+
|
284
|
+
benchmark_comparison(
|
285
|
+
'Exception Handling',
|
286
|
+
'Exception Recovery',
|
287
|
+
-> { manual_exception.failing_method },
|
288
|
+
-> { lazy_exception.failing_method }
|
289
|
+
)
|
290
|
+
end
|
291
|
+
|
292
|
+
def run_real_world_scenarios
|
293
|
+
puts "\n" + '=' * 60
|
294
|
+
puts '9. REAL-WORLD SCENARIOS'
|
295
|
+
puts '=' * 60
|
296
|
+
|
297
|
+
manual_webapp = create_manual_webapp
|
298
|
+
lazy_webapp = create_lazy_webapp
|
299
|
+
|
300
|
+
benchmark_comparison(
|
301
|
+
'Real-World',
|
302
|
+
'Web Application Stack',
|
303
|
+
-> { manual_webapp.application },
|
304
|
+
-> { lazy_webapp.application }
|
305
|
+
)
|
306
|
+
end
|
307
|
+
|
308
|
+
# ========================================
|
309
|
+
# FACTORY METHODS
|
310
|
+
# ========================================
|
311
|
+
|
312
|
+
def create_manual_basic
|
313
|
+
Class.new do
|
314
|
+
def expensive_value
|
315
|
+
@expensive_value ||= "computed_value_#{rand(1000)}"
|
316
|
+
end
|
317
|
+
end.new
|
318
|
+
end
|
319
|
+
|
320
|
+
def create_lazy_basic
|
321
|
+
Class.new do
|
322
|
+
extend LazyInit
|
323
|
+
lazy_attr_reader :expensive_value do
|
324
|
+
"computed_value_#{rand(1000)}"
|
325
|
+
end
|
326
|
+
end.new
|
327
|
+
end
|
328
|
+
|
329
|
+
def create_manual_light
|
330
|
+
Class.new do
|
331
|
+
def light_computation
|
332
|
+
@light_computation ||= (1..10).sum
|
333
|
+
end
|
334
|
+
end.new
|
335
|
+
end
|
336
|
+
|
337
|
+
def create_lazy_light
|
338
|
+
Class.new do
|
339
|
+
extend LazyInit
|
340
|
+
lazy_attr_reader :light_computation do
|
341
|
+
(1..10).sum
|
342
|
+
end
|
343
|
+
end.new
|
344
|
+
end
|
345
|
+
|
346
|
+
def create_manual_medium
|
347
|
+
Class.new do
|
348
|
+
def medium_computation
|
349
|
+
@medium_computation ||= (1..1000).map { |i| i * 2 }.sum
|
350
|
+
end
|
351
|
+
end.new
|
352
|
+
end
|
353
|
+
|
354
|
+
def create_lazy_medium
|
355
|
+
Class.new do
|
356
|
+
extend LazyInit
|
357
|
+
lazy_attr_reader :medium_computation do
|
358
|
+
(1..1000).map { |i| i * 2 }.sum
|
359
|
+
end
|
360
|
+
end.new
|
361
|
+
end
|
362
|
+
|
363
|
+
def create_manual_heavy
|
364
|
+
Class.new do
|
365
|
+
def heavy_computation
|
366
|
+
@heavy_computation ||= (1..10_000).select(&:even?).map { |i| Math.sqrt(i) }.sum
|
367
|
+
end
|
368
|
+
end.new
|
369
|
+
end
|
370
|
+
|
371
|
+
def create_lazy_heavy
|
372
|
+
Class.new do
|
373
|
+
extend LazyInit
|
374
|
+
lazy_attr_reader :heavy_computation do
|
375
|
+
(1..10_000).select(&:even?).map { |i| Math.sqrt(i) }.sum
|
376
|
+
end
|
377
|
+
end.new
|
378
|
+
end
|
379
|
+
|
380
|
+
def create_manual_deps
|
381
|
+
Class.new do
|
382
|
+
def config
|
383
|
+
@config ||= { database_url: 'postgresql://localhost/test' }
|
384
|
+
end
|
385
|
+
|
386
|
+
def database
|
387
|
+
@database ||= begin
|
388
|
+
config
|
389
|
+
"Connected to: #{config[:database_url]}"
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end.new
|
393
|
+
end
|
394
|
+
|
395
|
+
def create_lazy_deps
|
396
|
+
Class.new do
|
397
|
+
extend LazyInit
|
398
|
+
|
399
|
+
lazy_attr_reader :config do
|
400
|
+
{ database_url: 'postgresql://localhost/test' }
|
401
|
+
end
|
402
|
+
|
403
|
+
lazy_attr_reader :database, depends_on: [:config] do
|
404
|
+
"Connected to: #{config[:database_url]}"
|
405
|
+
end
|
406
|
+
end.new
|
407
|
+
end
|
408
|
+
|
409
|
+
def create_manual_complex_deps
|
410
|
+
Class.new do
|
411
|
+
def config
|
412
|
+
@config ||= { db_url: 'postgresql://localhost', api_key: 'test123', debug: false }
|
413
|
+
end
|
414
|
+
|
415
|
+
def database
|
416
|
+
@database ||= begin
|
417
|
+
config
|
418
|
+
"DB: #{config[:db_url]}"
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
def api_client
|
423
|
+
@api_client ||= begin
|
424
|
+
config
|
425
|
+
database
|
426
|
+
"API: #{config[:api_key]} using #{database}"
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def logger
|
431
|
+
@logger ||= begin
|
432
|
+
config
|
433
|
+
"Logger: debug=#{config[:debug]}"
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
def service
|
438
|
+
@service ||= begin
|
439
|
+
api_client
|
440
|
+
logger
|
441
|
+
"Service: #{api_client} with #{logger}"
|
442
|
+
end
|
443
|
+
end
|
444
|
+
end.new
|
445
|
+
end
|
446
|
+
|
447
|
+
def create_lazy_complex_deps
|
448
|
+
Class.new do
|
449
|
+
extend LazyInit
|
450
|
+
|
451
|
+
lazy_attr_reader :config do
|
452
|
+
{ db_url: 'postgresql://localhost', api_key: 'test123', debug: false }
|
453
|
+
end
|
454
|
+
|
455
|
+
lazy_attr_reader :database, depends_on: [:config] do
|
456
|
+
"DB: #{config[:db_url]}"
|
457
|
+
end
|
458
|
+
|
459
|
+
lazy_attr_reader :api_client, depends_on: %i[config database] do
|
460
|
+
"API: #{config[:api_key]} using #{database}"
|
461
|
+
end
|
462
|
+
|
463
|
+
lazy_attr_reader :logger, depends_on: [:config] do
|
464
|
+
"Logger: debug=#{config[:debug]}"
|
465
|
+
end
|
466
|
+
|
467
|
+
lazy_attr_reader :service, depends_on: %i[api_client logger] do
|
468
|
+
"Service: #{api_client} with #{logger}"
|
469
|
+
end
|
470
|
+
end.new
|
471
|
+
end
|
472
|
+
|
473
|
+
def create_manual_conditional(enabled)
|
474
|
+
Class.new do
|
475
|
+
attr_accessor :feature_enabled
|
476
|
+
|
477
|
+
def initialize(enabled)
|
478
|
+
@feature_enabled = enabled
|
479
|
+
end
|
480
|
+
|
481
|
+
def feature
|
482
|
+
return nil unless feature_enabled
|
483
|
+
|
484
|
+
@feature ||= 'Feature loaded'
|
485
|
+
end
|
486
|
+
end.new(enabled)
|
487
|
+
end
|
488
|
+
|
489
|
+
# def create_lazy_conditional(enabled)
|
490
|
+
# Class.new do
|
491
|
+
# extend LazyInit
|
492
|
+
# attr_accessor :feature_enabled
|
493
|
+
|
494
|
+
# def initialize(enabled)
|
495
|
+
# @feature_enabled = enabled
|
496
|
+
# end
|
497
|
+
|
498
|
+
# lazy_attr_reader :feature, if_condition: -> { feature_enabled } do
|
499
|
+
# "Feature loaded"
|
500
|
+
# end
|
501
|
+
# end.new(enabled)
|
502
|
+
# end
|
503
|
+
|
504
|
+
def create_manual_class_var
|
505
|
+
Class.new do
|
506
|
+
def self.shared_resource
|
507
|
+
@@shared_resource ||= "Shared resource #{rand(1000)}"
|
508
|
+
end
|
509
|
+
|
510
|
+
def shared_resource
|
511
|
+
self.class.shared_resource
|
512
|
+
end
|
513
|
+
end.new
|
514
|
+
end
|
515
|
+
|
516
|
+
def create_lazy_class_var
|
517
|
+
klass = Class.new do
|
518
|
+
extend LazyInit
|
519
|
+
|
520
|
+
lazy_class_variable :shared_resource do
|
521
|
+
"Shared resource #{rand(1000)}"
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
# Initialize the class variable
|
526
|
+
klass.shared_resource
|
527
|
+
klass.new
|
528
|
+
end
|
529
|
+
|
530
|
+
def create_manual_memo
|
531
|
+
Class.new do
|
532
|
+
def expensive_calc(key)
|
533
|
+
@memo ||= {}
|
534
|
+
@memo[key] ||= "computed_#{key}_#{rand(1000)}"
|
535
|
+
end
|
536
|
+
end.new
|
537
|
+
end
|
538
|
+
|
539
|
+
def create_lazy_memo
|
540
|
+
Class.new do
|
541
|
+
include LazyInit
|
542
|
+
|
543
|
+
def expensive_calc(key)
|
544
|
+
lazy_once { "computed_#{key}_#{rand(1000)}" }
|
545
|
+
end
|
546
|
+
end.new
|
547
|
+
end
|
548
|
+
|
549
|
+
def create_no_timeout
|
550
|
+
Class.new do
|
551
|
+
extend LazyInit
|
552
|
+
|
553
|
+
lazy_attr_reader :quick_operation do
|
554
|
+
(1..10).sum
|
555
|
+
end
|
556
|
+
end.new
|
557
|
+
end
|
558
|
+
|
559
|
+
def create_with_timeout
|
560
|
+
Class.new do
|
561
|
+
extend LazyInit
|
562
|
+
|
563
|
+
lazy_attr_reader :quick_operation, timeout: 5 do
|
564
|
+
(1..10).sum
|
565
|
+
end
|
566
|
+
end.new
|
567
|
+
end
|
568
|
+
|
569
|
+
def create_manual_exception
|
570
|
+
Class.new do
|
571
|
+
def failing_method
|
572
|
+
@failing_method ||= begin
|
573
|
+
raise StandardError, 'Always fails'
|
574
|
+
rescue StandardError
|
575
|
+
'recovered'
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end.new
|
579
|
+
end
|
580
|
+
|
581
|
+
def create_lazy_exception
|
582
|
+
Class.new do
|
583
|
+
extend LazyInit
|
584
|
+
|
585
|
+
lazy_attr_reader :failing_method do
|
586
|
+
raise StandardError, 'Always fails'
|
587
|
+
rescue StandardError
|
588
|
+
'recovered'
|
589
|
+
end
|
590
|
+
end.new
|
591
|
+
end
|
592
|
+
|
593
|
+
def create_manual_webapp
|
594
|
+
Class.new do
|
595
|
+
def config
|
596
|
+
@config ||= {
|
597
|
+
database_url: ENV['DATABASE_URL'] || 'postgresql://localhost/app',
|
598
|
+
redis_url: ENV['REDIS_URL'] || 'redis://localhost:6379',
|
599
|
+
api_key: ENV['API_KEY'] || 'test_key',
|
600
|
+
debug: ENV['DEBUG'] == 'true'
|
601
|
+
}
|
602
|
+
end
|
603
|
+
|
604
|
+
def database
|
605
|
+
@database ||= begin
|
606
|
+
config
|
607
|
+
"Database connection: #{config[:database_url]}"
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
def cache
|
612
|
+
@cache ||= begin
|
613
|
+
config
|
614
|
+
"Redis connection: #{config[:redis_url]}"
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
def api_client
|
619
|
+
@api_client ||= begin
|
620
|
+
config
|
621
|
+
"API client with key: #{config[:api_key]}"
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
def logger
|
626
|
+
@logger ||= begin
|
627
|
+
config
|
628
|
+
"Logger (debug: #{config[:debug]})"
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
def application
|
633
|
+
@application ||= begin
|
634
|
+
database
|
635
|
+
cache
|
636
|
+
api_client
|
637
|
+
logger
|
638
|
+
'Application initialized with all services'
|
639
|
+
end
|
640
|
+
end
|
641
|
+
end.new
|
642
|
+
end
|
643
|
+
|
644
|
+
def create_lazy_webapp
|
645
|
+
Class.new do
|
646
|
+
extend LazyInit
|
647
|
+
|
648
|
+
lazy_attr_reader :config do
|
649
|
+
{
|
650
|
+
database_url: ENV['DATABASE_URL'] || 'postgresql://localhost/app',
|
651
|
+
redis_url: ENV['REDIS_URL'] || 'redis://localhost:6379',
|
652
|
+
api_key: ENV['API_KEY'] || 'test_key',
|
653
|
+
debug: ENV['DEBUG'] == 'true'
|
654
|
+
}
|
655
|
+
end
|
656
|
+
|
657
|
+
lazy_attr_reader :database, depends_on: [:config] do
|
658
|
+
"Database connection: #{config[:database_url]}"
|
659
|
+
end
|
660
|
+
|
661
|
+
lazy_attr_reader :cache, depends_on: [:config] do
|
662
|
+
"Redis connection: #{config[:redis_url]}"
|
663
|
+
end
|
664
|
+
|
665
|
+
lazy_attr_reader :api_client, depends_on: [:config] do
|
666
|
+
"API client with key: #{config[:api_key]}"
|
667
|
+
end
|
668
|
+
|
669
|
+
lazy_attr_reader :logger, depends_on: [:config] do
|
670
|
+
"Logger (debug: #{config[:debug]})"
|
671
|
+
end
|
672
|
+
|
673
|
+
lazy_attr_reader :application, depends_on: %i[database cache api_client logger] do
|
674
|
+
'Application initialized with all services'
|
675
|
+
end
|
676
|
+
end.new
|
677
|
+
end
|
678
|
+
|
679
|
+
# ========================================
|
680
|
+
# RESULTS & SUMMARY
|
681
|
+
# ========================================
|
682
|
+
|
683
|
+
def print_summary
|
684
|
+
puts "\n" + '=' * 80
|
685
|
+
puts 'BENCHMARK SUMMARY'
|
686
|
+
puts '=' * 80
|
687
|
+
|
688
|
+
print_detailed_results
|
689
|
+
print_performance_analysis
|
690
|
+
print_recommendations
|
691
|
+
end
|
692
|
+
|
693
|
+
def print_detailed_results
|
694
|
+
puts "\nDetailed Results:"
|
695
|
+
puts '-' * 50
|
696
|
+
|
697
|
+
@results.each do |category, tests|
|
698
|
+
puts "\n#{category}:"
|
699
|
+
|
700
|
+
tests.each do |test_name, data|
|
701
|
+
overhead_str = if data[:overhead_percent] < -50
|
702
|
+
"#{(-data[:overhead_percent]).round(1)}% faster"
|
703
|
+
elsif data[:overhead_percent] > 0
|
704
|
+
"#{data[:ratio]}x slower"
|
705
|
+
else
|
706
|
+
'similar performance'
|
707
|
+
end
|
708
|
+
|
709
|
+
puts " #{test_name}:"
|
710
|
+
puts " Manual: #{format_ips(data[:manual])}"
|
711
|
+
puts " LazyInit: #{format_ips(data[:lazy_init])}"
|
712
|
+
puts " Result: #{overhead_str}"
|
713
|
+
end
|
714
|
+
end
|
715
|
+
end
|
716
|
+
|
717
|
+
def print_performance_analysis
|
718
|
+
puts "\n" + '=' * 50
|
719
|
+
puts 'PERFORMANCE ANALYSIS'
|
720
|
+
puts '=' * 50
|
721
|
+
|
722
|
+
all_ratios = @results.values.flat_map(&:values).map { |data| data[:ratio] }
|
723
|
+
avg_ratio = (all_ratios.sum / all_ratios.size).round(2)
|
724
|
+
min_ratio = all_ratios.min.round(2)
|
725
|
+
max_ratio = all_ratios.max.round(2)
|
726
|
+
|
727
|
+
puts "\nOverall Performance Impact:"
|
728
|
+
puts " Average slowdown: #{avg_ratio}x"
|
729
|
+
puts " Best case: #{min_ratio}x slower"
|
730
|
+
puts " Worst case: #{max_ratio}x slower"
|
731
|
+
|
732
|
+
# Analyze patterns
|
733
|
+
computational = @results['Computational Complexity']
|
734
|
+
if computational
|
735
|
+
light_ratio = computational['Lightweight (sum 1..10)'][:ratio]
|
736
|
+
heavy_ratio = computational['Heavy (filter+sqrt 1..10000)'][:ratio]
|
737
|
+
|
738
|
+
if light_ratio > heavy_ratio * 1.5
|
739
|
+
puts "\n• LazyInit overhead decreases with computation complexity"
|
740
|
+
puts '• Better suited for expensive operations'
|
741
|
+
else
|
742
|
+
puts "\n• LazyInit overhead is consistent across complexity levels"
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
746
|
+
conditional = @results['Conditional Loading']
|
747
|
+
return unless conditional
|
748
|
+
|
749
|
+
true_ratio = conditional['Condition True'][:ratio]
|
750
|
+
false_ratio = conditional['Condition False'][:ratio]
|
751
|
+
|
752
|
+
return unless false_ratio < true_ratio * 0.7
|
753
|
+
|
754
|
+
puts '• Conditional loading is efficient when conditions are false'
|
755
|
+
end
|
756
|
+
|
757
|
+
def print_recommendations
|
758
|
+
puts "\n" + '=' * 50
|
759
|
+
puts 'RECOMMENDATIONS'
|
760
|
+
puts '=' * 50
|
761
|
+
|
762
|
+
all_ratios = @results.values.flat_map(&:values).map { |data| data[:ratio] }
|
763
|
+
avg_ratio = all_ratios.sum / all_ratios.size
|
764
|
+
|
765
|
+
puts "\nWhen to use LazyInit:"
|
766
|
+
if avg_ratio > 10
|
767
|
+
puts '• Only for very expensive initialization (>10ms)'
|
768
|
+
puts '• When thread safety is critical'
|
769
|
+
puts '• Complex dependency chains'
|
770
|
+
elsif avg_ratio > 5
|
771
|
+
puts '• Expensive initialization (>1ms)'
|
772
|
+
puts '• Multi-threaded applications'
|
773
|
+
puts '• When manual synchronization is error-prone'
|
774
|
+
else
|
775
|
+
puts '• Most lazy initialization scenarios'
|
776
|
+
puts '• Thread-safe applications'
|
777
|
+
puts '• Clean dependency management needed'
|
778
|
+
end
|
779
|
+
|
780
|
+
puts "\n" + '=' * 80
|
781
|
+
puts 'BENCHMARK COMPLETED'
|
782
|
+
puts '=' * 80
|
783
|
+
end
|
784
|
+
end
|
785
|
+
|
786
|
+
# Run the benchmark
|
787
|
+
if __FILE__ == $0
|
788
|
+
begin
|
789
|
+
benchmark = LazyInitBenchmark.new
|
790
|
+
benchmark.run_all
|
791
|
+
rescue StandardError => e
|
792
|
+
puts "Benchmark failed: #{e.message}"
|
793
|
+
puts e.backtrace.first(5)
|
794
|
+
exit 1
|
795
|
+
end
|
796
|
+
end
|