tint_me 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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop_todo.yml +7 -0
  3. data/.serena/project.yml +68 -0
  4. data/.simplecov +24 -0
  5. data/.yardopts +6 -0
  6. data/AGENTS.md +60 -0
  7. data/CHANGELOG.md +29 -0
  8. data/CLAUDE.md +1 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +175 -0
  11. data/RELEASING.md +202 -0
  12. data/Rakefile +18 -0
  13. data/benchmark/2025-09-08-style-caching-optimization/01_baseline_results.txt +39 -0
  14. data/benchmark/2025-09-08-style-caching-optimization/02_with_full_caching_results.txt +54 -0
  15. data/benchmark/2025-09-08-style-caching-optimization/03_prefix_only_caching_results.txt +107 -0
  16. data/benchmark/2025-09-08-style-caching-optimization/04_baseline_vs_optimized_analysis.txt +65 -0
  17. data/benchmark/2025-09-08-style-caching-optimization/05_caching_approaches_comparison.txt +59 -0
  18. data/benchmark/2025-09-08-style-caching-optimization/06_append_operator_results.txt +107 -0
  19. data/benchmark/2025-09-08-style-caching-optimization/07_string_concatenation_comparison.txt +66 -0
  20. data/benchmark/2025-09-08-style-caching-optimization/08_with_freeze_optimization_results.txt +107 -0
  21. data/benchmark/2025-09-08-style-caching-optimization/09_freeze_optimization_analysis.txt +49 -0
  22. data/benchmark/2025-09-08-style-caching-optimization/10_constant_access_results.txt +107 -0
  23. data/benchmark/2025-09-08-style-caching-optimization/11_constant_vs_cache_analysis.txt +74 -0
  24. data/benchmark/2025-09-08-style-caching-optimization/12_empty_prefix_analysis.txt +81 -0
  25. data/benchmark/2025-09-08-style-caching-optimization/13_nil_check_optimization_results.txt +107 -0
  26. data/benchmark/2025-09-08-style-caching-optimization/14_nil_vs_empty_check_analysis.txt +81 -0
  27. data/benchmark/2025-09-08-style-caching-optimization/README.md +45 -0
  28. data/benchmark/2025-09-08-style-caching-optimization/benchmark_script.rb +180 -0
  29. data/docs/agents/git-pr.md +298 -0
  30. data/docs/agents/languages.md +388 -0
  31. data/docs/agents/rubocop.md +55 -0
  32. data/lib/tint_me/error.rb +6 -0
  33. data/lib/tint_me/sgr_builder.rb +259 -0
  34. data/lib/tint_me/style/schema.rb +22 -0
  35. data/lib/tint_me/style/types.rb +50 -0
  36. data/lib/tint_me/style.rb +286 -0
  37. data/lib/tint_me/version.rb +8 -0
  38. data/lib/tint_me.rb +62 -0
  39. data/mise.toml +5 -0
  40. data/sig/tint_me.rbs +61 -0
  41. metadata +131 -0
@@ -0,0 +1,107 @@
1
+ ============================================================
2
+ TIntMe Style Performance Benchmark
3
+ ============================================================
4
+
5
+ Memory Usage Analysis:
6
+ ----------------------------------------
7
+ Calculating -------------------------------------
8
+ Create simple style 6.048k memsize ( 0.000 retained)
9
+ 58.000 objects ( 0.000 retained)
10
+ 2.000 strings ( 0.000 retained)
11
+ Create complex style 7.744k memsize ( 0.000 retained)
12
+ 79.000 objects ( 0.000 retained)
13
+ 9.000 strings ( 0.000 retained)
14
+ Apply simple style (short text)
15
+ 616.000 memsize ( 0.000 retained)
16
+ 4.000 objects ( 0.000 retained)
17
+ 1.000 strings ( 0.000 retained)
18
+ Apply complex style (short text)
19
+ 264.000 memsize ( 0.000 retained)
20
+ 2.000 objects ( 0.000 retained)
21
+ 1.000 strings ( 0.000 retained)
22
+
23
+ Comparison:
24
+ Apply complex style (short text): 264 allocated
25
+ Apply simple style (short text): 616 allocated - 2.33x more
26
+ Create simple style: 6048 allocated - 22.91x more
27
+ Create complex style: 7744 allocated - 29.33x more
28
+
29
+ Performance Benchmark (operations per second):
30
+ ----------------------------------------
31
+ ruby 3.2.9 (2025-07-24 revision 8f611e0c46) [arm64-darwin24]
32
+ Warming up --------------------------------------
33
+ Create simple style 2.828k i/100ms
34
+ Create complex style 2.434k i/100ms
35
+ Apply simple style (short)
36
+ 566.692k i/100ms
37
+ Apply simple style (medium)
38
+ 492.842k i/100ms
39
+ Apply simple style (long)
40
+ 493.077k i/100ms
41
+ Apply complex style (short)
42
+ 388.767k i/100ms
43
+ Apply complex style (medium)
44
+ 465.948k i/100ms
45
+ Apply complex style (long)
46
+ 405.436k i/100ms
47
+ Apply hex style (short)
48
+ 454.188k i/100ms
49
+ Calculating -------------------------------------
50
+ Create simple style 26.982k (± 6.8%) i/s (37.06 μs/i) - 135.744k in 5.055760s
51
+ Create complex style 22.324k (± 7.6%) i/s (44.80 μs/i) - 111.964k in 5.048157s
52
+ Apply simple style (short)
53
+ 5.383M (± 4.2%) i/s (185.78 ns/i) - 27.201M in 5.062923s
54
+ Apply simple style (medium)
55
+ 4.710M (± 5.8%) i/s (212.30 ns/i) - 23.656M in 5.043190s
56
+ Apply simple style (long)
57
+ 4.582M (± 3.1%) i/s (218.23 ns/i) - 23.175M in 5.062439s
58
+ Apply complex style (short)
59
+ 3.777M (± 3.0%) i/s (264.78 ns/i) - 19.050M in 5.048997s
60
+ Apply complex style (medium)
61
+ 4.664M (± 2.6%) i/s (214.42 ns/i) - 23.763M in 5.099101s
62
+ Apply complex style (long)
63
+ 4.359M (± 5.2%) i/s (229.40 ns/i) - 21.894M in 5.038110s
64
+ Apply hex style (short)
65
+ 4.689M (± 3.3%) i/s (213.25 ns/i) - 23.618M in 5.042292s
66
+
67
+ Comparison:
68
+ Apply simple style (short): 5382832.4 i/s
69
+ Apply simple style (medium): 4710226.6 i/s - 1.14x slower
70
+ Apply hex style (short): 4689404.2 i/s - 1.15x slower
71
+ Apply complex style (medium): 4663664.1 i/s - 1.15x slower
72
+ Apply simple style (long): 4582346.7 i/s - 1.17x slower
73
+ Apply complex style (long): 4359256.4 i/s - 1.23x slower
74
+ Apply complex style (short): 3776649.2 i/s - 1.43x slower
75
+ Create simple style: 26982.3 i/s - 199.50x slower
76
+ Create complex style: 22323.7 i/s - 241.13x slower
77
+
78
+
79
+ Repeated Application Benchmark:
80
+ ----------------------------------------
81
+ Applying the same style 1000 times to measure caching benefit...
82
+ ruby 3.2.9 (2025-07-24 revision 8f611e0c46) [arm64-darwin24]
83
+ Warming up --------------------------------------
84
+ 1000x simple style 654.000 i/100ms
85
+ 1000x complex style 379.000 i/100ms
86
+ 1000x hex style 546.000 i/100ms
87
+ Calculating -------------------------------------
88
+ 1000x simple style 6.440k (± 1.6%) i/s (155.28 μs/i) - 32.700k in 5.079043s
89
+ 1000x complex style 4.218k (± 4.5%) i/s (237.07 μs/i) - 21.224k in 5.044673s
90
+ 1000x hex style 5.513k (± 2.2%) i/s (181.39 μs/i) - 27.846k in 5.053459s
91
+
92
+ Comparison:
93
+ 1000x simple style: 6439.9 i/s
94
+ 1000x hex style: 5513.1 i/s - 1.17x slower
95
+ 1000x complex style: 4218.1 i/s - 1.53x slower
96
+
97
+
98
+ Object Allocation Analysis:
99
+ ----------------------------------------
100
+ Create simple style: 117 objects allocated
101
+ Create complex style: 136 objects allocated
102
+ Apply simple style 100x: 104 objects allocated
103
+ Apply complex style 100x: 104 objects allocated
104
+
105
+ ============================================================
106
+ Benchmark complete!
107
+ ============================================================
@@ -0,0 +1,74 @@
1
+ CONSTANT ACCESS vs CACHING ANALYSIS
2
+ ====================================
3
+ Date: 2025-09-08
4
+
5
+ ## Implementation Comparison
6
+
7
+ ### Caching Approach (Previous)
8
+ ```ruby
9
+ # Initialization
10
+ @reset_code = sgr_builder.reset_code
11
+
12
+ # Usage
13
+ "#{@prefix}#{text}#{@reset_code}"
14
+ ```
15
+
16
+ ### Constant Access Approach (Current)
17
+ ```ruby
18
+ # Initialization
19
+ # (no @reset_code caching)
20
+
21
+ # Usage
22
+ "#{@prefix}#{text}#{SGRBuilder::RESET_CODE}"
23
+ ```
24
+
25
+ ## Performance & Memory Results
26
+
27
+ | Metric | Caching | Constant Access | Difference |
28
+ |--------|---------|-----------------|------------|
29
+ | **Performance** | 5.44M/s | 5.38M/s | **1% slower** |
30
+ | **Creation Memory** | 6.1KB | 6.0KB | **8 bytes saved** |
31
+ | **Runtime Memory** | 424 bytes | 616 bytes | **45% more** |
32
+
33
+ ## Analysis
34
+
35
+ ### Memory Trade-offs:
36
+
37
+ **Creation Time:**
38
+ - ✅ **8 bytes saved per Style instance** (no @reset_code storage)
39
+ - Negligible but consistent savings
40
+
41
+ **Runtime:**
42
+ - ❌ **45% more memory per call** for simple styles (424 → 616 bytes)
43
+ - Suggests constant lookup has slight overhead
44
+
45
+ ### Performance Impact:
46
+
47
+ - **1% slower performance** - minimal but measurable
48
+ - Constant lookup vs instance variable access overhead
49
+ - Still much faster than baseline (5.38M vs 766k original)
50
+
51
+ ### Code Complexity:
52
+
53
+ **Constant Access Pros:**
54
+ - Slightly less memory per instance
55
+ - Fewer instance variables
56
+ - Direct reference to source of truth
57
+
58
+ **Caching Pros:**
59
+ - Faster runtime performance
60
+ - Lower runtime memory usage
61
+ - Simpler call method (no constant lookup)
62
+
63
+ ## Recommendation
64
+
65
+ **Keep the caching approach** for optimal performance:
66
+
67
+ 1. **Performance priority**: The 1% performance difference matters for a performance-critical method
68
+ 2. **Runtime efficiency**: Lower memory usage during frequent calls
69
+ 3. **Minimal memory cost**: 8 bytes per instance is negligible for the benefits
70
+ 4. **Consistent pattern**: Matches the prefix caching strategy
71
+
72
+ The reset_code is accessed on every Style#call, making instance variable access more efficient than constant lookup. The tiny memory savings don't justify the performance cost.
73
+
74
+ **Final implementation: Cache both @prefix and @reset_code**
@@ -0,0 +1,81 @@
1
+ EMPTY PREFIX ANALYSIS
2
+ =====================
3
+ Date: 2025-09-08
4
+
5
+ ## When @prefix is Empty
6
+
7
+ Based on testing, `@prefix` becomes empty ("") in these cases:
8
+
9
+ 1. **No styling attributes**: `Style.new`
10
+ 2. **Nil values**: `Style.new(foreground: nil)`
11
+ 3. **Default values**: `Style.new(foreground: :default)`
12
+ 4. **False effects**: `Style.new(bold: false)`
13
+ 5. **Default instance**: `Style.new.to_h` result
14
+
15
+ **Key Finding**: `@prefix` is NEVER nil, always either `""` (empty) or a non-empty string.
16
+
17
+ ## Usage Likelihood Analysis
18
+
19
+ ### High Probability Cases:
20
+ - **Default/plain text styling**: Applications may use `Style.new` for conditional styling
21
+ - **Template systems**: May create base styles that are sometimes empty
22
+ - **Configuration-driven styling**: User settings might result in "no styling"
23
+
24
+ ### Low Probability Cases:
25
+ - **Performance-critical code**: Unlikely to create empty styles intentionally
26
+ - **Library internal usage**: Most internal usage probably has specific styling
27
+
28
+ ## Current vs Alternative Implementations
29
+
30
+ ### Current: `empty?` check
31
+ ```ruby
32
+ def call(text)
33
+ return text if @prefix.empty?
34
+ "#{@prefix}#{text}#{@reset_code}"
35
+ end
36
+ ```
37
+
38
+ ### Alternative 1: Length check
39
+ ```ruby
40
+ def call(text)
41
+ return text if @prefix.length == 0
42
+ "#{@prefix}#{text}#{@reset_code}"
43
+ end
44
+ ```
45
+
46
+ ### Alternative 2: String comparison
47
+ ```ruby
48
+ def call(text)
49
+ return text if @prefix == ""
50
+ "#{@prefix}#{text}#{@reset_code}"
51
+ end
52
+ ```
53
+
54
+ ### Alternative 3: No check (always interpolate)
55
+ ```ruby
56
+ def call(text)
57
+ "#{@prefix}#{text}#{@reset_code}"
58
+ end
59
+ ```
60
+
61
+ ## Performance Considerations
62
+
63
+ **If empty styles are rare (<5% of calls):**
64
+ - Current `empty?` check may be unnecessary overhead
65
+ - Alternative 3 (no check) might be faster overall
66
+
67
+ **If empty styles are common (>20% of calls):**
68
+ - Current implementation is optimal
69
+ - Early return saves string interpolation cost
70
+
71
+ ## Recommendation for Testing
72
+
73
+ Test Alternative 3 (no empty check) to see if:
74
+ 1. The empty check overhead exceeds the interpolation savings
75
+ 2. Performance improves when most styles are non-empty
76
+
77
+ This would be especially valuable since empty styles might be rare in typical usage patterns.
78
+
79
+ ## Conclusion
80
+
81
+ The `nil?` vs `empty?` question is moot since `@prefix` is never nil. The real question is whether the `empty?` check itself is worth the overhead for the expected usage patterns of the library.
@@ -0,0 +1,107 @@
1
+ ============================================================
2
+ TIntMe Style Performance Benchmark
3
+ ============================================================
4
+
5
+ Memory Usage Analysis:
6
+ ----------------------------------------
7
+ Calculating -------------------------------------
8
+ Create simple style 6.056k memsize ( 0.000 retained)
9
+ 58.000 objects ( 0.000 retained)
10
+ 2.000 strings ( 0.000 retained)
11
+ Create complex style 7.752k memsize ( 0.000 retained)
12
+ 79.000 objects ( 0.000 retained)
13
+ 9.000 strings ( 0.000 retained)
14
+ Apply simple style (short text)
15
+ 424.000 memsize ( 0.000 retained)
16
+ 3.000 objects ( 0.000 retained)
17
+ 1.000 strings ( 0.000 retained)
18
+ Apply complex style (short text)
19
+ 264.000 memsize ( 0.000 retained)
20
+ 2.000 objects ( 0.000 retained)
21
+ 1.000 strings ( 0.000 retained)
22
+
23
+ Comparison:
24
+ Apply complex style (short text): 264 allocated
25
+ Apply simple style (short text): 424 allocated - 1.61x more
26
+ Create simple style: 6056 allocated - 22.94x more
27
+ Create complex style: 7752 allocated - 29.36x more
28
+
29
+ Performance Benchmark (operations per second):
30
+ ----------------------------------------
31
+ ruby 3.2.9 (2025-07-24 revision 8f611e0c46) [arm64-darwin24]
32
+ Warming up --------------------------------------
33
+ Create simple style 2.856k i/100ms
34
+ Create complex style 2.237k i/100ms
35
+ Apply simple style (short)
36
+ 532.099k i/100ms
37
+ Apply simple style (medium)
38
+ 502.313k i/100ms
39
+ Apply simple style (long)
40
+ 488.252k i/100ms
41
+ Apply complex style (short)
42
+ 375.127k i/100ms
43
+ Apply complex style (medium)
44
+ 465.987k i/100ms
45
+ Apply complex style (long)
46
+ 465.624k i/100ms
47
+ Apply hex style (short)
48
+ 487.755k i/100ms
49
+ Calculating -------------------------------------
50
+ Create simple style 28.773k (± 3.1%) i/s (34.76 μs/i) - 145.656k in 5.067273s
51
+ Create complex style 24.042k (± 3.7%) i/s (41.59 μs/i) - 120.798k in 5.031925s
52
+ Apply simple style (short)
53
+ 5.456M (± 2.7%) i/s (183.29 ns/i) - 27.669M in 5.075360s
54
+ Apply simple style (medium)
55
+ 4.795M (± 2.8%) i/s (208.54 ns/i) - 24.111M in 5.032058s
56
+ Apply simple style (long)
57
+ 4.520M (± 5.1%) i/s (221.24 ns/i) - 22.948M in 5.091184s
58
+ Apply complex style (short)
59
+ 3.792M (± 3.6%) i/s (263.74 ns/i) - 19.131M in 5.052424s
60
+ Apply complex style (medium)
61
+ 4.633M (± 6.4%) i/s (215.86 ns/i) - 23.299M in 5.059053s
62
+ Apply complex style (long)
63
+ 4.508M (± 3.5%) i/s (221.85 ns/i) - 22.816M in 5.068199s
64
+ Apply hex style (short)
65
+ 4.534M (± 7.2%) i/s (220.55 ns/i) - 22.924M in 5.085921s
66
+
67
+ Comparison:
68
+ Apply simple style (short): 5455745.6 i/s
69
+ Apply simple style (medium): 4795323.9 i/s - 1.14x slower
70
+ Apply complex style (medium): 4632694.4 i/s - 1.18x slower
71
+ Apply hex style (short): 4534207.7 i/s - 1.20x slower
72
+ Apply simple style (long): 4520017.7 i/s - 1.21x slower
73
+ Apply complex style (long): 4507642.6 i/s - 1.21x slower
74
+ Apply complex style (short): 3791544.6 i/s - 1.44x slower
75
+ Create simple style: 28772.5 i/s - 189.62x slower
76
+ Create complex style: 24041.8 i/s - 226.93x slower
77
+
78
+
79
+ Repeated Application Benchmark:
80
+ ----------------------------------------
81
+ Applying the same style 1000 times to measure caching benefit...
82
+ ruby 3.2.9 (2025-07-24 revision 8f611e0c46) [arm64-darwin24]
83
+ Warming up --------------------------------------
84
+ 1000x simple style 644.000 i/100ms
85
+ 1000x complex style 440.000 i/100ms
86
+ 1000x hex style 542.000 i/100ms
87
+ Calculating -------------------------------------
88
+ 1000x simple style 6.412k (± 2.6%) i/s (155.95 μs/i) - 32.200k in 5.025177s
89
+ 1000x complex style 4.297k (± 2.5%) i/s (232.70 μs/i) - 21.560k in 5.020173s
90
+ 1000x hex style 5.504k (± 2.4%) i/s (181.70 μs/i) - 27.642k in 5.025548s
91
+
92
+ Comparison:
93
+ 1000x simple style: 6412.2 i/s
94
+ 1000x hex style: 5503.6 i/s - 1.17x slower
95
+ 1000x complex style: 4297.4 i/s - 1.49x slower
96
+
97
+
98
+ Object Allocation Analysis:
99
+ ----------------------------------------
100
+ Create simple style: 117 objects allocated
101
+ Create complex style: 136 objects allocated
102
+ Apply simple style 100x: 104 objects allocated
103
+ Apply complex style 100x: 104 objects allocated
104
+
105
+ ============================================================
106
+ Benchmark complete!
107
+ ============================================================
@@ -0,0 +1,81 @@
1
+ NIL? vs EMPTY? CHECK ANALYSIS
2
+ ==============================
3
+ Date: 2025-09-08
4
+
5
+ ## Implementation Comparison
6
+
7
+ ### Previous: empty? check
8
+ ```ruby
9
+ # Initialization
10
+ @prefix = sgr_builder.prefix_codes(...)
11
+
12
+ # Usage
13
+ return text if @prefix.empty?
14
+ ```
15
+
16
+ ### Current: nil? check with optimized assignment
17
+ ```ruby
18
+ # Initialization
19
+ prefix = sgr_builder.prefix_codes(...)
20
+ @prefix = prefix.empty? ? nil : prefix
21
+
22
+ # Usage
23
+ return text unless @prefix # equivalent to @prefix.nil?
24
+ ```
25
+
26
+ ## Performance Results
27
+
28
+ | Metric | empty? check | nil? check | Improvement |
29
+ |--------|-------------|------------|-------------|
30
+ | Simple style | 5.44M/s | 5.46M/s | **+0.4% faster** |
31
+ | Performance gain | Baseline | +22k ops/s | Marginal but positive |
32
+
33
+ ## Analysis
34
+
35
+ ### Why nil? check is (slightly) faster:
36
+
37
+ 1. **Simpler comparison**: `nil?` is checking against a single value (nil)
38
+ 2. **No method call**: `unless @prefix` is more direct than `@prefix.empty?`
39
+ 3. **CPU cache friendly**: nil comparisons are highly optimized in Ruby
40
+
41
+ ### Trade-offs:
42
+
43
+ **Pros of nil? optimization:**
44
+ - ✅ Slightly faster runtime performance
45
+ - ✅ More direct conditional check
46
+ - ✅ Saves one string object per empty Style (empty string not stored)
47
+
48
+ **Cons of nil? optimization:**
49
+ - ❌ Additional logic in initialization (empty? check to decide nil)
50
+ - ❌ Slightly more complex initialization code
51
+ - ❌ Marginal complexity increase
52
+
53
+ ## Memory Impact
54
+
55
+ ### Empty Style instances:
56
+ - **Before**: `@prefix = ""` (empty string object)
57
+ - **After**: `@prefix = nil` (no string object)
58
+ - **Savings**: ~40 bytes per empty Style instance
59
+
60
+ ### Non-empty Style instances:
61
+ - No difference in memory usage
62
+
63
+ ## Recommendation
64
+
65
+ **Keep the nil? optimization** because:
66
+
67
+ 1. **Performance improvement**: Even marginal gains matter for high-frequency methods
68
+ 2. **Memory efficiency**: Eliminates empty string objects for empty Styles
69
+ 3. **Semantic clarity**: nil clearly indicates "no styling" vs empty string
70
+ 4. **Ruby idiom**: `unless value` is more idiomatic than `if value.empty?`
71
+
72
+ The initialization complexity increase is minimal and the benefits outweigh the costs.
73
+
74
+ ## Final Implementation Pattern
75
+
76
+ This optimization demonstrates an important pattern:
77
+ - Transform empty values to nil during initialization
78
+ - Use simpler nil checks at runtime
79
+ - Trade slight initialization complexity for runtime performance
80
+
81
+ Perfect for performance-critical methods called frequently.
@@ -0,0 +1,45 @@
1
+ # Style Caching Performance Optimization Results
2
+
3
+ Date: 2025-09-08
4
+ Issue: #5
5
+
6
+ ## Files in this directory:
7
+
8
+ ### 1. Benchmark Script
9
+ - `benchmark_script.rb` - Performance testing script used for all measurements
10
+
11
+ ### 2. Benchmark Results (in chronological order)
12
+ - `01_baseline_results.txt` - Original performance before optimization
13
+ - `02_with_full_caching_results.txt` - Performance with both prefix and reset_code caching
14
+ - `03_prefix_only_caching_results.txt` - Performance with prefix caching only (no reset_code cache)
15
+ - `06_append_operator_results.txt` - Performance with << operator instead of string interpolation
16
+
17
+ ### 3. Analysis Files
18
+ - `04_baseline_vs_optimized_analysis.txt` - Comparison between baseline and fully optimized version
19
+ - `05_caching_approaches_comparison.txt` - Comparison between different caching strategies
20
+ - `07_string_concatenation_comparison.txt` - String interpolation vs << operator performance analysis
21
+
22
+ ## Key Results Summary
23
+
24
+ ### Final Implementation: Full Caching with String Interpolation
25
+ - **7-18x performance improvement** in style application
26
+ - **85-91% reduction** in object allocations
27
+ - **Minimal memory overhead** at creation time (6-18% increase)
28
+ - **Best overall trade-off** for typical usage patterns
29
+
30
+ ### Primary Optimization Results (Included in PR)
31
+ - **Full caching** (prefix + reset_code): Best performance
32
+ - **Prefix-only caching**: 14-22% slower, minimal memory savings
33
+ - **No caching** (baseline): Significantly slower, high object allocations
34
+
35
+ ### Additional Experiments (Reference Only)
36
+ - **String interpolation vs << operator**: String interpolation 13-47% faster
37
+ - **freeze optimization**: 2-4% slower (counter-productive)
38
+ - **Constant vs caching**: Caching 1% faster with better memory usage
39
+ - **nil? vs empty? check**: nil? optimization provides 0.4% improvement
40
+
41
+ ## Implementation Details
42
+ - Cache `@prefix` and `@reset_code` in `initialize` method before `super`
43
+ - Simplified `call` method from ~35 lines to 3 lines of string concatenation
44
+ - Maintains identical API and immutability guarantees
45
+ - Transforms algorithm from O(n) to O(1) per call
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "benchmark/ips"
5
+ require "benchmark/memory"
6
+ require "bundler/setup"
7
+ require "tint_me"
8
+
9
+ # Test data
10
+ SHORT_TEXT = "Hello"
11
+ MEDIUM_TEXT = "The quick brown fox jumps over the lazy dog"
12
+ LONG_TEXT = "Lorem ipsum dolor sit amet, " * 10
13
+
14
+ # Create various styles
15
+ SIMPLE_STYLE = TIntMe::Style.new(foreground: :red)
16
+ COMPLEX_STYLE = TIntMe::Style.new(
17
+ foreground: :blue,
18
+ background: :yellow,
19
+ bold: true,
20
+ italic: true,
21
+ underline: true
22
+ )
23
+ HEX_STYLE = TIntMe::Style.new(
24
+ foreground: "#FF6B35",
25
+ background: "#F7931E",
26
+ bold: true,
27
+ overline: true
28
+ )
29
+
30
+ puts "=" * 60
31
+ puts "TIntMe Style Performance Benchmark"
32
+ puts "=" * 60
33
+ puts
34
+
35
+ # Memory benchmark
36
+ puts "Memory Usage Analysis:"
37
+ puts "-" * 40
38
+ Benchmark.memory do |x|
39
+ x.report("Create simple style") do
40
+ TIntMe::Style.new(foreground: :red)
41
+ end
42
+
43
+ x.report("Create complex style") do
44
+ TIntMe::Style.new(
45
+ foreground: :blue,
46
+ background: :yellow,
47
+ bold: true,
48
+ italic: true,
49
+ underline: true,
50
+ overline: true,
51
+ blink: true
52
+ )
53
+ end
54
+
55
+ x.report("Apply simple style (short text)") do
56
+ SIMPLE_STYLE.call(SHORT_TEXT)
57
+ end
58
+
59
+ x.report("Apply complex style (short text)") do
60
+ COMPLEX_STYLE.call(SHORT_TEXT)
61
+ end
62
+
63
+ x.compare!
64
+ end
65
+
66
+ puts
67
+ puts "Performance Benchmark (operations per second):"
68
+ puts "-" * 40
69
+
70
+ Benchmark.ips do |x|
71
+ x.config(time: 5, warmup: 2)
72
+
73
+ # Style creation benchmarks
74
+ x.report("Create simple style") do
75
+ TIntMe::Style.new(foreground: :red)
76
+ end
77
+
78
+ x.report("Create complex style") do
79
+ TIntMe::Style.new(
80
+ foreground: :blue,
81
+ background: :yellow,
82
+ bold: true,
83
+ italic: true,
84
+ underline: true
85
+ )
86
+ end
87
+
88
+ # Style application benchmarks
89
+ x.report("Apply simple style (short)") do
90
+ SIMPLE_STYLE.call(SHORT_TEXT)
91
+ end
92
+
93
+ x.report("Apply simple style (medium)") do
94
+ SIMPLE_STYLE.call(MEDIUM_TEXT)
95
+ end
96
+
97
+ x.report("Apply simple style (long)") do
98
+ SIMPLE_STYLE.call(LONG_TEXT)
99
+ end
100
+
101
+ x.report("Apply complex style (short)") do
102
+ COMPLEX_STYLE.call(SHORT_TEXT)
103
+ end
104
+
105
+ x.report("Apply complex style (medium)") do
106
+ COMPLEX_STYLE.call(MEDIUM_TEXT)
107
+ end
108
+
109
+ x.report("Apply complex style (long)") do
110
+ COMPLEX_STYLE.call(LONG_TEXT)
111
+ end
112
+
113
+ x.report("Apply hex style (short)") do
114
+ HEX_STYLE.call(SHORT_TEXT)
115
+ end
116
+
117
+ x.compare!
118
+ end
119
+
120
+ # Repeated application benchmark
121
+ puts
122
+ puts "Repeated Application Benchmark:"
123
+ puts "-" * 40
124
+ puts "Applying the same style 1000 times to measure caching benefit..."
125
+
126
+ styles = [SIMPLE_STYLE, COMPLEX_STYLE, HEX_STYLE]
127
+ style_names = %w[simple complex hex]
128
+
129
+ Benchmark.ips do |x|
130
+ x.config(time: 5, warmup: 2)
131
+
132
+ styles.zip(style_names).each do |style, name|
133
+ x.report("1000x #{name} style") do
134
+ 1000.times { style.call(SHORT_TEXT) }
135
+ end
136
+ end
137
+
138
+ x.compare!
139
+ end
140
+
141
+ # Object allocation tracking
142
+ puts
143
+ puts "Object Allocation Analysis:"
144
+ puts "-" * 40
145
+
146
+ # Counts object allocations during block execution
147
+ # @return [Integer] Number of objects allocated
148
+ def count_allocations
149
+ before = GC.stat[:total_allocated_objects]
150
+ yield
151
+ after = GC.stat[:total_allocated_objects]
152
+ after - before
153
+ end
154
+
155
+ # Measure allocations for different operations
156
+ operations = {
157
+ "Create simple style" => -> { TIntMe::Style.new(foreground: :red) },
158
+ "Create complex style" => -> {
159
+ TIntMe::Style.new(
160
+ foreground: :blue,
161
+ background: :yellow,
162
+ bold: true,
163
+ italic: true,
164
+ underline: true
165
+ )
166
+ },
167
+ "Apply simple style 100x" => -> { 100.times { SIMPLE_STYLE.call(SHORT_TEXT) } },
168
+ "Apply complex style 100x" => -> { 100.times { COMPLEX_STYLE.call(SHORT_TEXT) } }
169
+ }
170
+
171
+ operations.each do |name, operation|
172
+ GC.start
173
+ allocations = count_allocations(&operation)
174
+ puts "#{name}: #{allocations} objects allocated"
175
+ end
176
+
177
+ puts
178
+ puts "=" * 60
179
+ puts "Benchmark complete!"
180
+ puts "=" * 60