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.
- checksums.yaml +7 -0
- data/.rubocop_todo.yml +7 -0
- data/.serena/project.yml +68 -0
- data/.simplecov +24 -0
- data/.yardopts +6 -0
- data/AGENTS.md +60 -0
- data/CHANGELOG.md +29 -0
- data/CLAUDE.md +1 -0
- data/LICENSE.txt +21 -0
- data/README.md +175 -0
- data/RELEASING.md +202 -0
- data/Rakefile +18 -0
- data/benchmark/2025-09-08-style-caching-optimization/01_baseline_results.txt +39 -0
- data/benchmark/2025-09-08-style-caching-optimization/02_with_full_caching_results.txt +54 -0
- data/benchmark/2025-09-08-style-caching-optimization/03_prefix_only_caching_results.txt +107 -0
- data/benchmark/2025-09-08-style-caching-optimization/04_baseline_vs_optimized_analysis.txt +65 -0
- data/benchmark/2025-09-08-style-caching-optimization/05_caching_approaches_comparison.txt +59 -0
- data/benchmark/2025-09-08-style-caching-optimization/06_append_operator_results.txt +107 -0
- data/benchmark/2025-09-08-style-caching-optimization/07_string_concatenation_comparison.txt +66 -0
- data/benchmark/2025-09-08-style-caching-optimization/08_with_freeze_optimization_results.txt +107 -0
- data/benchmark/2025-09-08-style-caching-optimization/09_freeze_optimization_analysis.txt +49 -0
- data/benchmark/2025-09-08-style-caching-optimization/10_constant_access_results.txt +107 -0
- data/benchmark/2025-09-08-style-caching-optimization/11_constant_vs_cache_analysis.txt +74 -0
- data/benchmark/2025-09-08-style-caching-optimization/12_empty_prefix_analysis.txt +81 -0
- data/benchmark/2025-09-08-style-caching-optimization/13_nil_check_optimization_results.txt +107 -0
- data/benchmark/2025-09-08-style-caching-optimization/14_nil_vs_empty_check_analysis.txt +81 -0
- data/benchmark/2025-09-08-style-caching-optimization/README.md +45 -0
- data/benchmark/2025-09-08-style-caching-optimization/benchmark_script.rb +180 -0
- data/docs/agents/git-pr.md +298 -0
- data/docs/agents/languages.md +388 -0
- data/docs/agents/rubocop.md +55 -0
- data/lib/tint_me/error.rb +6 -0
- data/lib/tint_me/sgr_builder.rb +259 -0
- data/lib/tint_me/style/schema.rb +22 -0
- data/lib/tint_me/style/types.rb +50 -0
- data/lib/tint_me/style.rb +286 -0
- data/lib/tint_me/version.rb +8 -0
- data/lib/tint_me.rb +62 -0
- data/mise.toml +5 -0
- data/sig/tint_me.rbs +61 -0
- 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
|