lazy_init 0.1.2 → 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.
@@ -5,11 +5,10 @@ require 'benchmark/ips'
5
5
  require_relative '../lib/lazy_init'
6
6
 
7
7
  class LazyInitBenchmark
8
- VERSION = '1.0.0'
8
+ VERSION = '2.0.0'
9
9
 
10
10
  def initialize
11
11
  @results = {}
12
- configure_lazy_init
13
12
  puts header
14
13
  end
15
14
 
@@ -17,11 +16,11 @@ class LazyInitBenchmark
17
16
  run_basic_patterns
18
17
  run_computational_complexity
19
18
  run_dependency_injection
20
- # run_conditional_loading
21
19
  run_class_level_shared
22
20
  run_method_memoization
23
21
  run_timeout_overhead
24
22
  run_exception_handling
23
+ run_thread_safety
25
24
  run_real_world_scenarios
26
25
 
27
26
  print_summary
@@ -29,17 +28,10 @@ class LazyInitBenchmark
29
28
 
30
29
  private
31
30
 
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
31
  def header
40
32
  <<~HEADER
41
33
  ===================================================================
42
- LazyInit Performance Benchmark v#{VERSION}
34
+ LazyInit Performance Benchmark v#{VERSION} (Fixed Methodology)
43
35
  ===================================================================
44
36
  Ruby: #{RUBY_VERSION} (#{RUBY_ENGINE})
45
37
  Platform: #{RUBY_PLATFORM}
@@ -51,14 +43,22 @@ class LazyInitBenchmark
51
43
  def benchmark_comparison(category, test_name, manual_impl, lazy_impl, warmup: true)
52
44
  puts "\n--- #{test_name} ---"
53
45
 
54
- # Warmup if requested
46
+ # Enhanced warmup for more reliable results
55
47
  if warmup
56
- manual_impl.call
57
- lazy_impl.call
48
+ puts ' Warming up...'
49
+ 5.times do
50
+ manual_impl.call
51
+ lazy_impl.call
52
+ end
53
+
54
+ # Additional warmup for GC stabilization
55
+ GC.start
56
+ sleep 0.01
58
57
  end
59
58
 
60
- # Run benchmark
59
+ # Run benchmark with more iterations for stability
61
60
  suite = Benchmark.ips do |x|
61
+ x.config(time: 5, warmup: 2)
62
62
  x.report('Manual', &manual_impl)
63
63
  x.report('LazyInit', &lazy_impl)
64
64
  x.compare!
@@ -105,14 +105,18 @@ class LazyInitBenchmark
105
105
  # ========================================
106
106
 
107
107
  def run_basic_patterns
108
- puts "\n" + '=' * 60
108
+ puts "\n" + '=' * 30
109
109
  puts '1. BASIC LAZY INITIALIZATION PATTERNS'
110
- puts '=' * 60
110
+ puts '=' * 30
111
111
 
112
- # Hot path performance
112
+ # Hot path performance - fair comparison after both are initialized
113
113
  manual_basic = create_manual_basic
114
114
  lazy_basic = create_lazy_basic
115
115
 
116
+ # Pre-initialize both for hot path test
117
+ manual_basic.expensive_value
118
+ lazy_basic.expensive_value
119
+
116
120
  benchmark_comparison(
117
121
  'Basic Patterns',
118
122
  'Hot Path (after initialization)',
@@ -120,7 +124,7 @@ class LazyInitBenchmark
120
124
  -> { lazy_basic.expensive_value }
121
125
  )
122
126
 
123
- # Cold start performance
127
+ # Cold start performance - fresh instances each time
124
128
  benchmark_comparison(
125
129
  'Basic Patterns',
126
130
  'Cold Start (new instances)',
@@ -128,12 +132,21 @@ class LazyInitBenchmark
128
132
  -> { create_lazy_basic.expensive_value },
129
133
  warmup: false
130
134
  )
135
+
136
+ # Memory overhead test - check after many instances
137
+ benchmark_comparison(
138
+ 'Basic Patterns',
139
+ 'Memory Overhead (100 instances)',
140
+ -> { 100.times { create_manual_basic } },
141
+ -> { 100.times { create_lazy_basic } },
142
+ warmup: false
143
+ )
131
144
  end
132
145
 
133
146
  def run_computational_complexity
134
- puts "\n" + '=' * 60
147
+ puts "\n" + '=' * 30
135
148
  puts '2. COMPUTATIONAL COMPLEXITY SCENARIOS'
136
- puts '=' * 60
149
+ puts '=' * 30
137
150
 
138
151
  # Light computation
139
152
  manual_light = create_manual_light
@@ -170,9 +183,9 @@ class LazyInitBenchmark
170
183
  end
171
184
 
172
185
  def run_dependency_injection
173
- puts "\n" + '=' * 60
186
+ puts "\n" + '=' * 30
174
187
  puts '3. DEPENDENCY INJECTION PERFORMANCE'
175
- puts '=' * 60
188
+ puts '=' * 30
176
189
 
177
190
  # Simple dependencies
178
191
  manual_deps = create_manual_deps
@@ -195,89 +208,149 @@ class LazyInitBenchmark
195
208
  -> { manual_complex.service },
196
209
  -> { lazy_complex.service }
197
210
  )
198
- end
199
211
 
200
- # def run_conditional_loading
201
- # puts "\n" + "="*60
202
- # puts "4. CONDITIONAL LOADING PERFORMANCE"
203
- # puts "="*60
212
+ # Mixed access patterns
213
+ manual_mixed = create_manual_complex_deps
214
+ lazy_mixed = create_lazy_complex_deps
204
215
 
205
- # # Condition true
206
- # manual_true = create_manual_conditional(true)
207
- # lazy_true = create_lazy_conditional(true)
216
+ benchmark_comparison(
217
+ 'Dependency Injection',
218
+ 'Mixed Access Pattern',
219
+ lambda {
220
+ manual_mixed.config
221
+ manual_mixed.database
222
+ manual_mixed.service
223
+ },
224
+ lambda {
225
+ lazy_mixed.config
226
+ lazy_mixed.database
227
+ lazy_mixed.service
228
+ }
229
+ )
230
+ end
208
231
 
209
- # benchmark_comparison(
210
- # "Conditional Loading",
211
- # "Condition True",
212
- # -> { manual_true.feature },
213
- # -> { lazy_true.feature }
214
- # )
232
+ def run_class_level_shared
233
+ puts "\n" + '=' * 30
234
+ puts '4. CLASS-LEVEL SHARED RESOURCES'
235
+ puts '=' * 30
215
236
 
216
- # # Condition false
217
- # manual_false = create_manual_conditional(false)
218
- # lazy_false = create_lazy_conditional(false)
237
+ # FIXED: Don't pre-initialize class variables
238
+ benchmark_comparison(
239
+ 'Class-Level Resources',
240
+ 'Shared Resources (Cold)',
241
+ -> { create_manual_class_var.shared_resource },
242
+ -> { create_lazy_class_var.shared_resource }
243
+ )
219
244
 
220
- # benchmark_comparison(
221
- # "Conditional Loading",
222
- # "Condition False",
223
- # -> { manual_false.feature },
224
- # -> { lazy_false.feature }
225
- # )
226
- # end
245
+ # Hot path for class variables
246
+ manual_class_hot = create_manual_class_var
247
+ lazy_class_hot = create_lazy_class_var
227
248
 
228
- def run_class_level_shared
229
- puts "\n" + '=' * 60
230
- puts '5. CLASS-LEVEL SHARED RESOURCES'
231
- puts '=' * 60
249
+ # Initialize both
250
+ manual_class_hot.shared_resource
251
+ lazy_class_hot.shared_resource
232
252
 
233
- manual_class = create_manual_class_var
234
- lazy_class = create_lazy_class_var
253
+ benchmark_comparison(
254
+ 'Class-Level Resources',
255
+ 'Shared Resources (Hot)',
256
+ -> { manual_class_hot.shared_resource },
257
+ -> { lazy_class_hot.shared_resource }
258
+ )
235
259
 
260
+ # Multiple instances accessing same class variable
236
261
  benchmark_comparison(
237
262
  'Class-Level Resources',
238
- 'Shared Resources',
239
- -> { manual_class.shared_resource },
240
- -> { lazy_class.shared_resource }
263
+ 'Multiple Instances Access',
264
+ lambda {
265
+ instances = 5.times.map { create_manual_class_var }
266
+ instances.each(&:shared_resource)
267
+ },
268
+ lambda {
269
+ instances = 5.times.map { create_lazy_class_var }
270
+ instances.each(&:shared_resource)
271
+ }
241
272
  )
242
273
  end
243
274
 
244
275
  def run_method_memoization
245
- puts "\n" + '=' * 60
246
- puts '6. METHOD-LOCAL MEMOIZATION'
247
- puts '=' * 60
276
+ puts "\n" + '=' * 30
277
+ puts '5. METHOD-LOCAL MEMOIZATION'
278
+ puts '=' * 30
248
279
 
280
+ # FIXED: Fair comparison with same caching behavior
249
281
  manual_memo = create_manual_memo
250
282
  lazy_memo = create_lazy_memo
251
283
 
284
+ # Test with same key (cache hit scenario)
252
285
  benchmark_comparison(
253
286
  'Method Memoization',
254
- 'Hot Path Memoization',
287
+ 'Same Key Cache Hit',
255
288
  -> { manual_memo.expensive_calc(100) },
256
289
  -> { lazy_memo.expensive_calc(100) }
257
290
  )
291
+
292
+ # Test with different keys (cache miss scenario)
293
+ key_counter = 0
294
+ benchmark_comparison(
295
+ 'Method Memoization',
296
+ 'Different Keys',
297
+ lambda {
298
+ key_counter += 1
299
+ create_manual_memo.expensive_calc(key_counter)
300
+ },
301
+ lambda {
302
+ key_counter += 1
303
+ create_lazy_memo.expensive_calc(key_counter)
304
+ },
305
+ warmup: false
306
+ )
307
+
308
+ # Test cache performance with many keys
309
+ benchmark_comparison(
310
+ 'Method Memoization',
311
+ 'Many Keys Performance',
312
+ lambda {
313
+ memo = create_manual_memo
314
+ 100.times { |i| memo.expensive_calc(i) }
315
+ },
316
+ lambda {
317
+ memo = create_lazy_memo
318
+ 100.times { |i| memo.expensive_calc(i) }
319
+ }
320
+ )
258
321
  end
259
322
 
260
323
  def run_timeout_overhead
261
- puts "\n" + '=' * 60
262
- puts '7. TIMEOUT OVERHEAD'
263
- puts '=' * 60
324
+ puts "\n" + '=' * 30
325
+ puts '6. TIMEOUT OVERHEAD'
326
+ puts '=' * 30
264
327
 
265
328
  no_timeout = create_no_timeout
266
329
  with_timeout = create_with_timeout
267
330
 
268
331
  benchmark_comparison(
269
332
  'Timeout Support',
270
- 'Timeout vs No Timeout',
333
+ 'No Timeout vs With Timeout',
271
334
  -> { no_timeout.quick_operation },
272
335
  -> { with_timeout.quick_operation }
273
336
  )
337
+
338
+ # Test timeout configuration overhead
339
+ benchmark_comparison(
340
+ 'Timeout Support',
341
+ 'Timeout Configuration Cost',
342
+ -> { create_no_timeout.quick_operation },
343
+ -> { create_with_timeout.quick_operation },
344
+ warmup: false
345
+ )
274
346
  end
275
347
 
276
348
  def run_exception_handling
277
- puts "\n" + '=' * 60
278
- puts '8. EXCEPTION HANDLING OVERHEAD'
279
- puts '=' * 60
349
+ puts "\n" + '=' * 30
350
+ puts '7. EXCEPTION HANDLING OVERHEAD'
351
+ puts '=' * 30
280
352
 
353
+ # FIXED: Same exception handling behavior
281
354
  manual_exception = create_manual_exception
282
355
  lazy_exception = create_lazy_exception
283
356
 
@@ -287,12 +360,134 @@ class LazyInitBenchmark
287
360
  -> { manual_exception.failing_method },
288
361
  -> { lazy_exception.failing_method }
289
362
  )
363
+
364
+ # Test exception caching behavior
365
+ manual_exception_cached = create_manual_exception_cached
366
+ lazy_exception_cached = create_lazy_exception_cached
367
+
368
+ benchmark_comparison(
369
+ 'Exception Handling',
370
+ 'Exception Caching',
371
+ lambda {
372
+ begin
373
+ manual_exception_cached.always_fails
374
+ rescue StandardError
375
+ # Exception not cached in manual approach
376
+ end
377
+ },
378
+ lambda {
379
+ begin
380
+ lazy_exception_cached.always_fails
381
+ rescue StandardError
382
+ # Exception cached in LazyInit
383
+ end
384
+ }
385
+ )
386
+ end
387
+
388
+ def run_thread_safety
389
+ puts "\n" + '=' * 30
390
+ puts '8. THREAD SAFETY PERFORMANCE'
391
+ puts '=' * 30
392
+
393
+ # This is where LazyInit should shine - testing concurrent access
394
+ manual_concurrent = create_manual_concurrent
395
+ lazy_concurrent = create_lazy_concurrent
396
+
397
+ benchmark_comparison(
398
+ 'Thread Safety',
399
+ 'Concurrent Access (10 threads)',
400
+ lambda {
401
+ threads = 10.times.map do
402
+ Thread.new { manual_concurrent.thread_safe_value }
403
+ end
404
+ threads.each(&:join)
405
+ },
406
+ lambda {
407
+ threads = 10.times.map do
408
+ Thread.new { lazy_concurrent.thread_safe_value }
409
+ end
410
+ threads.each(&:join)
411
+ }
412
+ )
413
+
414
+ # Test under high contention
415
+ benchmark_comparison(
416
+ 'Thread Safety',
417
+ 'High Contention (50 threads)',
418
+ lambda {
419
+ instance = create_manual_concurrent
420
+ threads = 50.times.map do
421
+ Thread.new { instance.thread_safe_value }
422
+ end
423
+ threads.each(&:join)
424
+ },
425
+ lambda {
426
+ instance = create_lazy_concurrent
427
+ threads = 50.times.map do
428
+ Thread.new { instance.thread_safe_value }
429
+ end
430
+ threads.each(&:join)
431
+ }
432
+ )
433
+
434
+ # Test reset safety under concurrent access
435
+ benchmark_comparison(
436
+ 'Thread Safety',
437
+ 'Concurrent Reset Safety',
438
+ lambda {
439
+ instance = create_manual_concurrent
440
+ threads = []
441
+
442
+ # Readers
443
+ 10.times do
444
+ threads << Thread.new do
445
+ 20.times { instance.resettable_value }
446
+ end
447
+ end
448
+
449
+ # Resetters
450
+ 2.times do
451
+ threads << Thread.new do
452
+ 5.times do
453
+ sleep 0.001
454
+ instance.reset_resettable_value!
455
+ end
456
+ end
457
+ end
458
+
459
+ threads.each(&:join)
460
+ },
461
+ lambda {
462
+ instance = create_lazy_concurrent
463
+ threads = []
464
+
465
+ # Readers
466
+ 10.times do
467
+ threads << Thread.new do
468
+ 20.times { instance.resettable_value }
469
+ end
470
+ end
471
+
472
+ # Resetters
473
+ 2.times do
474
+ threads << Thread.new do
475
+ 5.times do
476
+ sleep 0.001
477
+ instance.reset_resettable_value!
478
+ end
479
+ end
480
+ end
481
+
482
+ threads.each(&:join)
483
+ }
484
+ )
290
485
  end
291
486
 
292
487
  def run_real_world_scenarios
293
- puts "\n" + '=' * 60
488
+ puts "\n" + '=' * 30
294
489
  puts '9. REAL-WORLD SCENARIOS'
295
- puts '=' * 60
490
+ puts '=' * 30
296
491
 
297
492
  manual_webapp = create_manual_webapp
298
493
  lazy_webapp = create_lazy_webapp
@@ -303,10 +498,44 @@ class LazyInitBenchmark
303
498
  -> { manual_webapp.application },
304
499
  -> { lazy_webapp.application }
305
500
  )
501
+
502
+ # Rails-like service pattern
503
+ benchmark_comparison(
504
+ 'Real-World',
505
+ 'Service Container Pattern',
506
+ lambda {
507
+ container = create_manual_service_container
508
+ container.user_service
509
+ container.notification_service
510
+ container.payment_service
511
+ },
512
+ lambda {
513
+ container = create_lazy_service_container
514
+ container.user_service
515
+ container.notification_service
516
+ container.payment_service
517
+ }
518
+ )
519
+
520
+ # Background job pattern
521
+ benchmark_comparison(
522
+ 'Real-World',
523
+ 'Background Job Setup',
524
+ lambda {
525
+ job = create_manual_background_job
526
+ job.setup_dependencies
527
+ job.process_data('test_data')
528
+ },
529
+ lambda {
530
+ job = create_lazy_background_job
531
+ job.setup_dependencies
532
+ job.process_data('test_data')
533
+ }
534
+ )
306
535
  end
307
536
 
308
537
  # ========================================
309
- # FACTORY METHODS
538
+ # FACTORY METHODS (FIXED)
310
539
  # ========================================
311
540
 
312
541
  def create_manual_basic
@@ -470,38 +699,9 @@ class LazyInitBenchmark
470
699
  end.new
471
700
  end
472
701
 
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
-
702
+ # FIXED: Class variable tests without pre-initialization
504
703
  def create_manual_class_var
704
+ # Create fresh class each time to avoid shared state
505
705
  Class.new do
506
706
  def self.shared_resource
507
707
  @@shared_resource ||= "Shared resource #{rand(1000)}"
@@ -514,19 +714,17 @@ class LazyInitBenchmark
514
714
  end
515
715
 
516
716
  def create_lazy_class_var
517
- klass = Class.new do
717
+ # Create fresh class each time without pre-initialization
718
+ Class.new do
518
719
  extend LazyInit
519
720
 
520
721
  lazy_class_variable :shared_resource do
521
722
  "Shared resource #{rand(1000)}"
522
723
  end
523
- end
524
-
525
- # Initialize the class variable
526
- klass.shared_resource
527
- klass.new
724
+ end.new
528
725
  end
529
726
 
727
+ # FIXED: Method memoization with fair comparison
530
728
  def create_manual_memo
531
729
  Class.new do
532
730
  def expensive_calc(key)
@@ -540,8 +738,15 @@ class LazyInitBenchmark
540
738
  Class.new do
541
739
  include LazyInit
542
740
 
741
+ def initialize
742
+ @memo_cache = {}
743
+ end
744
+
543
745
  def expensive_calc(key)
544
- lazy_once { "computed_#{key}_#{rand(1000)}" }
746
+ # Fair comparison: cache per key like manual version
747
+ return @memo_cache[key] if @memo_cache.key?(key)
748
+
749
+ @memo_cache[key] = lazy_once { "computed_#{key}_#{rand(1000)}" }
545
750
  end
546
751
  end.new
547
752
  end
@@ -566,6 +771,7 @@ class LazyInitBenchmark
566
771
  end.new
567
772
  end
568
773
 
774
+ # FIXED: Exception handling with same behavior
569
775
  def create_manual_exception
570
776
  Class.new do
571
777
  def failing_method
@@ -590,6 +796,78 @@ class LazyInitBenchmark
590
796
  end.new
591
797
  end
592
798
 
799
+ # NEW: Exception caching behavior tests
800
+ def create_manual_exception_cached
801
+ Class.new do
802
+ def always_fails
803
+ # Manual approach doesn't cache exceptions
804
+ raise StandardError, 'Always fails'
805
+ end
806
+ end.new
807
+ end
808
+
809
+ def create_lazy_exception_cached
810
+ Class.new do
811
+ extend LazyInit
812
+
813
+ lazy_attr_reader :always_fails do
814
+ raise StandardError, 'Always fails'
815
+ end
816
+ end.new
817
+ end
818
+
819
+ # NEW: Thread safety test implementations
820
+ def create_manual_concurrent
821
+ Class.new do
822
+ def initialize
823
+ @mutex = Mutex.new
824
+ @reset_mutex = Mutex.new
825
+ end
826
+
827
+ def thread_safe_value
828
+ return @thread_safe_value if defined?(@thread_safe_value)
829
+
830
+ @mutex.synchronize do
831
+ return @thread_safe_value if defined?(@thread_safe_value)
832
+
833
+ @thread_safe_value = "thread_safe_#{rand(1000)}"
834
+ end
835
+ end
836
+
837
+ def resettable_value
838
+ return @resettable_value if defined?(@resettable_value)
839
+
840
+ @mutex.synchronize do
841
+ return @resettable_value if defined?(@resettable_value)
842
+
843
+ @resettable_value = "resettable_#{rand(1000)}"
844
+ end
845
+ end
846
+
847
+ def reset_resettable_value!
848
+ @reset_mutex.synchronize do
849
+ @mutex.synchronize do
850
+ remove_instance_variable(:@resettable_value) if defined?(@resettable_value)
851
+ end
852
+ end
853
+ end
854
+ end.new
855
+ end
856
+
857
+ def create_lazy_concurrent
858
+ Class.new do
859
+ extend LazyInit
860
+
861
+ lazy_attr_reader :thread_safe_value do
862
+ "thread_safe_#{rand(1000)}"
863
+ end
864
+
865
+ lazy_attr_reader :resettable_value do
866
+ "resettable_#{rand(1000)}"
867
+ end
868
+ end.new
869
+ end
870
+
593
871
  def create_manual_webapp
594
872
  Class.new do
595
873
  def config
@@ -676,34 +954,155 @@ class LazyInitBenchmark
676
954
  end.new
677
955
  end
678
956
 
957
+ # NEW: Additional realistic test cases
958
+ def create_manual_service_container
959
+ Class.new do
960
+ def database_config
961
+ @database_config ||= { url: 'postgresql://localhost/app' }
962
+ end
963
+
964
+ def redis_config
965
+ @redis_config ||= { url: 'redis://localhost:6379' }
966
+ end
967
+
968
+ def user_service
969
+ @user_service ||= begin
970
+ database_config
971
+ "UserService using #{database_config[:url]}"
972
+ end
973
+ end
974
+
975
+ def notification_service
976
+ @notification_service ||= begin
977
+ redis_config
978
+ user_service
979
+ "NotificationService using #{redis_config[:url]} and #{user_service}"
980
+ end
981
+ end
982
+
983
+ def payment_service
984
+ @payment_service ||= begin
985
+ user_service
986
+ "PaymentService using #{user_service}"
987
+ end
988
+ end
989
+ end.new
990
+ end
991
+
992
+ def create_lazy_service_container
993
+ Class.new do
994
+ extend LazyInit
995
+
996
+ lazy_attr_reader :database_config do
997
+ { url: 'postgresql://localhost/app' }
998
+ end
999
+
1000
+ lazy_attr_reader :redis_config do
1001
+ { url: 'redis://localhost:6379' }
1002
+ end
1003
+
1004
+ lazy_attr_reader :user_service, depends_on: [:database_config] do
1005
+ "UserService using #{database_config[:url]}"
1006
+ end
1007
+
1008
+ lazy_attr_reader :notification_service, depends_on: %i[redis_config user_service] do
1009
+ "NotificationService using #{redis_config[:url]} and #{user_service}"
1010
+ end
1011
+
1012
+ lazy_attr_reader :payment_service, depends_on: [:user_service] do
1013
+ "PaymentService using #{user_service}"
1014
+ end
1015
+ end.new
1016
+ end
1017
+
1018
+ def create_manual_background_job
1019
+ Class.new do
1020
+ def setup_dependencies
1021
+ processor
1022
+ storage
1023
+ logger
1024
+ end
1025
+
1026
+ def processor
1027
+ @processor ||= 'DataProcessor initialized'
1028
+ end
1029
+
1030
+ def storage
1031
+ @storage ||= begin
1032
+ processor
1033
+ "StorageService using #{processor}"
1034
+ end
1035
+ end
1036
+
1037
+ def logger
1038
+ @logger ||= 'Logger initialized'
1039
+ end
1040
+
1041
+ def process_data(data)
1042
+ setup_dependencies
1043
+ "Processing #{data} with #{processor}, #{storage}, #{logger}"
1044
+ end
1045
+ end.new
1046
+ end
1047
+
1048
+ def create_lazy_background_job
1049
+ Class.new do
1050
+ extend LazyInit
1051
+
1052
+ lazy_attr_reader :processor do
1053
+ 'DataProcessor initialized'
1054
+ end
1055
+
1056
+ lazy_attr_reader :storage, depends_on: [:processor] do
1057
+ "StorageService using #{processor}"
1058
+ end
1059
+
1060
+ lazy_attr_reader :logger do
1061
+ 'Logger initialized'
1062
+ end
1063
+
1064
+ def setup_dependencies
1065
+ processor
1066
+ storage
1067
+ logger
1068
+ end
1069
+
1070
+ def process_data(data)
1071
+ setup_dependencies
1072
+ "Processing #{data} with #{processor}, #{storage}, #{logger}"
1073
+ end
1074
+ end.new
1075
+ end
1076
+
679
1077
  # ========================================
680
- # RESULTS & SUMMARY
1078
+ # RESULTS & SUMMARY (Enhanced)
681
1079
  # ========================================
682
1080
 
683
1081
  def print_summary
684
- puts "\n" + '=' * 80
685
- puts 'BENCHMARK SUMMARY'
686
- puts '=' * 80
1082
+ puts "\n" + '=' * 30
1083
+ puts 'BENCHMARK SUMMARY (Fixed Methodology)'
1084
+ puts '=' * 30
687
1085
 
688
1086
  print_detailed_results
689
1087
  print_performance_analysis
1088
+ print_thread_safety_analysis
690
1089
  print_recommendations
691
1090
  end
692
1091
 
693
1092
  def print_detailed_results
694
1093
  puts "\nDetailed Results:"
695
- puts '-' * 50
1094
+ puts '-' * 30
696
1095
 
697
1096
  @results.each do |category, tests|
698
1097
  puts "\n#{category}:"
699
1098
 
700
1099
  tests.each do |test_name, data|
701
- overhead_str = if data[:overhead_percent] < -50
1100
+ overhead_str = if data[:overhead_percent] < -20
702
1101
  "#{(-data[:overhead_percent]).round(1)}% faster"
703
- elsif data[:overhead_percent] > 0
704
- "#{data[:ratio]}x slower"
705
- else
1102
+ elsif data[:ratio] < 1.2
706
1103
  'similar performance'
1104
+ else
1105
+ "#{data[:ratio]}x slower"
707
1106
  end
708
1107
 
709
1108
  puts " #{test_name}:"
@@ -715,9 +1114,9 @@ class LazyInitBenchmark
715
1114
  end
716
1115
 
717
1116
  def print_performance_analysis
718
- puts "\n" + '=' * 50
1117
+ puts "\n" + '=' * 30
719
1118
  puts 'PERFORMANCE ANALYSIS'
720
- puts '=' * 50
1119
+ puts '=' * 30
721
1120
 
722
1121
  all_ratios = @results.values.flat_map(&:values).map { |data| data[:ratio] }
723
1122
  avg_ratio = (all_ratios.sum / all_ratios.size).round(2)
@@ -725,48 +1124,108 @@ class LazyInitBenchmark
725
1124
  max_ratio = all_ratios.max.round(2)
726
1125
 
727
1126
  puts "\nOverall Performance Impact:"
728
- puts " Average slowdown: #{avg_ratio}x"
1127
+ puts " Average overhead: #{avg_ratio}x"
729
1128
  puts " Best case: #{min_ratio}x slower"
730
1129
  puts " Worst case: #{max_ratio}x slower"
731
1130
 
732
- # Analyze patterns
1131
+ # Analyze hot path vs cold start
1132
+ basic = @results['Basic Patterns']
1133
+ if basic
1134
+ hot_path = basic['Hot Path (after initialization)']
1135
+ cold_start = basic['Cold Start (new instances)']
1136
+
1137
+ if hot_path && cold_start
1138
+ puts "\nHot Path vs Cold Start:"
1139
+ puts " Hot path overhead: #{hot_path[:ratio]}x"
1140
+ puts " Cold start overhead: #{cold_start[:ratio]}x"
1141
+
1142
+ if hot_path[:ratio] < 1.5
1143
+ puts ' ✓ Excellent hot path performance!'
1144
+ elsif cold_start[:ratio] > hot_path[:ratio] * 3
1145
+ puts ' ⚠ Cold start overhead is significant'
1146
+ end
1147
+ end
1148
+ end
1149
+
1150
+ # Analyze computational complexity impact
733
1151
  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]
1152
+ return unless computational
1153
+
1154
+ light_ratio = computational['Lightweight (sum 1..10)']&.[](:ratio)
1155
+ heavy_ratio = computational['Heavy (filter+sqrt 1..10000)']&.[](:ratio)
1156
+
1157
+ return unless light_ratio && heavy_ratio
1158
+
1159
+ puts "\nComputational Complexity Impact:"
1160
+ puts " Light computation overhead: #{light_ratio}x"
1161
+ puts " Heavy computation overhead: #{heavy_ratio}x"
1162
+
1163
+ if (light_ratio - heavy_ratio).abs < 0.5
1164
+ puts ' • Overhead is consistent across complexity levels'
1165
+ elsif light_ratio > heavy_ratio
1166
+ puts ' • Better suited for expensive operations'
1167
+ end
1168
+ end
1169
+
1170
+ def print_thread_safety_analysis
1171
+ thread_safety = @results['Thread Safety']
1172
+ return unless thread_safety
1173
+
1174
+ puts "\n" + '=' * 50
1175
+ puts 'THREAD SAFETY ANALYSIS'
1176
+ puts '=' * 50
1177
+
1178
+ concurrent = thread_safety['Concurrent Access (10 threads)']
1179
+ high_contention = thread_safety['High Contention (50 threads)']
1180
+ reset_safety = thread_safety['Concurrent Reset Safety']
737
1181
 
738
- if light_ratio > heavy_ratio * 1.5
739
- puts "\n• LazyInit overhead decreases with computation complexity"
740
- puts '• Better suited for expensive operations'
1182
+ if concurrent
1183
+ puts "\nConcurrent Access Performance:"
1184
+ puts " 10 threads: #{concurrent[:ratio]}x slower"
1185
+ puts " 50 threads: #{high_contention[:ratio]}x slower" if high_contention
1186
+
1187
+ if concurrent[:ratio] < 2.0
1188
+ puts ' ✓ Good concurrent performance!'
1189
+ elsif concurrent[:ratio] < 5.0
1190
+ puts ' ⚠ Moderate concurrent overhead'
741
1191
  else
742
- puts "\n• LazyInit overhead is consistent across complexity levels"
1192
+ puts ' ❌ High concurrent overhead'
743
1193
  end
744
1194
  end
745
1195
 
746
- conditional = @results['Conditional Loading']
747
- return unless conditional
748
-
749
- true_ratio = conditional['Condition True'][:ratio]
750
- false_ratio = conditional['Condition False'][:ratio]
1196
+ return unless reset_safety
751
1197
 
752
- return unless false_ratio < true_ratio * 0.7
1198
+ puts "\nReset Safety:"
1199
+ puts " Concurrent reset overhead: #{reset_safety[:ratio]}x"
753
1200
 
754
- puts '• Conditional loading is efficient when conditions are false'
1201
+ if reset_safety[:ratio] < 10.0
1202
+ puts ' ✓ Thread-safe resets are reasonably efficient'
1203
+ else
1204
+ puts ' ⚠ Thread-safe resets have significant overhead'
1205
+ end
755
1206
  end
756
1207
 
757
1208
  def print_recommendations
758
- puts "\n" + '=' * 50
1209
+ puts "\n" + '=' * 20
759
1210
  puts 'RECOMMENDATIONS'
760
- puts '=' * 50
1211
+ puts '=' * 20
761
1212
 
762
1213
  all_ratios = @results.values.flat_map(&:values).map { |data| data[:ratio] }
763
1214
  avg_ratio = all_ratios.sum / all_ratios.size
764
1215
 
1216
+ basic = @results['Basic Patterns']
1217
+ hot_path_ratio = basic&.[]('Hot Path (after initialization)')&.[](:ratio)
1218
+
765
1219
  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'
1220
+
1221
+ if hot_path_ratio && hot_path_ratio < 1.5
1222
+ puts ' Hot path performance is excellent - safe for frequent access'
1223
+ puts ' Thread safety benefits outweigh minimal overhead'
1224
+ puts '✓ Recommended for most lazy initialization scenarios'
1225
+ elsif avg_ratio > 10
1226
+ puts '⚠ Only for very expensive initialization (>10ms)'
1227
+ puts '⚠ When thread safety is absolutely critical'
1228
+ puts '⚠ Complex dependency chains only'
770
1229
  elsif avg_ratio > 5
771
1230
  puts '• Expensive initialization (>1ms)'
772
1231
  puts '• Multi-threaded applications'
@@ -777,9 +1236,21 @@ class LazyInitBenchmark
777
1236
  puts '• Clean dependency management needed'
778
1237
  end
779
1238
 
780
- puts "\n" + '=' * 80
781
- puts 'BENCHMARK COMPLETED'
782
- puts '=' * 80
1239
+ puts "\nKey Findings:"
1240
+ puts "• Hot path overhead is minimal (#{hot_path_ratio}x)" if hot_path_ratio && hot_path_ratio < 2.0
1241
+
1242
+ thread_safety = @results['Thread Safety']
1243
+ if thread_safety
1244
+ concurrent_ratio = thread_safety['Concurrent Access (10 threads)']&.[](:ratio)
1245
+ puts '• Good performance under concurrent access' if concurrent_ratio && concurrent_ratio < 3.0
1246
+ end
1247
+
1248
+ puts '• Trade-off: initialization cost vs runtime safety'
1249
+ puts '• Consider cold start impact for short-lived processes'
1250
+
1251
+ puts "\n" + '=' * 50
1252
+ puts 'BENCHMARK COMPLETED (Fixed Methodology)'
1253
+ puts '=' * 50
783
1254
  end
784
1255
  end
785
1256
 
@@ -790,7 +1261,7 @@ if __FILE__ == $0
790
1261
  benchmark.run_all
791
1262
  rescue StandardError => e
792
1263
  puts "Benchmark failed: #{e.message}"
793
- puts e.backtrace.first(5)
1264
+ puts e.backtrace.first(10)
794
1265
  exit 1
795
1266
  end
796
1267
  end