t-ruby 0.0.1

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,592 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+ require "json"
5
+ require "fileutils"
6
+
7
+ module TRuby
8
+ # Benchmark suite for T-Ruby performance measurement
9
+ class BenchmarkSuite
10
+ attr_reader :results, :config
11
+
12
+ BENCHMARK_CATEGORIES = %i[
13
+ parsing
14
+ type_checking
15
+ compilation
16
+ incremental
17
+ parallel
18
+ memory
19
+ ].freeze
20
+
21
+ def initialize(config = nil)
22
+ @config = config || Config.new
23
+ @results = {}
24
+ @compiler = nil
25
+ @type_checker = nil
26
+ end
27
+
28
+ # Run all benchmarks
29
+ def run_all(iterations: 5, warmup: 2)
30
+ puts "T-Ruby Benchmark Suite"
31
+ puts "=" * 60
32
+ puts "Iterations: #{iterations}, Warmup: #{warmup}"
33
+ puts
34
+
35
+ BENCHMARK_CATEGORIES.each do |category|
36
+ run_category(category, iterations: iterations, warmup: warmup)
37
+ end
38
+
39
+ print_summary
40
+ @results
41
+ end
42
+
43
+ # Run specific category
44
+ def run_category(category, iterations: 5, warmup: 2)
45
+ puts "Running #{category} benchmarks..."
46
+ puts "-" * 40
47
+
48
+ @results[category] = case category
49
+ when :parsing then benchmark_parsing(iterations, warmup)
50
+ when :type_checking then benchmark_type_checking(iterations, warmup)
51
+ when :compilation then benchmark_compilation(iterations, warmup)
52
+ when :incremental then benchmark_incremental(iterations, warmup)
53
+ when :parallel then benchmark_parallel(iterations, warmup)
54
+ when :memory then benchmark_memory
55
+ end
56
+
57
+ puts
58
+ end
59
+
60
+ # Export results to JSON
61
+ def export_json(path = "benchmark_results.json")
62
+ File.write(path, JSON.pretty_generate({
63
+ timestamp: Time.now.iso8601,
64
+ ruby_version: RUBY_VERSION,
65
+ platform: RUBY_PLATFORM,
66
+ results: @results
67
+ }))
68
+ end
69
+
70
+ # Export results to Markdown
71
+ def export_markdown(path = "benchmark_results.md")
72
+ md = []
73
+ md << "# T-Ruby Benchmark Results"
74
+ md << ""
75
+ md << "**Generated:** #{Time.now}"
76
+ md << "**Ruby Version:** #{RUBY_VERSION}"
77
+ md << "**Platform:** #{RUBY_PLATFORM}"
78
+ md << ""
79
+
80
+ @results.each do |category, benchmarks|
81
+ md << "## #{category.to_s.capitalize}"
82
+ md << ""
83
+ md << "| Benchmark | Time (ms) | Memory (KB) | Iterations/sec |"
84
+ md << "|-----------|-----------|-------------|----------------|"
85
+
86
+ benchmarks.each do |name, data|
87
+ time_ms = (data[:avg_time] * 1000).round(2)
88
+ memory_kb = (data[:memory] || 0).round(2)
89
+ ips = data[:avg_time] > 0 ? (1.0 / data[:avg_time]).round(2) : 0
90
+ md << "| #{name} | #{time_ms} | #{memory_kb} | #{ips} |"
91
+ end
92
+ md << ""
93
+ end
94
+
95
+ File.write(path, md.join("\n"))
96
+ end
97
+
98
+ # Compare with previous results
99
+ def compare(previous_path)
100
+ return nil unless File.exist?(previous_path)
101
+
102
+ previous = JSON.parse(File.read(previous_path), symbolize_names: true)
103
+ comparison = {}
104
+
105
+ @results.each do |category, benchmarks|
106
+ prev_cat = previous[:results][category]
107
+ next unless prev_cat
108
+
109
+ comparison[category] = {}
110
+ benchmarks.each do |name, data|
111
+ prev_data = prev_cat[name]
112
+ next unless prev_data
113
+
114
+ diff = ((data[:avg_time] - prev_data[:avg_time]) / prev_data[:avg_time] * 100).round(2)
115
+ comparison[category][name] = {
116
+ current: data[:avg_time],
117
+ previous: prev_data[:avg_time],
118
+ diff_percent: diff,
119
+ improved: diff < 0
120
+ }
121
+ end
122
+ end
123
+
124
+ comparison
125
+ end
126
+
127
+ private
128
+
129
+ def compiler
130
+ @compiler ||= Compiler.new(@config)
131
+ end
132
+
133
+ def type_checker
134
+ @type_checker ||= TypeChecker.new
135
+ end
136
+
137
+ # Parsing benchmarks
138
+ def benchmark_parsing(iterations, warmup)
139
+ test_files = generate_test_files(:parsing)
140
+ results = {}
141
+
142
+ test_files.each do |name, content|
143
+ times = []
144
+
145
+ # Warmup
146
+ warmup.times { Parser.new(content).parse }
147
+
148
+ # Actual benchmark
149
+ iterations.times do
150
+ time = Benchmark.realtime { Parser.new(content).parse }
151
+ times << time
152
+ end
153
+
154
+ results[name] = calculate_stats(times)
155
+ print_result(name, results[name])
156
+ end
157
+
158
+ results
159
+ end
160
+
161
+ # Type checking benchmarks
162
+ def benchmark_type_checking(iterations, warmup)
163
+ test_cases = generate_test_files(:type_checking)
164
+ results = {}
165
+
166
+ test_cases.each do |name, content|
167
+ times = []
168
+ ast = Parser.new(content).parse
169
+
170
+ # Warmup
171
+ warmup.times { TypeChecker.new.check(ast) }
172
+
173
+ # Actual benchmark
174
+ iterations.times do
175
+ checker = TypeChecker.new
176
+ time = Benchmark.realtime { checker.check(ast) }
177
+ times << time
178
+ end
179
+
180
+ results[name] = calculate_stats(times)
181
+ print_result(name, results[name])
182
+ end
183
+
184
+ results
185
+ end
186
+
187
+ # Compilation benchmarks
188
+ def benchmark_compilation(iterations, warmup)
189
+ test_cases = generate_test_files(:compilation)
190
+ results = {}
191
+
192
+ Dir.mktmpdir("trb_bench") do |tmpdir|
193
+ test_cases.each do |name, content|
194
+ input_path = File.join(tmpdir, "#{name}.trb")
195
+ File.write(input_path, content)
196
+
197
+ times = []
198
+
199
+ # Warmup
200
+ warmup.times { compiler.compile(input_path) }
201
+
202
+ # Actual benchmark
203
+ iterations.times do
204
+ time = Benchmark.realtime { compiler.compile(input_path) }
205
+ times << time
206
+ end
207
+
208
+ results[name] = calculate_stats(times)
209
+ print_result(name, results[name])
210
+ end
211
+ end
212
+
213
+ results
214
+ end
215
+
216
+ # Incremental compilation benchmarks
217
+ def benchmark_incremental(iterations, warmup)
218
+ results = {}
219
+
220
+ Dir.mktmpdir("trb_incr_bench") do |tmpdir|
221
+ # Create test files
222
+ files = 10.times.map do |i|
223
+ path = File.join(tmpdir, "file_#{i}.trb")
224
+ File.write(path, generate_test_content(i))
225
+ path
226
+ end
227
+
228
+ # Full compilation
229
+ full_times = []
230
+ warmup.times { IncrementalCompiler.new(compiler).compile_all(files) }
231
+ iterations.times do
232
+ ic = IncrementalCompiler.new(compiler)
233
+ time = Benchmark.realtime { ic.compile_all(files) }
234
+ full_times << time
235
+ end
236
+ results[:full_compile] = calculate_stats(full_times)
237
+ print_result(:full_compile, results[:full_compile])
238
+
239
+ # Incremental (single file change)
240
+ incr_times = []
241
+ ic = IncrementalCompiler.new(compiler)
242
+ ic.compile_all(files)
243
+
244
+ warmup.times do
245
+ File.write(files[0], generate_test_content(0, modified: true))
246
+ ic.compile_incremental([files[0]])
247
+ end
248
+
249
+ iterations.times do
250
+ File.write(files[0], generate_test_content(0, modified: true))
251
+ time = Benchmark.realtime { ic.compile_incremental([files[0]]) }
252
+ incr_times << time
253
+ end
254
+ results[:incremental_single] = calculate_stats(incr_times)
255
+ print_result(:incremental_single, results[:incremental_single])
256
+
257
+ # Calculate speedup
258
+ if results[:full_compile][:avg_time] > 0
259
+ speedup = results[:full_compile][:avg_time] / results[:incremental_single][:avg_time]
260
+ puts " Incremental speedup: #{speedup.round(2)}x"
261
+ end
262
+ end
263
+
264
+ results
265
+ end
266
+
267
+ # Parallel compilation benchmarks
268
+ def benchmark_parallel(iterations, warmup)
269
+ results = {}
270
+
271
+ Dir.mktmpdir("trb_parallel_bench") do |tmpdir|
272
+ # Create 20 test files
273
+ files = 20.times.map do |i|
274
+ path = File.join(tmpdir, "parallel_#{i}.trb")
275
+ File.write(path, generate_test_content(i))
276
+ path
277
+ end
278
+
279
+ # Sequential
280
+ seq_times = []
281
+ warmup.times do
282
+ files.each { |f| compiler.compile(f) }
283
+ end
284
+ iterations.times do
285
+ time = Benchmark.realtime do
286
+ files.each { |f| compiler.compile(f) }
287
+ end
288
+ seq_times << time
289
+ end
290
+ results[:sequential] = calculate_stats(seq_times)
291
+ print_result(:sequential, results[:sequential])
292
+
293
+ # Parallel (2 workers)
294
+ par2_times = []
295
+ processor = ParallelProcessor.new(workers: 2)
296
+ warmup.times { processor.process_files(files) { |f| compiler.compile(f) } }
297
+ iterations.times do
298
+ time = Benchmark.realtime do
299
+ processor.process_files(files) { |f| compiler.compile(f) }
300
+ end
301
+ par2_times << time
302
+ end
303
+ results[:parallel_2] = calculate_stats(par2_times)
304
+ print_result(:parallel_2, results[:parallel_2])
305
+
306
+ # Parallel (4 workers)
307
+ par4_times = []
308
+ processor4 = ParallelProcessor.new(workers: 4)
309
+ warmup.times { processor4.process_files(files) { |f| compiler.compile(f) } }
310
+ iterations.times do
311
+ time = Benchmark.realtime do
312
+ processor4.process_files(files) { |f| compiler.compile(f) }
313
+ end
314
+ par4_times << time
315
+ end
316
+ results[:parallel_4] = calculate_stats(par4_times)
317
+ print_result(:parallel_4, results[:parallel_4])
318
+
319
+ # Print speedups
320
+ if results[:sequential][:avg_time] > 0
321
+ puts " Parallel(2) speedup: #{(results[:sequential][:avg_time] / results[:parallel_2][:avg_time]).round(2)}x"
322
+ puts " Parallel(4) speedup: #{(results[:sequential][:avg_time] / results[:parallel_4][:avg_time]).round(2)}x"
323
+ end
324
+ end
325
+
326
+ results
327
+ end
328
+
329
+ # Memory benchmarks
330
+ def benchmark_memory
331
+ results = {}
332
+
333
+ # Baseline memory
334
+ GC.start
335
+ baseline = get_memory_usage
336
+
337
+ # Parser memory
338
+ content = generate_test_content(0)
339
+ GC.start
340
+ before = get_memory_usage
341
+ 10.times { Parser.new(content).parse }
342
+ GC.start
343
+ after = get_memory_usage
344
+ results[:parsing] = { memory: (after - before) / 10.0, avg_time: 0, min_time: 0, max_time: 0, std_dev: 0 }
345
+ print_result(:parsing, results[:parsing], unit: "KB")
346
+
347
+ # Type checker memory
348
+ ast = Parser.new(content).parse
349
+ GC.start
350
+ before = get_memory_usage
351
+ 10.times { TypeChecker.new.check(ast) }
352
+ GC.start
353
+ after = get_memory_usage
354
+ results[:type_checking] = { memory: (after - before) / 10.0, avg_time: 0, min_time: 0, max_time: 0, std_dev: 0 }
355
+ print_result(:type_checking, results[:type_checking], unit: "KB")
356
+
357
+ # Cache memory
358
+ GC.start
359
+ before = get_memory_usage
360
+ cache = CompilationCache.new
361
+ 1000.times { |i| cache.set("key_#{i}", "value_#{i}") }
362
+ GC.start
363
+ after = get_memory_usage
364
+ results[:cache_1000] = { memory: after - before, avg_time: 0, min_time: 0, max_time: 0, std_dev: 0 }
365
+ print_result(:cache_1000, results[:cache_1000], unit: "KB")
366
+
367
+ results
368
+ end
369
+
370
+ def generate_test_files(category)
371
+ case category
372
+ when :parsing
373
+ {
374
+ small_file: generate_test_content(0, lines: 10),
375
+ medium_file: generate_test_content(0, lines: 100),
376
+ large_file: generate_test_content(0, lines: 500),
377
+ complex_types: generate_complex_types_content
378
+ }
379
+ when :type_checking
380
+ {
381
+ simple_types: generate_simple_types_content,
382
+ generic_types: generate_generic_types_content,
383
+ union_types: generate_union_types_content,
384
+ interface_types: generate_interface_types_content
385
+ }
386
+ when :compilation
387
+ {
388
+ minimal: "def hello: void; end",
389
+ with_types: generate_test_content(0, lines: 50),
390
+ with_interfaces: generate_interface_types_content
391
+ }
392
+ else
393
+ {}
394
+ end
395
+ end
396
+
397
+ def generate_test_content(seed, lines: 50, modified: false)
398
+ content = []
399
+ content << "# Test file #{seed}#{modified ? ' (modified)' : ''}"
400
+ content << ""
401
+ content << "type CustomType#{seed} = String | Integer | nil"
402
+ content << ""
403
+ content << "interface TestInterface#{seed}"
404
+ content << " value: CustomType#{seed}"
405
+ content << " process: Boolean"
406
+ content << "end"
407
+ content << ""
408
+
409
+ (lines - 10).times do |i|
410
+ content << "def method_#{seed}_#{i}(arg: String): Integer"
411
+ content << " arg.length"
412
+ content << "end"
413
+ content << ""
414
+ end
415
+
416
+ content.join("\n")
417
+ end
418
+
419
+ def generate_complex_types_content
420
+ <<~TRB
421
+ type DeepNested<T> = Hash<String, Array<Hash<Symbol, T>>>
422
+ type UnionOfGenerics<A, B> = Array<A> | Hash<String, B> | nil
423
+ type FunctionType = Proc<Integer> | Lambda<String>
424
+
425
+ interface ComplexInterface<T, U>
426
+ data: DeepNested<T>
427
+ transform: UnionOfGenerics<T, U>
428
+ callback: FunctionType
429
+ end
430
+
431
+ def complex_method<T>(
432
+ input: DeepNested<T>,
433
+ options: Hash<Symbol, String | Integer | Boolean>
434
+ ): UnionOfGenerics<T, String>
435
+ nil
436
+ end
437
+ TRB
438
+ end
439
+
440
+ def generate_simple_types_content
441
+ <<~TRB
442
+ def add(a: Integer, b: Integer): Integer
443
+ a + b
444
+ end
445
+
446
+ def greet(name: String): String
447
+ "Hello, \#{name}"
448
+ end
449
+
450
+ def valid?(value: Boolean): Boolean
451
+ value
452
+ end
453
+ TRB
454
+ end
455
+
456
+ def generate_generic_types_content
457
+ <<~TRB
458
+ def first<T>(items: Array<T>): T | nil
459
+ items.first
460
+ end
461
+
462
+ def map_values<K, V, R>(hash: Hash<K, V>, &block: Proc<R>): Hash<K, R>
463
+ hash.transform_values(&block)
464
+ end
465
+
466
+ def wrap<T>(value: T): Array<T>
467
+ [value]
468
+ end
469
+ TRB
470
+ end
471
+
472
+ def generate_union_types_content
473
+ <<~TRB
474
+ type StringOrNumber = String | Integer
475
+ type NullableString = String | nil
476
+ type Status = "pending" | "active" | "completed"
477
+
478
+ def process(value: StringOrNumber): String
479
+ value.to_s
480
+ end
481
+
482
+ def safe_call(input: NullableString): String
483
+ input || "default"
484
+ end
485
+ TRB
486
+ end
487
+
488
+ def generate_interface_types_content
489
+ <<~TRB
490
+ interface Comparable<T>
491
+ <=>: Integer
492
+ end
493
+
494
+ interface Enumerable<T>
495
+ each: void
496
+ map: Array<T>
497
+ select: Array<T>
498
+ end
499
+
500
+ interface Repository<T>
501
+ find: T | nil
502
+ save: Boolean
503
+ delete: Boolean
504
+ all: Array<T>
505
+ end
506
+
507
+ def sort<T: Comparable<T>>(items: Array<T>): Array<T>
508
+ items.sort
509
+ end
510
+ TRB
511
+ end
512
+
513
+ def calculate_stats(times)
514
+ avg = times.sum / times.length.to_f
515
+ min = times.min
516
+ max = times.max
517
+ variance = times.map { |t| (t - avg)**2 }.sum / times.length.to_f
518
+ std_dev = Math.sqrt(variance)
519
+
520
+ {
521
+ avg_time: avg,
522
+ min_time: min,
523
+ max_time: max,
524
+ std_dev: std_dev,
525
+ iterations: times.length
526
+ }
527
+ end
528
+
529
+ def print_result(name, stats, unit: "ms")
530
+ if unit == "KB"
531
+ puts " #{name}: #{stats[:memory].round(2)} KB"
532
+ else
533
+ avg_ms = (stats[:avg_time] * 1000).round(3)
534
+ std_ms = (stats[:std_dev] * 1000).round(3)
535
+ puts " #{name}: #{avg_ms}ms (±#{std_ms}ms)"
536
+ end
537
+ end
538
+
539
+ def print_summary
540
+ puts "=" * 60
541
+ puts "SUMMARY"
542
+ puts "=" * 60
543
+
544
+ total_time = 0
545
+ @results.each do |category, benchmarks|
546
+ cat_time = benchmarks.values.sum { |b| b[:avg_time] || 0 }
547
+ total_time += cat_time
548
+ puts "#{category}: #{(cat_time * 1000).round(2)}ms total"
549
+ end
550
+
551
+ puts "-" * 40
552
+ puts "Total benchmark time: #{(total_time * 1000).round(2)}ms"
553
+ end
554
+
555
+ def get_memory_usage
556
+ # Returns memory in KB
557
+ if RUBY_PLATFORM =~ /linux/
558
+ File.read("/proc/#{Process.pid}/statm").split[1].to_i * 4 # pages * 4KB
559
+ else
560
+ # Fallback using GC stats
561
+ GC.stat[:heap_live_slots] * 40 / 1024.0 # approximate
562
+ end
563
+ end
564
+ end
565
+
566
+ # Quick benchmark helper
567
+ module QuickBenchmark
568
+ def self.measure(name = "Operation", iterations: 100)
569
+ times = []
570
+
571
+ iterations.times do
572
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
573
+ yield
574
+ times << Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
575
+ end
576
+
577
+ avg = times.sum / times.length
578
+ puts "#{name}: #{(avg * 1000).round(3)}ms avg (#{iterations} iterations)"
579
+
580
+ avg
581
+ end
582
+
583
+ def self.compare(name, &block)
584
+ before = Process.clock_gettime(Process::CLOCK_MONOTONIC)
585
+ result = block.call
586
+ after = Process.clock_gettime(Process::CLOCK_MONOTONIC)
587
+
588
+ puts "#{name}: #{((after - before) * 1000).round(3)}ms"
589
+ result
590
+ end
591
+ end
592
+ end