string_to_number 0.2.0 → 0.3.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 +4 -4
- data/.github/workflows/ci.yml +81 -0
- data/.github/workflows/release.yml +62 -0
- data/.rubocop.yml +110 -0
- data/CLAUDE.md +23 -85
- data/Gemfile +9 -0
- data/Gemfile.lock +32 -1
- data/README.md +53 -163
- data/Rakefile +5 -1
- data/SECURITY.md +25 -0
- data/benchmark.rb +41 -40
- data/docs/ARCHITECTURE.md +131 -0
- data/docs/demo.gif +0 -0
- data/lib/string_to_number/parser.rb +49 -79
- data/lib/string_to_number/to_number.rb +21 -22
- data/lib/string_to_number/version.rb +3 -1
- data/lib/string_to_number.rb +9 -7
- data/microbenchmark.rb +81 -80
- data/performance_comparison.rb +34 -35
- data/profile.rb +44 -45
- data/string_to_number.gemspec +5 -6
- metadata +15 -51
- data/.travis.yml +0 -5
- /data/{LICENSE.txt → LICENSE} +0 -0
data/microbenchmark.rb
CHANGED
|
@@ -9,10 +9,10 @@ require 'benchmark'
|
|
|
9
9
|
|
|
10
10
|
class MicroBenchmark
|
|
11
11
|
def self.run
|
|
12
|
-
puts
|
|
13
|
-
puts
|
|
12
|
+
puts 'StringToNumber Micro-Benchmarks'
|
|
13
|
+
puts '=' * 50
|
|
14
14
|
puts
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
# Test individual components
|
|
17
17
|
test_initialization
|
|
18
18
|
test_regex_compilation
|
|
@@ -20,43 +20,45 @@ class MicroBenchmark
|
|
|
20
20
|
test_hash_lookups
|
|
21
21
|
test_string_operations
|
|
22
22
|
test_recursion_overhead
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
puts "\nConclusions and Recommendations:"
|
|
25
|
-
puts
|
|
25
|
+
puts '=' * 50
|
|
26
26
|
analyze_results
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def self.test_initialization
|
|
30
|
-
puts
|
|
31
|
-
puts
|
|
32
|
-
|
|
30
|
+
puts '1. Initialization Performance'
|
|
31
|
+
puts '-' * 30
|
|
32
|
+
|
|
33
33
|
# Test the cost of creating new instances
|
|
34
34
|
sentences = ['un', 'vingt et un', 'mille deux cent', 'trois milliards cinq cents millions']
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
sentences.each do |sentence|
|
|
37
37
|
time = Benchmark.realtime do
|
|
38
38
|
1000.times { StringToNumber::ToNumber.new(sentence) }
|
|
39
39
|
end
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
puts "#{sentence.ljust(35)}: #{(time * 1000).round(4)}ms per 1000 instances"
|
|
42
42
|
end
|
|
43
43
|
puts
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def self.test_regex_compilation
|
|
47
|
-
puts
|
|
48
|
-
puts
|
|
49
|
-
|
|
47
|
+
puts '2. Regex Compilation Performance'
|
|
48
|
+
puts '-' * 30
|
|
49
|
+
|
|
50
50
|
# Test the cost of regex compilation vs pre-compiled regex
|
|
51
|
-
keys = StringToNumber::ToNumber::POWERS_OF_TEN.keys.reject
|
|
52
|
-
|
|
51
|
+
keys = StringToNumber::ToNumber::POWERS_OF_TEN.keys.reject do |k|
|
|
52
|
+
%w[un dix].include?(k)
|
|
53
|
+
end.sort_by(&:length).reverse.join('|')
|
|
54
|
+
|
|
53
55
|
# Dynamic compilation
|
|
54
56
|
dynamic_time = Benchmark.realtime do
|
|
55
57
|
1000.times do
|
|
56
58
|
/(?<f>.*?)\s?(?<m>#{keys})/.match('trois milliards')
|
|
57
59
|
end
|
|
58
60
|
end
|
|
59
|
-
|
|
61
|
+
|
|
60
62
|
# Pre-compiled regex
|
|
61
63
|
compiled_regex = /(?<f>.*?)\s?(?<m>#{Regexp.escape(keys)})/
|
|
62
64
|
precompiled_time = Benchmark.realtime do
|
|
@@ -64,7 +66,7 @@ class MicroBenchmark
|
|
|
64
66
|
compiled_regex.match('trois milliards')
|
|
65
67
|
end
|
|
66
68
|
end
|
|
67
|
-
|
|
69
|
+
|
|
68
70
|
puts "Dynamic regex compilation: #{(dynamic_time * 1000).round(4)}ms per 1000 matches"
|
|
69
71
|
puts "Pre-compiled regex: #{(precompiled_time * 1000).round(4)}ms per 1000 matches"
|
|
70
72
|
puts "Compilation overhead: #{((dynamic_time - precompiled_time) * 1000).round(4)}ms per 1000 matches"
|
|
@@ -72,62 +74,64 @@ class MicroBenchmark
|
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
def self.test_regex_matching
|
|
75
|
-
puts
|
|
76
|
-
puts
|
|
77
|
-
|
|
77
|
+
puts '3. Regex Pattern Complexity'
|
|
78
|
+
puts '-' * 30
|
|
79
|
+
|
|
78
80
|
# Test different regex patterns to see which are expensive
|
|
79
81
|
test_patterns = {
|
|
80
82
|
'Simple word match' => /vingt/,
|
|
81
83
|
'Word boundary match' => /\bvingt\b/,
|
|
82
84
|
'Named capture groups' => /(?<f>.*?)\s?(?<m>vingt)/,
|
|
83
85
|
'Complex alternation' => /(?<f>.*?)\s?(?<m>vingt|trente|quarante|cinquante)/,
|
|
84
|
-
'Full keys pattern' => /(?<f>.*?)\s?(?<m>#{StringToNumber::ToNumber::POWERS_OF_TEN.keys.reject
|
|
86
|
+
'Full keys pattern' => /(?<f>.*?)\s?(?<m>#{StringToNumber::ToNumber::POWERS_OF_TEN.keys.reject do |k|
|
|
87
|
+
%w[un dix].include?(k)
|
|
88
|
+
end.sort_by(&:length).reverse.join('|')})/
|
|
85
89
|
}
|
|
86
|
-
|
|
90
|
+
|
|
87
91
|
test_string = 'trois milliards cinq cents millions'
|
|
88
|
-
|
|
92
|
+
|
|
89
93
|
test_patterns.each do |name, pattern|
|
|
90
94
|
time = Benchmark.realtime do
|
|
91
95
|
5000.times { pattern.match(test_string) }
|
|
92
96
|
end
|
|
93
|
-
|
|
97
|
+
|
|
94
98
|
puts "#{name.ljust(25)}: #{(time * 1000).round(4)}ms per 5000 matches"
|
|
95
99
|
end
|
|
96
100
|
puts
|
|
97
101
|
end
|
|
98
102
|
|
|
99
103
|
def self.test_hash_lookups
|
|
100
|
-
puts
|
|
101
|
-
puts
|
|
102
|
-
|
|
104
|
+
puts '4. Hash Lookup Performance'
|
|
105
|
+
puts '-' * 30
|
|
106
|
+
|
|
103
107
|
exceptions = StringToNumber::ToNumber::EXCEPTIONS
|
|
104
108
|
powers = StringToNumber::ToNumber::POWERS_OF_TEN
|
|
105
|
-
|
|
109
|
+
|
|
106
110
|
# Test lookup performance
|
|
107
111
|
exceptions_time = Benchmark.realtime do
|
|
108
|
-
|
|
112
|
+
10_000.times do
|
|
109
113
|
exceptions['vingt']
|
|
110
114
|
exceptions['trois']
|
|
111
115
|
exceptions['cent']
|
|
112
116
|
end
|
|
113
117
|
end
|
|
114
|
-
|
|
118
|
+
|
|
115
119
|
powers_time = Benchmark.realtime do
|
|
116
|
-
|
|
120
|
+
10_000.times do
|
|
117
121
|
powers['million']
|
|
118
122
|
powers['mille']
|
|
119
123
|
powers['cent']
|
|
120
124
|
end
|
|
121
125
|
end
|
|
122
|
-
|
|
126
|
+
|
|
123
127
|
# Test nil checks
|
|
124
128
|
nil_check_time = Benchmark.realtime do
|
|
125
|
-
|
|
129
|
+
10_000.times do
|
|
126
130
|
exceptions['nonexistent'].nil?
|
|
127
131
|
powers['nonexistent'].nil?
|
|
128
132
|
end
|
|
129
133
|
end
|
|
130
|
-
|
|
134
|
+
|
|
131
135
|
puts "EXCEPTIONS hash lookups: #{(exceptions_time * 100).round(4)}ms per 10000 lookups"
|
|
132
136
|
puts "POWERS_OF_TEN hash lookups: #{(powers_time * 100).round(4)}ms per 10000 lookups"
|
|
133
137
|
puts "Nil check operations: #{(nil_check_time * 100).round(4)}ms per 10000 checks"
|
|
@@ -135,28 +139,28 @@ class MicroBenchmark
|
|
|
135
139
|
end
|
|
136
140
|
|
|
137
141
|
def self.test_string_operations
|
|
138
|
-
puts
|
|
139
|
-
puts
|
|
140
|
-
|
|
142
|
+
puts '5. String Operations Performance'
|
|
143
|
+
puts '-' * 30
|
|
144
|
+
|
|
141
145
|
test_string = 'TROIS MILLIARDS CINQ CENTS MILLIONS'
|
|
142
|
-
|
|
146
|
+
|
|
143
147
|
# Test different string operations
|
|
144
148
|
downcase_time = Benchmark.realtime do
|
|
145
149
|
5000.times { test_string.downcase }
|
|
146
150
|
end
|
|
147
|
-
|
|
151
|
+
|
|
148
152
|
gsub_time = Benchmark.realtime do
|
|
149
|
-
5000.times { test_string.gsub(
|
|
153
|
+
5000.times { test_string.gsub('MILLIONS', '') }
|
|
150
154
|
end
|
|
151
|
-
|
|
155
|
+
|
|
152
156
|
split_time = Benchmark.realtime do
|
|
153
|
-
5000.times { test_string.split
|
|
157
|
+
5000.times { test_string.split }
|
|
154
158
|
end
|
|
155
|
-
|
|
159
|
+
|
|
156
160
|
tr_time = Benchmark.realtime do
|
|
157
161
|
5000.times { test_string.tr('-', ' ') }
|
|
158
162
|
end
|
|
159
|
-
|
|
163
|
+
|
|
160
164
|
puts "String#downcase: #{(downcase_time * 1000).round(4)}ms per 5000 operations"
|
|
161
165
|
puts "String#gsub: #{(gsub_time * 1000).round(4)}ms per 5000 operations"
|
|
162
166
|
puts "String#split: #{(split_time * 1000).round(4)}ms per 5000 operations"
|
|
@@ -165,29 +169,28 @@ class MicroBenchmark
|
|
|
165
169
|
end
|
|
166
170
|
|
|
167
171
|
def self.test_recursion_overhead
|
|
168
|
-
puts
|
|
169
|
-
puts
|
|
170
|
-
|
|
172
|
+
puts '6. Recursion vs Iteration Performance'
|
|
173
|
+
puts '-' * 30
|
|
174
|
+
|
|
171
175
|
# Compare recursive vs iterative approaches
|
|
172
|
-
|
|
176
|
+
recursive_sum = lambda do |arr, index = 0|
|
|
173
177
|
return 0 if index >= arr.length
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def self.iterative_sum(arr)
|
|
178
|
-
arr.sum
|
|
178
|
+
|
|
179
|
+
arr[index] + recursive_sum.call(arr, index + 1)
|
|
179
180
|
end
|
|
180
|
-
|
|
181
|
+
|
|
182
|
+
iterative_sum = :sum.to_proc
|
|
183
|
+
|
|
181
184
|
test_array = Array.new(100) { rand(100) }
|
|
182
|
-
|
|
185
|
+
|
|
183
186
|
recursive_time = Benchmark.realtime do
|
|
184
|
-
1000.times { recursive_sum(test_array) }
|
|
187
|
+
1000.times { recursive_sum.call(test_array) }
|
|
185
188
|
end
|
|
186
|
-
|
|
189
|
+
|
|
187
190
|
iterative_time = Benchmark.realtime do
|
|
188
|
-
1000.times { iterative_sum(test_array) }
|
|
191
|
+
1000.times { iterative_sum.call(test_array) }
|
|
189
192
|
end
|
|
190
|
-
|
|
193
|
+
|
|
191
194
|
puts "Recursive approach: #{(recursive_time * 1000).round(4)}ms per 1000 operations"
|
|
192
195
|
puts "Iterative approach: #{(iterative_time * 1000).round(4)}ms per 1000 operations"
|
|
193
196
|
puts "Recursion overhead: #{((recursive_time - iterative_time) * 1000).round(4)}ms per 1000 operations"
|
|
@@ -195,32 +198,30 @@ class MicroBenchmark
|
|
|
195
198
|
end
|
|
196
199
|
|
|
197
200
|
def self.analyze_results
|
|
198
|
-
puts
|
|
201
|
+
puts 'Key Performance Insights:'
|
|
199
202
|
puts
|
|
200
|
-
puts
|
|
201
|
-
puts
|
|
202
|
-
puts
|
|
203
|
+
puts '1. 🔍 INITIALIZATION COST:'
|
|
204
|
+
puts ' - Creating new ToNumber instances is expensive (~13ms per 1000)'
|
|
205
|
+
puts ' - Consider caching or singleton pattern for repeated use'
|
|
203
206
|
puts
|
|
204
|
-
puts
|
|
205
|
-
puts
|
|
206
|
-
puts
|
|
207
|
-
puts
|
|
207
|
+
puts '2. 🔍 REGEX COMPLEXITY:'
|
|
208
|
+
puts ' - Complex alternation patterns are the main bottleneck'
|
|
209
|
+
puts ' - Keys pattern is 521 characters long - very expensive to match'
|
|
210
|
+
puts ' - Consider breaking down into simpler patterns or using different approach'
|
|
208
211
|
puts
|
|
209
|
-
puts
|
|
210
|
-
puts
|
|
211
|
-
puts
|
|
212
|
-
puts
|
|
212
|
+
puts '3. 🔍 SCALABILITY ISSUES:'
|
|
213
|
+
puts ' - Performance degrades significantly with input length (43x for longest)'
|
|
214
|
+
puts ' - Recursive parsing creates overhead for complex numbers'
|
|
215
|
+
puts ' - String operations add up with multiple passes'
|
|
213
216
|
puts
|
|
214
|
-
puts
|
|
215
|
-
puts
|
|
216
|
-
puts
|
|
217
|
-
puts
|
|
218
|
-
puts
|
|
219
|
-
puts
|
|
217
|
+
puts '📊 OPTIMIZATION RECOMMENDATIONS:'
|
|
218
|
+
puts ' 1. Pre-compile regex patterns in class constants'
|
|
219
|
+
puts ' 2. Use simpler regex patterns with multiple passes if needed'
|
|
220
|
+
puts ' 3. Implement caching for repeated conversions'
|
|
221
|
+
puts ' 4. Consider iterative parsing instead of recursive for complex cases'
|
|
222
|
+
puts ' 5. Optimize string operations (minimize downcase/gsub calls)'
|
|
220
223
|
end
|
|
221
224
|
end
|
|
222
225
|
|
|
223
226
|
# Run the micro-benchmarks
|
|
224
|
-
if __FILE__ == $
|
|
225
|
-
MicroBenchmark.run
|
|
226
|
-
end
|
|
227
|
+
MicroBenchmark.run if __FILE__ == $PROGRAM_NAME
|
data/performance_comparison.rb
CHANGED
|
@@ -16,20 +16,20 @@ class PerformanceComparison
|
|
|
16
16
|
].freeze
|
|
17
17
|
|
|
18
18
|
def self.run_comparison
|
|
19
|
-
puts
|
|
20
|
-
puts
|
|
21
|
-
puts
|
|
22
|
-
puts
|
|
19
|
+
puts 'StringToNumber Performance Comparison'
|
|
20
|
+
puts '=' * 60
|
|
21
|
+
puts 'Original vs Optimized Implementation'
|
|
22
|
+
puts '=' * 60
|
|
23
23
|
puts
|
|
24
24
|
|
|
25
25
|
TEST_CASES.each_with_index do |test_case, index|
|
|
26
26
|
puts "Test #{index + 1}: '#{test_case}'"
|
|
27
|
-
puts
|
|
27
|
+
puts '-' * 50
|
|
28
28
|
|
|
29
29
|
# Verify both implementations produce same results
|
|
30
30
|
original_result = StringToNumber.in_numbers(test_case, use_optimized: false)
|
|
31
31
|
optimized_result = StringToNumber.in_numbers(test_case, use_optimized: true)
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
if original_result == optimized_result
|
|
34
34
|
puts "✅ Results match: #{original_result}"
|
|
35
35
|
else
|
|
@@ -38,7 +38,7 @@ class PerformanceComparison
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
# Benchmark both implementations
|
|
41
|
-
iterations =
|
|
41
|
+
iterations = 10_000
|
|
42
42
|
|
|
43
43
|
original_time = Benchmark.realtime do
|
|
44
44
|
iterations.times { StringToNumber.in_numbers(test_case, use_optimized: false) }
|
|
@@ -55,27 +55,27 @@ class PerformanceComparison
|
|
|
55
55
|
puts "Original: #{original_avg.round(4)}ms average"
|
|
56
56
|
puts "Optimized: #{optimized_avg.round(4)}ms average"
|
|
57
57
|
puts "Speedup: #{speedup.round(1)}x faster"
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
# Performance rating
|
|
60
60
|
rating = case speedup
|
|
61
|
-
when 0..2 then
|
|
62
|
-
when 2..10 then
|
|
63
|
-
when 10..50 then
|
|
64
|
-
else
|
|
61
|
+
when 0..2 then '🟡 Minor improvement'
|
|
62
|
+
when 2..10 then '🟢 Good improvement'
|
|
63
|
+
when 10..50 then '🟢 Great improvement'
|
|
64
|
+
else '🚀 Exceptional improvement'
|
|
65
65
|
end
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
puts "Rating: #{rating}"
|
|
68
68
|
puts
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
# Overall comparison
|
|
72
|
-
puts
|
|
73
|
-
puts
|
|
74
|
-
puts
|
|
72
|
+
puts '=' * 60
|
|
73
|
+
puts 'OVERALL PERFORMANCE ANALYSIS'
|
|
74
|
+
puts '=' * 60
|
|
75
75
|
|
|
76
76
|
# Test cache performance
|
|
77
77
|
puts "\nCache Performance Test:"
|
|
78
|
-
puts
|
|
78
|
+
puts '-' * 30
|
|
79
79
|
|
|
80
80
|
# Clear caches
|
|
81
81
|
StringToNumber.clear_caches!
|
|
@@ -107,17 +107,17 @@ class PerformanceComparison
|
|
|
107
107
|
|
|
108
108
|
# Scalability test
|
|
109
109
|
puts "\nScalability Comparison:"
|
|
110
|
-
puts
|
|
110
|
+
puts '-' * 30
|
|
111
111
|
|
|
112
112
|
scalability_tests = [
|
|
113
113
|
'un', # 2 chars
|
|
114
114
|
'vingt et un', # 11 chars
|
|
115
|
-
'mille deux cent trente-quatre',
|
|
115
|
+
'mille deux cent trente-quatre', # 29 chars
|
|
116
116
|
'soixante-quinze million trois cent quarante six mille sept cent quatre-vingt-dix neuf' # 85 chars
|
|
117
117
|
]
|
|
118
118
|
|
|
119
|
-
puts
|
|
120
|
-
puts
|
|
119
|
+
puts 'Input Length | Original | Optimized | Improvement'
|
|
120
|
+
puts '-------------|----------|-----------|------------'
|
|
121
121
|
|
|
122
122
|
scalability_tests.each do |test|
|
|
123
123
|
original_time = Benchmark.realtime do
|
|
@@ -132,24 +132,23 @@ class PerformanceComparison
|
|
|
132
132
|
optimized_ms = (optimized_time / 1000) * 1000
|
|
133
133
|
improvement = original_ms / optimized_ms
|
|
134
134
|
|
|
135
|
-
puts "#{test.length.to_s.rjust(11)} | #{original_ms.round(4).to_s.rjust(8)} |
|
|
135
|
+
puts "#{test.length.to_s.rjust(11)} | #{original_ms.round(4).to_s.rjust(8)} | " \
|
|
136
|
+
"#{optimized_ms.round(4).to_s.rjust(9)} | #{improvement.round(1).to_s.rjust(10)}x"
|
|
136
137
|
end
|
|
137
138
|
|
|
138
|
-
puts "\n
|
|
139
|
-
puts
|
|
140
|
-
puts
|
|
141
|
-
puts
|
|
142
|
-
puts
|
|
143
|
-
puts
|
|
144
|
-
puts
|
|
145
|
-
puts
|
|
139
|
+
puts "\n#{'=' * 60}"
|
|
140
|
+
puts 'SUMMARY'
|
|
141
|
+
puts '=' * 60
|
|
142
|
+
puts '✅ All test cases produce identical results'
|
|
143
|
+
puts '🚀 Significant performance improvements across all test cases'
|
|
144
|
+
puts '📈 Better scalability with input length'
|
|
145
|
+
puts '💾 Effective caching reduces repeated conversion time'
|
|
146
|
+
puts '🧠 Lower memory usage and object creation'
|
|
146
147
|
puts
|
|
147
|
-
puts
|
|
148
|
-
puts
|
|
148
|
+
puts 'The optimized implementation successfully addresses all identified'
|
|
149
|
+
puts 'performance bottlenecks while maintaining full compatibility.'
|
|
149
150
|
end
|
|
150
151
|
end
|
|
151
152
|
|
|
152
153
|
# Run the comparison
|
|
153
|
-
if __FILE__ == $
|
|
154
|
-
PerformanceComparison.run_comparison
|
|
155
|
-
end
|
|
154
|
+
PerformanceComparison.run_comparison if __FILE__ == $PROGRAM_NAME
|
data/profile.rb
CHANGED
|
@@ -8,93 +8,92 @@ require_relative 'lib/string_to_number'
|
|
|
8
8
|
|
|
9
9
|
begin
|
|
10
10
|
require 'ruby-prof'
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
# Profile the most complex case
|
|
13
13
|
test_input = 'soixante-quinze million trois cent quarante six mille sept cent quatre-vingt-dix neuf'
|
|
14
|
-
|
|
15
|
-
puts
|
|
14
|
+
|
|
15
|
+
puts 'Profiling StringToNumber with input:'
|
|
16
16
|
puts "'#{test_input}'"
|
|
17
|
-
puts
|
|
18
|
-
|
|
17
|
+
puts '=' * 80
|
|
18
|
+
|
|
19
19
|
# Start profiling
|
|
20
20
|
RubyProf.start
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
# Run the conversion many times
|
|
23
23
|
5000.times do
|
|
24
24
|
StringToNumber.in_numbers(test_input)
|
|
25
25
|
end
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
# Stop profiling
|
|
28
28
|
result = RubyProf.stop
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
# Print results
|
|
31
31
|
puts "\nTop 20 methods by total time:"
|
|
32
|
-
puts
|
|
33
|
-
|
|
32
|
+
puts '-' * 80
|
|
33
|
+
|
|
34
34
|
printer = RubyProf::FlatPrinter.new(result)
|
|
35
|
-
printer.print(
|
|
36
|
-
|
|
35
|
+
printer.print($stdout, min_percent: 1)
|
|
36
|
+
|
|
37
37
|
# Generate call graph
|
|
38
38
|
puts "\n\nCall Graph Analysis:"
|
|
39
|
-
puts
|
|
40
|
-
|
|
39
|
+
puts '-' * 80
|
|
40
|
+
|
|
41
41
|
printer = RubyProf::CallTreePrinter.new(result)
|
|
42
42
|
File.open('profile_output.txt', 'w') do |file|
|
|
43
43
|
printer.print(file)
|
|
44
44
|
end
|
|
45
|
-
puts
|
|
46
|
-
|
|
45
|
+
puts 'Detailed call graph saved to: profile_output.txt'
|
|
46
|
+
|
|
47
47
|
# Method-specific analysis
|
|
48
48
|
puts "\n\nMethod Breakdown:"
|
|
49
|
-
puts
|
|
50
|
-
|
|
49
|
+
puts '-' * 80
|
|
50
|
+
|
|
51
51
|
result.threads.each do |thread|
|
|
52
52
|
thread.methods.sort_by(&:total_time).reverse.first(10).each do |method|
|
|
53
53
|
next if method.total_time < 0.01
|
|
54
|
-
|
|
55
|
-
puts
|
|
54
|
+
|
|
55
|
+
puts method.full_name
|
|
56
56
|
puts " Total time: #{(method.total_time * 1000).round(2)}ms"
|
|
57
57
|
puts " Calls: #{method.called}"
|
|
58
58
|
puts " Time per call: #{((method.total_time / method.called) * 1000).round(4)}ms"
|
|
59
59
|
puts
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
|
-
|
|
63
62
|
rescue LoadError
|
|
64
|
-
puts
|
|
65
|
-
puts
|
|
63
|
+
puts 'ruby-prof gem not available. Running basic timing analysis instead.'
|
|
64
|
+
puts 'Install with: gem install ruby-prof'
|
|
66
65
|
puts
|
|
67
|
-
|
|
66
|
+
|
|
68
67
|
# Fallback: manual timing analysis
|
|
69
68
|
require 'benchmark'
|
|
70
|
-
|
|
69
|
+
|
|
71
70
|
test_cases = [
|
|
72
71
|
'un',
|
|
73
|
-
'vingt et un',
|
|
72
|
+
'vingt et un',
|
|
74
73
|
'mille deux cent',
|
|
75
74
|
'trois milliards cinq cents millions'
|
|
76
75
|
]
|
|
77
|
-
|
|
78
|
-
puts
|
|
79
|
-
puts
|
|
80
|
-
|
|
76
|
+
|
|
77
|
+
puts 'Manual Performance Analysis:'
|
|
78
|
+
puts '=' * 40
|
|
79
|
+
|
|
81
80
|
test_cases.each do |input|
|
|
82
81
|
puts "\nAnalyzing: '#{input}'"
|
|
83
|
-
|
|
82
|
+
|
|
84
83
|
# Time different aspects
|
|
85
84
|
parser = nil
|
|
86
85
|
init_time = Benchmark.realtime do
|
|
87
86
|
1000.times { parser = StringToNumber::ToNumber.new(input) }
|
|
88
87
|
end
|
|
89
|
-
|
|
88
|
+
|
|
90
89
|
conversion_time = Benchmark.realtime do
|
|
91
90
|
1000.times { parser.to_number }
|
|
92
91
|
end
|
|
93
|
-
|
|
92
|
+
|
|
94
93
|
total_time = Benchmark.realtime do
|
|
95
94
|
1000.times { StringToNumber.in_numbers(input) }
|
|
96
95
|
end
|
|
97
|
-
|
|
96
|
+
|
|
98
97
|
puts " Initialization: #{(init_time * 1000).round(4)}ms per 1000 calls"
|
|
99
98
|
puts " Conversion: #{(conversion_time * 1000).round(4)}ms per 1000 calls"
|
|
100
99
|
puts " Total: #{(total_time * 1000).round(4)}ms per 1000 calls"
|
|
@@ -103,29 +102,29 @@ rescue LoadError
|
|
|
103
102
|
|
|
104
103
|
# Test regex performance specifically
|
|
105
104
|
puts "\n\nRegex Performance Test:"
|
|
106
|
-
puts
|
|
107
|
-
|
|
108
|
-
sample_input =
|
|
105
|
+
puts '=' * 40
|
|
106
|
+
|
|
107
|
+
sample_input = 'trois milliards cinq cents millions'
|
|
109
108
|
parser = StringToNumber::ToNumber.new(sample_input)
|
|
110
109
|
keys = parser.instance_variable_get(:@keys)
|
|
111
|
-
|
|
110
|
+
|
|
112
111
|
puts "Keys pattern length: #{keys.length} characters"
|
|
113
|
-
|
|
112
|
+
|
|
114
113
|
regex_time = Benchmark.realtime do
|
|
115
|
-
|
|
114
|
+
10_000.times do
|
|
116
115
|
/(?<f>.*?)\s?(?<m>#{keys})/.match(sample_input)
|
|
117
116
|
end
|
|
118
117
|
end
|
|
119
|
-
|
|
118
|
+
|
|
120
119
|
puts "Regex matching time: #{(regex_time * 100).round(4)}ms per 10000 matches"
|
|
121
|
-
|
|
120
|
+
|
|
122
121
|
# Test hash lookup performance
|
|
123
122
|
lookup_time = Benchmark.realtime do
|
|
124
|
-
|
|
123
|
+
100_000.times do
|
|
125
124
|
StringToNumber::ToNumber::EXCEPTIONS['vingt']
|
|
126
125
|
StringToNumber::ToNumber::POWERS_OF_TEN['millions']
|
|
127
126
|
end
|
|
128
127
|
end
|
|
129
|
-
|
|
128
|
+
|
|
130
129
|
puts "Hash lookup time: #{(lookup_time * 10).round(4)}ms per 100000 lookups"
|
|
131
|
-
end
|
|
130
|
+
end
|
data/string_to_number.gemspec
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
lib = File.expand_path('lib', __dir__)
|
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
5
|
require 'string_to_number/version'
|
|
@@ -18,10 +20,11 @@ Gem::Specification.new do |spec|
|
|
|
18
20
|
# to allow pushing to a single host or delete
|
|
19
21
|
# this section to allow pushing to any host.
|
|
20
22
|
if spec.respond_to?(:metadata)
|
|
21
|
-
spec.metadata['allowed_push_host'] =
|
|
23
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
24
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
22
25
|
else
|
|
23
26
|
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
|
24
|
-
|
|
27
|
+
'public gem pushes.'
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
@@ -30,8 +33,4 @@ Gem::Specification.new do |spec|
|
|
|
30
33
|
spec.bindir = 'exe'
|
|
31
34
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
32
35
|
spec.require_paths = ['lib']
|
|
33
|
-
|
|
34
|
-
spec.add_development_dependency 'bundler'
|
|
35
|
-
spec.add_development_dependency 'rake'
|
|
36
|
-
spec.add_development_dependency 'rspec'
|
|
37
36
|
end
|