hyll 0.1.1 → 1.0.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,539 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hyll"
4
+ require "redis"
5
+ require "benchmark"
6
+ require "benchmark/ips"
7
+ require "memory_profiler"
8
+ require "optparse"
9
+ require "json"
10
+
11
+ class HyllRedisComparison
12
+ def initialize(precision: 10, data_size: 10_000, overlapping: 1_000, warm_up: true, benchmark_type: :all)
13
+ @precision = precision
14
+ @data_size = data_size
15
+ @overlapping = overlapping
16
+ @warm_up = warm_up
17
+ @benchmark_type = benchmark_type
18
+ @redis = Redis.new
19
+ @results = {}
20
+
21
+ puts "Initializing benchmark with:"
22
+ puts " - Precision: #{@precision}"
23
+ puts " - Data size: #{@data_size} elements"
24
+ puts " - Overlapping elements for merge tests: #{@overlapping}"
25
+ puts " - Warm-up: #{@warm_up ? "Enabled" : "Disabled"}"
26
+ puts "\n"
27
+
28
+ # Clean up any existing Redis keys
29
+ @redis.del("hll1", "hll2", "hll_merged", "redis_hll1", "redis_hll2", "redis_merged", "accuracy_test")
30
+
31
+ # Pre-generazione di dati per i test
32
+ @elements = (0...@data_size).map { |i| "element-#{i}" }.freeze
33
+ @set1_elements = (0...@data_size).map { |i| "set1-#{i}" }.freeze
34
+ @set2_elements = (0...@data_size).map { |i| "set2-#{i + @data_size - @overlapping}" }.freeze
35
+
36
+ # Pre-caricamento Redis
37
+ if %i[all cardinality memory].include?(benchmark_type)
38
+ @redis.pipelined do |pipeline|
39
+ @elements.each { |e| pipeline.pfadd("hll1", e) }
40
+ end
41
+ end
42
+
43
+ if %i[all merge].include?(benchmark_type)
44
+ @redis.pipelined do |pipeline|
45
+ @set1_elements.each { |e| pipeline.pfadd("redis_hll1", e) }
46
+ @set2_elements.each { |e| pipeline.pfadd("redis_hll2", e) }
47
+ end
48
+ end
49
+
50
+ if %i[all cardinality].include?(benchmark_type)
51
+ @pre_hll_standard = Hyll::HyperLogLog.new(@precision)
52
+ @pre_hll_enhanced = Hyll::EnhancedHyperLogLog.new(@precision)
53
+
54
+ @elements.each do |e|
55
+ @pre_hll_standard.add(e)
56
+ @pre_hll_enhanced.add(e)
57
+ end
58
+ end
59
+
60
+ if %i[all merge].include?(benchmark_type)
61
+ @pre_merge_hll1 = Hyll::HyperLogLog.new(@precision)
62
+ @pre_merge_hll2 = Hyll::HyperLogLog.new(@precision)
63
+ @pre_merge_enhanced1 = Hyll::EnhancedHyperLogLog.new(@precision)
64
+ @pre_merge_enhanced2 = Hyll::EnhancedHyperLogLog.new(@precision)
65
+
66
+ @set1_elements.each do |e|
67
+ @pre_merge_hll1.add(e)
68
+ @pre_merge_enhanced1.add(e)
69
+ end
70
+
71
+ @set2_elements.each do |e|
72
+ @pre_merge_hll2.add(e)
73
+ @pre_merge_enhanced2.add(e)
74
+ end
75
+
76
+ @pre_merge_hll1_serialized = Marshal.dump(@pre_merge_hll1)
77
+ @pre_merge_enhanced1_serialized = Marshal.dump(@pre_merge_enhanced1)
78
+ end
79
+
80
+ warm_up_benchmarks if @warm_up
81
+ end
82
+
83
+ def run_benchmarks
84
+ case @benchmark_type
85
+ when :insertion
86
+ benchmark_insertion
87
+ when :cardinality
88
+ benchmark_cardinality
89
+ when :merge
90
+ benchmark_merge
91
+ when :memory
92
+ benchmark_memory_usage
93
+ when :accuracy
94
+ benchmark_accuracy
95
+ else
96
+ benchmark_insertion
97
+ benchmark_cardinality
98
+ benchmark_merge
99
+ benchmark_memory_usage
100
+ benchmark_accuracy
101
+ end
102
+
103
+ print_summary
104
+ end
105
+
106
+ def warm_up_benchmarks
107
+ puts "Performing warm-up..."
108
+ # Warm-up JIT compiler
109
+ warm_up_count = [@data_size / 10, 1000].min
110
+
111
+ # Warm-up insertion
112
+ hll_warmup = Hyll::HyperLogLog.new(@precision)
113
+ enhanced_warmup = Hyll::EnhancedHyperLogLog.new(@precision)
114
+
115
+ warm_up_count.times do |i|
116
+ hll_warmup.add("warmup-#{i}")
117
+ enhanced_warmup.add("warmup-#{i}")
118
+ @redis.pfadd("warmup_hll", "warmup-#{i}")
119
+ end
120
+
121
+ # Warm-up cardinality
122
+ 10.times do
123
+ hll_warmup.cardinality
124
+ hll_warmup.mle_cardinality
125
+ enhanced_warmup.cardinality
126
+ enhanced_warmup.cardinality(use_streaming: true)
127
+ @redis.pfcount("warmup_hll")
128
+ end
129
+
130
+ # Warm-up merge
131
+ warm_up_hll1 = Hyll::HyperLogLog.new(@precision)
132
+ warm_up_hll2 = Hyll::HyperLogLog.new(@precision)
133
+ 5.times do
134
+ warm_up_copy = Marshal.load(Marshal.dump(warm_up_hll1))
135
+ warm_up_copy.merge(warm_up_hll2)
136
+ end
137
+
138
+ @redis.del("warmup_hll")
139
+ puts "Warm-up complete.\n\n"
140
+ end
141
+
142
+ def benchmark_insertion
143
+ puts "=== Insertion Performance ==="
144
+ GC.start # Sincronizzazione GC per risultati più consistenti
145
+
146
+ results = Benchmark.ips do |x|
147
+ x.config(time: 2, warmup: 1)
148
+
149
+ # Hyll standard insertion
150
+ x.report("Hyll Standard") do
151
+ hll = Hyll::HyperLogLog.new(@precision)
152
+ @elements.each { |e| hll.add(e) }
153
+ end
154
+
155
+ # Hyll enhanced insertion
156
+ x.report("Hyll Enhanced") do
157
+ hll = Hyll::EnhancedHyperLogLog.new(@precision)
158
+ @elements.each { |e| hll.add(e) }
159
+ end
160
+
161
+ # Hyll batch insertion
162
+ x.report("Hyll Batch") do
163
+ Hyll::HyperLogLog.new(@precision).add_all(@elements)
164
+ end
165
+
166
+ # Redis insertion
167
+ x.report("Redis PFADD") do
168
+ @redis.del("bench_hll")
169
+ @elements.each { |e| @redis.pfadd("bench_hll", e) }
170
+ end
171
+
172
+ # Redis pipelined insertion
173
+ x.report("Redis Pipelined") do
174
+ @redis.del("bench_hll")
175
+ @redis.pipelined do |pipeline|
176
+ @elements.each { |e| pipeline.pfadd("bench_hll", e) }
177
+ end
178
+ end
179
+
180
+ x.compare!
181
+ end
182
+
183
+ @results[:insertion] = results
184
+ puts "\n"
185
+ end
186
+
187
+ def benchmark_cardinality
188
+ puts "=== Cardinality Estimation Performance ==="
189
+ GC.start
190
+
191
+ results = Benchmark.ips do |x|
192
+ x.config(time: 2, warmup: 1)
193
+
194
+ # Hyll standard cardinality
195
+ x.report("Hyll Standard") do
196
+ @pre_hll_standard.cardinality
197
+ end
198
+
199
+ # Hyll standard MLE
200
+ x.report("Hyll MLE") do
201
+ @pre_hll_standard.mle_cardinality
202
+ end
203
+
204
+ # Hyll enhanced cardinality
205
+ x.report("Hyll Enhanced") do
206
+ @pre_hll_enhanced.cardinality
207
+ end
208
+
209
+ # Hyll enhanced streaming
210
+ x.report("Hyll Enhanced Stream") do
211
+ @pre_hll_enhanced.cardinality(use_streaming: true)
212
+ end
213
+
214
+ # Redis cardinality
215
+ x.report("Redis PFCOUNT") do
216
+ @redis.pfcount("hll1")
217
+ end
218
+
219
+ x.compare!
220
+ end
221
+
222
+ @results[:cardinality] = results
223
+ puts "\n"
224
+ end
225
+
226
+ def benchmark_merge
227
+ puts "=== Merge Performance ==="
228
+ GC.start
229
+
230
+ results = Benchmark.ips do |x|
231
+ x.config(time: 2, warmup: 1)
232
+
233
+ # Hyll standard merge
234
+ x.report("Hyll Standard") do
235
+ hll_copy = Marshal.load(@pre_merge_hll1_serialized)
236
+ hll_copy.merge(@pre_merge_hll2)
237
+ end
238
+
239
+ # Hyll enhanced merge
240
+ x.report("Hyll Enhanced") do
241
+ enhanced_copy = Marshal.load(@pre_merge_enhanced1_serialized)
242
+ enhanced_copy.merge(@pre_merge_enhanced2)
243
+ end
244
+
245
+ # Redis merge
246
+ x.report("Redis PFMERGE") do
247
+ @redis.pfmerge("redis_merged", "redis_hll1", "redis_hll2")
248
+ end
249
+
250
+ x.compare!
251
+ end
252
+
253
+ @results[:merge] = results
254
+ puts "\n"
255
+ end
256
+
257
+ def benchmark_memory_usage
258
+ puts "=== Memory Usage ==="
259
+ GC.start
260
+
261
+ # Memory usage of standard HLL
262
+ hll_standard_memory = report_memory("Hyll Standard") do
263
+ hll = Hyll::HyperLogLog.new(@precision)
264
+ @elements.each { |e| hll.add(e) }
265
+ hll
266
+ end
267
+
268
+ # Memory usage of enhanced HLL
269
+ hll_enhanced_memory = report_memory("Hyll Enhanced") do
270
+ hll = Hyll::EnhancedHyperLogLog.new(@precision)
271
+ @elements.each { |e| hll.add(e) }
272
+ hll
273
+ end
274
+
275
+ # Memory usage of actual elements (for comparison)
276
+ raw_elements_memory = report_memory("Raw Elements Array") do
277
+ @elements.dup
278
+ end
279
+
280
+ # Redis memory usage
281
+ redis_memory = @redis.memory("USAGE", "hll1")
282
+ puts "Redis memory usage for HLL key: #{redis_memory} bytes"
283
+
284
+ # Calcola compression ratio
285
+ puts "\nCompression ratios:"
286
+ puts " Hyll Standard: #{(raw_elements_memory[:allocated] / hll_standard_memory[:retained]).round(2)}x"
287
+ puts " Hyll Enhanced: #{(raw_elements_memory[:allocated] / hll_enhanced_memory[:retained]).round(2)}x"
288
+ puts " Redis: #{(raw_elements_memory[:allocated] * 1024 / redis_memory).round(2)}x"
289
+
290
+ @results[:memory] = {
291
+ hyll_standard: hll_standard_memory,
292
+ hyll_enhanced: hll_enhanced_memory,
293
+ raw_elements: raw_elements_memory,
294
+ redis: redis_memory
295
+ }
296
+
297
+ puts "\n"
298
+ end
299
+
300
+ def benchmark_accuracy
301
+ puts "=== Accuracy Comparison ==="
302
+ GC.start
303
+
304
+ accuracy_elements = (0...@data_size).map { |i| "accuracy-#{i}" }
305
+
306
+ # Hyll standard
307
+ hll_standard = Hyll::HyperLogLog.new(@precision)
308
+ hll_standard.add_all(accuracy_elements)
309
+
310
+ # Hyll enhanced
311
+ hll_enhanced = Hyll::EnhancedHyperLogLog.new(@precision)
312
+ hll_enhanced.add_all(accuracy_elements)
313
+
314
+ hll_standard_high = Hyll::HyperLogLog.new([@precision + 2, 16].min)
315
+ hll_standard_high.add_all(accuracy_elements)
316
+
317
+ # Redis
318
+ @redis.del("accuracy_test")
319
+ @redis.pipelined do |pipeline|
320
+ accuracy_elements.each { |e| pipeline.pfadd("accuracy_test", e) }
321
+ end
322
+
323
+ # Get estimates
324
+ hll_standard_est = hll_standard.cardinality
325
+ hll_standard_mle = hll_standard.mle_cardinality
326
+ hll_standard_high_est = hll_standard_high.cardinality
327
+ hll_enhanced_est = hll_enhanced.cardinality
328
+ hll_enhanced_stream = hll_enhanced.cardinality(use_streaming: true)
329
+ redis_est = @redis.pfcount("accuracy_test")
330
+
331
+ # Calcola errori
332
+ standard_error = calculate_error("Hyll Standard", hll_standard_est, @data_size)
333
+ standard_mle_error = calculate_error("Hyll Standard MLE", hll_standard_mle, @data_size)
334
+ standard_high_error = calculate_error("Hyll Standard (High Precision)", hll_standard_high_est, @data_size)
335
+ enhanced_error = calculate_error("Hyll Enhanced", hll_enhanced_est, @data_size)
336
+ enhanced_stream_error = calculate_error("Hyll Enhanced Stream", hll_enhanced_stream, @data_size)
337
+ redis_error = calculate_error("Redis", redis_est, @data_size)
338
+
339
+ @results[:accuracy] = {
340
+ hyll_standard: standard_error,
341
+ hyll_standard_mle: standard_mle_error,
342
+ hyll_standard_high: standard_high_error,
343
+ hyll_enhanced: enhanced_error,
344
+ hyll_enhanced_stream: enhanced_stream_error,
345
+ redis: redis_error
346
+ }
347
+
348
+ # Grafico dell'errore (ASCII art)
349
+ puts "\nError comparison (lower is better):"
350
+ print_error_bar("Hyll Standard", standard_error[:percent])
351
+ print_error_bar("Hyll MLE", standard_mle_error[:percent])
352
+ print_error_bar("Hyll (High Prec)", standard_high_error[:percent])
353
+ print_error_bar("Hyll Enhanced", enhanced_error[:percent])
354
+ print_error_bar("Hyll Enh Stream", enhanced_stream_error[:percent])
355
+ print_error_bar("Redis", redis_error[:percent])
356
+ end
357
+
358
+ def print_error_bar(label, error_pct)
359
+ display_error = [error_pct, 300].min
360
+ bars = (display_error / 5).to_i
361
+ truncated = display_error < error_pct
362
+
363
+ printf("%-18s |%-60s| %.2f%%%s\n",
364
+ label,
365
+ "#" * bars,
366
+ error_pct,
367
+ truncated ? " (truncated)" : "")
368
+ end
369
+
370
+ def print_summary
371
+ puts "\n=== BENCHMARK SUMMARY ==="
372
+ puts "Precision: #{@precision}, Data size: #{@data_size}"
373
+
374
+ puts "\nACCURACY WINNER: #{get_accuracy_winner}" if @results[:accuracy]
375
+
376
+ if @results[:insertion] && @results[:cardinality] && @results[:merge]
377
+ puts "\nPERFORMANCE WINNERS:"
378
+ puts " Insertion: #{get_insertion_winner}"
379
+ puts " Cardinality: #{get_cardinality_winner}"
380
+ puts " Merge: #{get_merge_winner}"
381
+ end
382
+
383
+ puts "\nMEMORY USAGE WINNER: #{get_memory_winner}" if @results[:memory]
384
+
385
+ puts "\nRECOMMENDATION:"
386
+ puts generate_recommendation
387
+ end
388
+
389
+ def get_accuracy_winner
390
+ errors = @results[:accuracy].transform_values { |v| v[:percent] }
391
+ winner = errors.min_by { |_, v| v }
392
+ "#{winner[0].to_s.split("_").map(&:capitalize).join(" ")} (#{winner[1].round(2)}% error)"
393
+ end
394
+
395
+ def get_insertion_winner
396
+ @results[:insertion].entries.max_by(&:ips).label
397
+ end
398
+
399
+ def get_cardinality_winner
400
+ @results[:cardinality].entries.max_by(&:ips).label
401
+ end
402
+
403
+ def get_merge_winner
404
+ @results[:merge].entries.max_by(&:ips).label
405
+ end
406
+
407
+ def get_memory_winner
408
+ memories = {
409
+ hyll_standard: @results[:memory][:hyll_standard][:retained],
410
+ hyll_enhanced: @results[:memory][:hyll_enhanced][:retained],
411
+ redis: @results[:memory][:redis]
412
+ }
413
+
414
+ winner = memories.min_by { |_, v| v }
415
+ "#{winner[0].to_s.split("_").map(&:capitalize).join(" ")} (#{winner[1] / 1024.0} KB)"
416
+ end
417
+
418
+ def generate_recommendation
419
+ return "Run accuracy benchmark to generate recommendation" unless @results[:accuracy]
420
+
421
+ errors = @results[:accuracy].transform_values { |v| v[:percent] }
422
+
423
+ if errors[:redis] < 5.0
424
+ "Redis offers excellent accuracy and good performance, recommended for most use cases."
425
+ elsif errors[:hyll_standard] < errors[:hyll_enhanced] && errors[:hyll_standard] < 15.0
426
+ "Hyll Standard with precision #{@precision} offers good accuracy and best insertion performance."
427
+ elsif errors[:hyll_enhanced] < 15.0
428
+ "Hyll Enhanced offers better accuracy than Standard and good overall performance."
429
+ else
430
+ "Consider using higher precision (#{[@precision + 2, 16].min}) for better accuracy."
431
+ end
432
+ end
433
+
434
+ def export_results(filename)
435
+ File.write(filename, JSON.pretty_generate(@results))
436
+ puts "Results exported to #{filename}"
437
+ end
438
+
439
+ private
440
+
441
+ def report_memory(label)
442
+ GC.start # Force GC before measurement
443
+ result = nil
444
+ report = MemoryProfiler.report do
445
+ result = yield
446
+ end
447
+
448
+ allocated = report.total_allocated_memsize / 1024.0
449
+ retained = report.total_retained_memsize / 1024.0
450
+
451
+ puts "#{label}:"
452
+ puts " Total allocated: #{allocated.round(2)} KB"
453
+ puts " Total retained: #{retained.round(2)} KB"
454
+
455
+ # Return memory stats
456
+ { allocated: allocated, retained: retained, result: result }
457
+ end
458
+
459
+ def calculate_error(label, estimate, actual)
460
+ error_pct = ((estimate - actual).abs / actual.to_f) * 100
461
+ result = {
462
+ estimate: estimate.round,
463
+ actual: actual,
464
+ difference: (estimate - actual).round,
465
+ percent: error_pct.round(2)
466
+ }
467
+
468
+ puts "#{label}: Estimated #{result[:estimate]} vs Actual #{actual} (Error: #{result[:percent]}%)"
469
+ result
470
+ end
471
+ end
472
+
473
+ # Parse command line options
474
+ options = {
475
+ precision: 10,
476
+ data_size: 10_000,
477
+ overlapping: 1_000,
478
+ warm_up: true,
479
+ benchmark_type: :all,
480
+ output_file: nil
481
+ }
482
+
483
+ OptionParser.new do |opts|
484
+ opts.banner = "Usage: ruby redis_comparison_benchmark.rb [options]"
485
+
486
+ opts.on("-p", "--precision PRECISION", Integer, "HyperLogLog precision (4-16)") do |p|
487
+ options[:precision] = p
488
+ end
489
+
490
+ opts.on("-d", "--data-size SIZE", Integer, "Number of elements to add") do |d|
491
+ options[:data_size] = d
492
+ end
493
+
494
+ opts.on("-o", "--overlapping SIZE", Integer, "Number of overlapping elements for merge tests") do |o|
495
+ options[:overlapping] = o
496
+ end
497
+
498
+ opts.on("--no-warm-up", "Skip warm-up phase") do
499
+ options[:warm_up] = false
500
+ end
501
+
502
+ opts.on("-b", "--benchmark TYPE", %i[all insertion cardinality merge memory accuracy],
503
+ "Run specific benchmark type (all, insertion, cardinality, merge, memory, accuracy)") do |b|
504
+ options[:benchmark_type] = b
505
+ end
506
+
507
+ opts.on("--output FILE", "Export results to JSON file") do |f|
508
+ options[:output_file] = f
509
+ end
510
+
511
+ opts.on("-h", "--help", "Show this help message") do
512
+ puts opts
513
+ exit
514
+ end
515
+ end.parse!
516
+
517
+ # Run benchmarks
518
+ puts "Starting HyperLogLog Comparison Benchmark: Hyll vs Redis"
519
+ puts "-----------------------------------------------------"
520
+
521
+ begin
522
+ comparison = HyllRedisComparison.new(
523
+ precision: options[:precision],
524
+ data_size: options[:data_size],
525
+ overlapping: options[:overlapping],
526
+ warm_up: options[:warm_up],
527
+ benchmark_type: options[:benchmark_type]
528
+ )
529
+
530
+ comparison.run_benchmarks
531
+
532
+ comparison.export_results(options[:output_file]) if options[:output_file]
533
+ rescue Redis::CannotConnectError
534
+ puts "ERROR: Cannot connect to Redis server."
535
+ puts "Please ensure Redis is running locally on the default port (6379)."
536
+ puts "You can start Redis with: redis-server"
537
+ end
538
+
539
+ puts "Benchmark complete!"
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Hyll v1.0.0 - Blazing Fast Edition Benchmark
4
+ # Run with: ruby examples/v1_benchmark.rb
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
7
+ require "hyll"
8
+ require "benchmark"
9
+
10
+ puts "=" * 60
11
+ puts "HYLL v1.0.0 - BLAZING FAST EDITION BENCHMARK"
12
+ puts "=" * 60
13
+ puts
14
+
15
+ # Test 1: Element Addition
16
+ puts "1. Element Addition Performance"
17
+ puts "-" * 40
18
+
19
+ hll_standard = Hyll.new(type: :standard)
20
+ hll_enhanced = Hyll.new(type: :enhanced)
21
+
22
+ # Use integers for cleaner testing
23
+ elements = (1..100_000).to_a
24
+
25
+ time_standard = Benchmark.measure { elements.each { |e| hll_standard.add(e) } }
26
+ puts "Standard HLL (100k elements): #{time_standard.real.round(4)}s"
27
+
28
+ time_enhanced = Benchmark.measure { elements.each { |e| hll_enhanced.add(e) } }
29
+ puts "Enhanced HLL (100k elements): #{time_enhanced.real.round(4)}s"
30
+ puts
31
+
32
+ # Test 2: Batch Addition
33
+ puts "2. Batch Addition Performance"
34
+ puts "-" * 40
35
+
36
+ hll_batch = Hyll.new(type: :standard)
37
+ time_batch = Benchmark.measure { hll_batch.add_all(elements) }
38
+ puts "Standard HLL batch (100k elements): #{time_batch.real.round(4)}s"
39
+ puts "Speedup vs individual: #{(time_standard.real / time_batch.real).round(2)}x"
40
+ puts
41
+
42
+ # Test 3: Cardinality Calculation
43
+ puts "3. Cardinality Calculation Performance"
44
+ puts "-" * 40
45
+
46
+ hll = Hyll.new(type: :standard)
47
+ hll.add_all(elements)
48
+
49
+ time_card = Benchmark.measure { 1000.times { hll.cardinality } }
50
+ puts "Standard HLL (1000 calls): #{time_card.real.round(4)}s"
51
+ puts "Per call: #{((time_card.real / 1000) * 1_000_000).round(2)} microseconds"
52
+ puts
53
+
54
+ # Test 4: Memory Efficiency
55
+ puts "4. Memory Efficiency"
56
+ puts "-" * 40
57
+
58
+ # For integers, estimate as 8 bytes each (Fixnum size)
59
+ array_size = elements.size * 8
60
+ hll_size = hll.serialize.bytesize
61
+
62
+ puts "Raw data size: #{array_size} bytes"
63
+ puts "HLL serialized size: #{hll_size} bytes"
64
+ puts "Compression ratio: #{(array_size.to_f / hll_size).round(2)}x"
65
+ puts
66
+
67
+ # Test 5: Accuracy
68
+ puts "5. Accuracy Check"
69
+ puts "-" * 40
70
+ puts "Actual count: 100,000"
71
+ puts "Estimated count: #{hll.cardinality.round(0)}"
72
+ puts "Error: #{((hll.cardinality - 100_000).abs / 100_000.0 * 100).round(2)}%"
73
+ puts
74
+
75
+ # Test 6: Hash Performance
76
+ puts "6. Hash Function Performance"
77
+ puts "-" * 40
78
+
79
+ class HashTester
80
+ include Hyll::Utils::Hash
81
+ end
82
+
83
+ tester = HashTester.new
84
+ time_hash = Benchmark.measure do
85
+ 100_000.times { |i| tester.murmurhash3(i.to_s) }
86
+ end
87
+ puts "MurmurHash3 (100k hashes): #{time_hash.real.round(4)}s"
88
+ puts "Per hash: #{((time_hash.real / 100_000) * 1_000_000).round(2)} microseconds"
89
+ puts
90
+
91
+ puts "=" * 60
92
+ puts "BENCHMARK COMPLETE"
93
+ puts "=" * 60