cataract 0.1.3 → 0.2.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-manual-rubies.yml +44 -0
- data/.overcommit.yml +1 -1
- data/.rubocop.yml +96 -4
- data/.rubocop_todo.yml +186 -0
- data/BENCHMARKS.md +62 -141
- data/CHANGELOG.md +20 -0
- data/RAGEL_MIGRATION.md +2 -2
- data/README.md +37 -4
- data/Rakefile +72 -32
- data/cataract.gemspec +4 -1
- data/ext/cataract/cataract.c +59 -50
- data/ext/cataract/cataract.h +5 -3
- data/ext/cataract/css_parser.c +173 -65
- data/ext/cataract/extconf.rb +2 -2
- data/ext/cataract/{merge.c → flatten.c} +526 -468
- data/ext/cataract/shorthand_expander.c +164 -115
- data/lib/cataract/at_rule.rb +8 -9
- data/lib/cataract/declaration.rb +18 -0
- data/lib/cataract/import_resolver.rb +63 -43
- data/lib/cataract/import_statement.rb +49 -0
- data/lib/cataract/pure/byte_constants.rb +69 -0
- data/lib/cataract/pure/flatten.rb +1145 -0
- data/lib/cataract/pure/helpers.rb +35 -0
- data/lib/cataract/pure/imports.rb +268 -0
- data/lib/cataract/pure/parser.rb +1340 -0
- data/lib/cataract/pure/serializer.rb +590 -0
- data/lib/cataract/pure/specificity.rb +206 -0
- data/lib/cataract/pure.rb +153 -0
- data/lib/cataract/rule.rb +69 -15
- data/lib/cataract/stylesheet.rb +356 -49
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +43 -26
- metadata +14 -26
- data/benchmarks/benchmark_harness.rb +0 -193
- data/benchmarks/benchmark_merging.rb +0 -121
- data/benchmarks/benchmark_optimization_comparison.rb +0 -168
- data/benchmarks/benchmark_parsing.rb +0 -153
- data/benchmarks/benchmark_ragel_removal.rb +0 -56
- data/benchmarks/benchmark_runner.rb +0 -70
- data/benchmarks/benchmark_serialization.rb +0 -180
- data/benchmarks/benchmark_shorthand.rb +0 -109
- data/benchmarks/benchmark_shorthand_expansion.rb +0 -176
- data/benchmarks/benchmark_specificity.rb +0 -124
- data/benchmarks/benchmark_string_allocation.rb +0 -151
- data/benchmarks/benchmark_stylesheet_to_s.rb +0 -62
- data/benchmarks/benchmark_to_s_cached.rb +0 -55
- data/benchmarks/benchmark_value_splitter.rb +0 -54
- data/benchmarks/benchmark_yjit.rb +0 -158
- data/benchmarks/benchmark_yjit_workers.rb +0 -61
- data/benchmarks/profile_to_s.rb +0 -23
- data/benchmarks/speedup_calculator.rb +0 -83
- data/benchmarks/system_metadata.rb +0 -81
- data/benchmarks/templates/benchmarks.md.erb +0 -221
- data/benchmarks/yjit_tests.rb +0 -141
- data/scripts/fuzzer/run.rb +0 -828
- data/scripts/fuzzer/worker.rb +0 -99
- data/scripts/generate_benchmarks_md.rb +0 -155
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'benchmark/ips'
|
|
4
|
-
require 'cataract'
|
|
5
|
-
|
|
6
|
-
# Load css_parser for comparison
|
|
7
|
-
begin
|
|
8
|
-
require 'css_parser'
|
|
9
|
-
CSS_PARSER_AVAILABLE = true
|
|
10
|
-
rescue LoadError
|
|
11
|
-
CSS_PARSER_AVAILABLE = false
|
|
12
|
-
puts 'Warning: css_parser gem not found. Install with: gem install css_parser'
|
|
13
|
-
puts 'Running Cataract-only benchmarks...'
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Test values
|
|
17
|
-
MARGIN_VALUES = [
|
|
18
|
-
'10px',
|
|
19
|
-
'10px 20px',
|
|
20
|
-
'10px 20px 30px',
|
|
21
|
-
'10px 20px 30px 40px',
|
|
22
|
-
'10px calc(100% - 20px)',
|
|
23
|
-
'10px !important'
|
|
24
|
-
].freeze
|
|
25
|
-
|
|
26
|
-
BORDER_VALUES = [
|
|
27
|
-
'1px solid red',
|
|
28
|
-
'2px dashed blue',
|
|
29
|
-
'thin dotted #000'
|
|
30
|
-
].freeze
|
|
31
|
-
|
|
32
|
-
FONT_VALUES = [
|
|
33
|
-
'12px Arial',
|
|
34
|
-
"bold 14px/1.5 'Helvetica Neue', sans-serif"
|
|
35
|
-
].freeze
|
|
36
|
-
|
|
37
|
-
puts "\n=== Shorthand Expansion Benchmark ==="
|
|
38
|
-
puts "Comparing Cataract (C) vs css_parser (Ruby)\n\n"
|
|
39
|
-
|
|
40
|
-
# Sanity check: verify both implementations produce same results
|
|
41
|
-
if CSS_PARSER_AVAILABLE
|
|
42
|
-
puts '--- Sanity Check: Comparing Outputs ---'
|
|
43
|
-
|
|
44
|
-
# Test margin expansion
|
|
45
|
-
cataract_result = Cataract._expand_margin('10px 20px 30px 40px')
|
|
46
|
-
css_parser_rs = CssParser::RuleSet.new(block: 'margin: 10px 20px 30px 40px')
|
|
47
|
-
css_parser_rs.expand_shorthand!
|
|
48
|
-
css_parser_result = {}
|
|
49
|
-
css_parser_rs.each_declaration { |prop, val, _| css_parser_result[prop] = val }
|
|
50
|
-
|
|
51
|
-
if cataract_result == css_parser_result
|
|
52
|
-
puts '✓ Margin expansion: MATCH'
|
|
53
|
-
else
|
|
54
|
-
puts '✗ Margin expansion: MISMATCH'
|
|
55
|
-
puts " Cataract: #{cataract_result.inspect}"
|
|
56
|
-
puts " css_parser: #{css_parser_result.inspect}"
|
|
57
|
-
exit 1
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Test border expansion
|
|
61
|
-
cataract_result = Cataract._expand_border('1px solid red')
|
|
62
|
-
css_parser_rs = CssParser::RuleSet.new(block: 'border: 1px solid red')
|
|
63
|
-
css_parser_rs.expand_shorthand!
|
|
64
|
-
css_parser_result = {}
|
|
65
|
-
css_parser_rs.each_declaration { |prop, val, _| css_parser_result[prop] = val }
|
|
66
|
-
|
|
67
|
-
if cataract_result == css_parser_result
|
|
68
|
-
puts '✓ Border expansion: MATCH'
|
|
69
|
-
else
|
|
70
|
-
puts '✗ Border expansion: MISMATCH'
|
|
71
|
-
puts " Cataract: #{cataract_result.inspect}"
|
|
72
|
-
puts " css_parser: #{css_parser_result.inspect}"
|
|
73
|
-
exit 1
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
puts "All sanity checks passed!\n\n"
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Margin expansion
|
|
80
|
-
puts '--- Margin Expansion (4 values) ---'
|
|
81
|
-
Benchmark.ips do |x|
|
|
82
|
-
x.config(time: 5, warmup: 2)
|
|
83
|
-
|
|
84
|
-
x.report('Cataract (C)') do
|
|
85
|
-
Cataract._expand_margin('10px 20px 30px 40px')
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
if CSS_PARSER_AVAILABLE
|
|
89
|
-
x.report('css_parser (Ruby)') do
|
|
90
|
-
rs = CssParser::RuleSet.new(block: 'margin: 10px 20px 30px 40px')
|
|
91
|
-
rs.expand_shorthand!
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
x.compare!
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Margin with calc()
|
|
99
|
-
puts "\n--- Margin with calc() ---"
|
|
100
|
-
Benchmark.ips do |x|
|
|
101
|
-
x.config(time: 5, warmup: 2)
|
|
102
|
-
|
|
103
|
-
x.report('Cataract (C)') do
|
|
104
|
-
Cataract._expand_margin('10px calc(100% - 20px)')
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
if CSS_PARSER_AVAILABLE
|
|
108
|
-
x.report('css_parser (Ruby)') do
|
|
109
|
-
rs = CssParser::RuleSet.new(block: 'margin: 10px calc(100% - 20px)')
|
|
110
|
-
rs.expand_shorthand!
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
x.compare!
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Margin with !important
|
|
118
|
-
puts "\n--- Margin with !important ---"
|
|
119
|
-
Benchmark.ips do |x|
|
|
120
|
-
x.config(time: 5, warmup: 2)
|
|
121
|
-
|
|
122
|
-
x.report('Cataract (C)') do
|
|
123
|
-
Cataract._expand_margin('10px !important')
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
if CSS_PARSER_AVAILABLE
|
|
127
|
-
x.report('css_parser (Ruby)') do
|
|
128
|
-
rs = CssParser::RuleSet.new(block: 'margin: 10px !important')
|
|
129
|
-
rs.expand_shorthand!
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
x.compare!
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Border expansion
|
|
137
|
-
puts "\n--- Border Expansion ---"
|
|
138
|
-
Benchmark.ips do |x|
|
|
139
|
-
x.config(time: 5, warmup: 2)
|
|
140
|
-
|
|
141
|
-
x.report('Cataract (C)') do
|
|
142
|
-
Cataract._expand_border('1px solid red')
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
if CSS_PARSER_AVAILABLE
|
|
146
|
-
x.report('css_parser (Ruby)') do
|
|
147
|
-
rs = CssParser::RuleSet.new(block: 'border: 1px solid red')
|
|
148
|
-
rs.expand_shorthand!
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
x.compare!
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Font expansion
|
|
156
|
-
puts "\n--- Font Expansion ---"
|
|
157
|
-
Benchmark.ips do |x|
|
|
158
|
-
x.config(time: 5, warmup: 2)
|
|
159
|
-
|
|
160
|
-
x.report('Cataract (C)') do
|
|
161
|
-
Cataract._expand_font("bold 14px/1.5 'Helvetica Neue', sans-serif")
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
if CSS_PARSER_AVAILABLE
|
|
165
|
-
x.report('css_parser (Ruby)') do
|
|
166
|
-
rs = CssParser::RuleSet.new(block: "font: bold 14px/1.5 'Helvetica Neue', sans-serif")
|
|
167
|
-
rs.expand_shorthand!
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
x.compare!
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
puts "\n=== Summary ==="
|
|
175
|
-
puts 'Cataract uses a C implementation with Ragel state machine for value splitting'
|
|
176
|
-
puts 'css_parser uses pure Ruby with regex-based parsing'
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'benchmark_harness'
|
|
4
|
-
|
|
5
|
-
# Load the local development version, not installed gem
|
|
6
|
-
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
7
|
-
require 'cataract'
|
|
8
|
-
|
|
9
|
-
# CSS Specificity Calculation Benchmark
|
|
10
|
-
class SpecificityBenchmark < BenchmarkHarness
|
|
11
|
-
def self.benchmark_name
|
|
12
|
-
'specificity'
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def self.description
|
|
16
|
-
'Time to calculate CSS selector specificity values'
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def self.metadata
|
|
20
|
-
{
|
|
21
|
-
'test_cases' => [
|
|
22
|
-
{
|
|
23
|
-
'name' => 'Simple Selectors',
|
|
24
|
-
'key' => 'simple',
|
|
25
|
-
'selectors' => { 'div' => 1, '.class' => 10, '#id' => 100 }
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
'name' => 'Compound Selectors',
|
|
29
|
-
'key' => 'compound',
|
|
30
|
-
'selectors' => { 'div.container' => 11, 'div#main' => 101, 'div.container#main' => 111 }
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
'name' => 'Combinators',
|
|
34
|
-
'key' => 'combinators',
|
|
35
|
-
'selectors' => { 'div p' => 2, 'div > p' => 2, 'h1 + p' => 2, 'div.container > p.intro' => 22 }
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
'name' => 'Pseudo-classes & Pseudo-elements',
|
|
39
|
-
'key' => 'pseudo',
|
|
40
|
-
'selectors' => { 'a:hover' => 11, 'p::before' => 2, 'li:first-child' => 11, 'p:first-child::before' => 12 }
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
'name' => ':not() Pseudo-class (CSS3)',
|
|
44
|
-
'key' => 'not',
|
|
45
|
-
'selectors' => { '#s12:not(foo)' => 101, 'div:not(.active)' => 11, '.button:not([disabled])' => 20 },
|
|
46
|
-
'note' => "css_parser has a bug - doesn't parse :not() content"
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
'name' => 'Complex Real-world Selectors',
|
|
50
|
-
'key' => 'complex',
|
|
51
|
-
'selectors' => {
|
|
52
|
-
'ul#nav li.active a:hover' => 122,
|
|
53
|
-
'div.wrapper > article#main > section.content > p:first-child' => 123,
|
|
54
|
-
"[data-theme='dark'] body.admin #dashboard .widget a[href^='http']::before" => 143
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
]
|
|
58
|
-
}
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def self.speedup_config
|
|
62
|
-
{
|
|
63
|
-
baseline_matcher: SpeedupCalculator::Matchers.css_parser,
|
|
64
|
-
comparison_matcher: SpeedupCalculator::Matchers.cataract,
|
|
65
|
-
test_case_key: :key
|
|
66
|
-
}
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def sanity_checks
|
|
70
|
-
require 'css_parser'
|
|
71
|
-
|
|
72
|
-
# Verify Cataract calculations
|
|
73
|
-
raise 'Cataract simple selector failed' unless Cataract.calculate_specificity('div') == 1
|
|
74
|
-
raise 'Cataract class selector failed' unless Cataract.calculate_specificity('.class') == 10
|
|
75
|
-
raise 'Cataract id selector failed' unless Cataract.calculate_specificity('#id') == 100
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def call
|
|
79
|
-
self.class.metadata['test_cases'].each do |test_case|
|
|
80
|
-
benchmark_category(test_case)
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
private
|
|
85
|
-
|
|
86
|
-
def benchmark_category(test_case)
|
|
87
|
-
puts '=' * 80
|
|
88
|
-
puts "TEST: #{test_case['name']}"
|
|
89
|
-
puts test_case['note'] if test_case['note']
|
|
90
|
-
puts '=' * 80
|
|
91
|
-
|
|
92
|
-
key = test_case['key']
|
|
93
|
-
selectors = test_case['selectors']
|
|
94
|
-
|
|
95
|
-
# Show individual selector examples in terminal output
|
|
96
|
-
puts 'Selectors tested:'
|
|
97
|
-
selectors.each do |selector, expected_specificity|
|
|
98
|
-
puts " #{selector} => #{expected_specificity}"
|
|
99
|
-
end
|
|
100
|
-
puts
|
|
101
|
-
|
|
102
|
-
benchmark(key) do |x|
|
|
103
|
-
x.config(time: 2, warmup: 1)
|
|
104
|
-
|
|
105
|
-
# Report aggregated results per test case for speedup calculation
|
|
106
|
-
x.report("css_parser: #{key}") do
|
|
107
|
-
selectors.each_key do |selector|
|
|
108
|
-
CssParser.calculate_specificity(selector)
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
x.report("cataract: #{key}") do
|
|
113
|
-
selectors.each_key do |selector|
|
|
114
|
-
Cataract.calculate_specificity(selector)
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
x.compare!
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Run if executed directly
|
|
124
|
-
SpecificityBenchmark.run if __FILE__ == $PROGRAM_NAME
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Benchmark string allocation optimization impact
|
|
5
|
-
# This compares parsing performance with rb_str_buf_new vs rb_str_new_cstr
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# 1. Run without optimization:
|
|
9
|
-
# rake compile && ruby test/benchmarks/benchmark_string_allocation.rb
|
|
10
|
-
#
|
|
11
|
-
# 2. Recompile with optimization:
|
|
12
|
-
# CFLAGS="-DUSE_STR_BUF_OPTIMIZATION" rake compile
|
|
13
|
-
#
|
|
14
|
-
# 3. Run with optimization:
|
|
15
|
-
# ruby test/benchmarks/benchmark_string_allocation.rb
|
|
16
|
-
#
|
|
17
|
-
# The benchmark will automatically detect which version is running and save
|
|
18
|
-
# results to a JSON file for comparison.
|
|
19
|
-
|
|
20
|
-
require 'benchmark/ips'
|
|
21
|
-
require 'fileutils'
|
|
22
|
-
|
|
23
|
-
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
24
|
-
require 'cataract'
|
|
25
|
-
|
|
26
|
-
# State files for benchmark-ips to compare across runs
|
|
27
|
-
# Store in hidden directory to keep them out of the way
|
|
28
|
-
# Use separate files for each test so we only compare like-to-like
|
|
29
|
-
RESULTS_DIR = File.expand_path('.benchmark_results', __dir__)
|
|
30
|
-
FileUtils.mkdir_p(RESULTS_DIR)
|
|
31
|
-
|
|
32
|
-
RESULTS_FILE_PARSE = File.join(RESULTS_DIR, 'string_allocation_parse.json')
|
|
33
|
-
RESULTS_FILE_ITERATE = File.join(RESULTS_DIR, 'string_allocation_iterate.json')
|
|
34
|
-
RESULTS_FILE_10X = File.join(RESULTS_DIR, 'string_allocation_10x.json')
|
|
35
|
-
|
|
36
|
-
# Large CSS fixture - using Bootstrap 5 CSS for realistic benchmark
|
|
37
|
-
LARGE_CSS_FIXTURE = File.read(File.expand_path('../test/fixtures/bootstrap.css', __dir__))
|
|
38
|
-
|
|
39
|
-
# Detect which version we're running by checking the compile-time constant
|
|
40
|
-
actual_mode = Cataract::STRING_ALLOC_MODE
|
|
41
|
-
# Label based on what's actually running (buffer is the default/production mode)
|
|
42
|
-
mode_label = actual_mode == :buffer ? 'buffer' : 'dynamic'
|
|
43
|
-
|
|
44
|
-
puts '=' * 80
|
|
45
|
-
puts 'String Allocation Optimization Benchmark'
|
|
46
|
-
puts '=' * 80
|
|
47
|
-
puts "Ruby version: #{RUBY_VERSION}"
|
|
48
|
-
puts "String allocation mode: #{actual_mode.inspect}"
|
|
49
|
-
if actual_mode == :buffer
|
|
50
|
-
puts ' → Using rb_str_buf_new (pre-allocated buffers, production default)'
|
|
51
|
-
else
|
|
52
|
-
puts ' → Using rb_str_new_cstr (dynamic allocation, disabled for comparison)'
|
|
53
|
-
end
|
|
54
|
-
puts '=' * 80
|
|
55
|
-
puts
|
|
56
|
-
puts 'This benchmark focuses on at-rules that build selector strings:'
|
|
57
|
-
puts ' - @font-face (large descriptor blocks)'
|
|
58
|
-
puts ' - @property (selector with prelude)'
|
|
59
|
-
puts ' - @keyframes (selector building)'
|
|
60
|
-
puts ' - @page (selector with pseudo)'
|
|
61
|
-
puts ' - @counter-style (selector with name)'
|
|
62
|
-
puts
|
|
63
|
-
puts "CSS fixture: #{LARGE_CSS_FIXTURE.lines.count} lines, #{LARGE_CSS_FIXTURE.bytesize} bytes"
|
|
64
|
-
puts '=' * 80
|
|
65
|
-
puts
|
|
66
|
-
|
|
67
|
-
parser = Cataract::Stylesheet.new
|
|
68
|
-
parser.add_block(LARGE_CSS_FIXTURE)
|
|
69
|
-
GC.start
|
|
70
|
-
# Verify we actually parsed everything
|
|
71
|
-
raise 'Parse failed' if parser.rules_count.zero?
|
|
72
|
-
|
|
73
|
-
puts "\n#{'=' * 80}"
|
|
74
|
-
puts 'TEST 1: Parse CSS with many at-rules'
|
|
75
|
-
puts '=' * 80
|
|
76
|
-
|
|
77
|
-
Benchmark.ips do |x|
|
|
78
|
-
x.config(time: 10, warmup: 2)
|
|
79
|
-
|
|
80
|
-
x.report(mode_label) do
|
|
81
|
-
parser = Cataract::Stylesheet.new
|
|
82
|
-
parser.add_block(LARGE_CSS_FIXTURE)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
x.save! RESULTS_FILE_PARSE
|
|
86
|
-
x.compare!
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
GC.start
|
|
90
|
-
|
|
91
|
-
puts "\n#{'=' * 80}"
|
|
92
|
-
puts 'TEST 2: Parse + iterate through all rules'
|
|
93
|
-
puts '=' * 80
|
|
94
|
-
|
|
95
|
-
Benchmark.ips do |x|
|
|
96
|
-
x.config(time: 10, warmup: 2)
|
|
97
|
-
|
|
98
|
-
x.report(mode_label) do
|
|
99
|
-
parser = Cataract::Stylesheet.new
|
|
100
|
-
parser.add_block(LARGE_CSS_FIXTURE)
|
|
101
|
-
|
|
102
|
-
count = 0
|
|
103
|
-
parser.select(&:selector?).each do |rule|
|
|
104
|
-
# Force string to be used
|
|
105
|
-
_ = rule.selector.length
|
|
106
|
-
_ = rule.declarations.to_s
|
|
107
|
-
count += 1
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
raise 'No rules found' if count.zero?
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
x.save! RESULTS_FILE_ITERATE
|
|
114
|
-
x.compare!
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
GC.start
|
|
118
|
-
|
|
119
|
-
puts "\n#{'=' * 80}"
|
|
120
|
-
puts 'TEST 3: Multiple parse operations (10x)'
|
|
121
|
-
puts '=' * 80
|
|
122
|
-
|
|
123
|
-
Benchmark.ips do |x|
|
|
124
|
-
x.config(time: 10, warmup: 2)
|
|
125
|
-
|
|
126
|
-
x.report(mode_label) do
|
|
127
|
-
10.times do
|
|
128
|
-
parser = Cataract::Stylesheet.new
|
|
129
|
-
parser.add_block(LARGE_CSS_FIXTURE)
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
x.save! RESULTS_FILE_10X
|
|
134
|
-
x.compare!
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
puts "\n#{'=' * 80}"
|
|
138
|
-
puts 'Results saved to:'
|
|
139
|
-
puts " - #{RESULTS_FILE_PARSE}"
|
|
140
|
-
puts " - #{RESULTS_FILE_ITERATE}"
|
|
141
|
-
puts " - #{RESULTS_FILE_10X}"
|
|
142
|
-
puts ''
|
|
143
|
-
puts 'To compare dynamic vs buffer (default):'
|
|
144
|
-
puts ' 1. Run with dynamic: DISABLE_STR_BUF_OPTIMIZATION=1 rake compile && ruby test/benchmarks/benchmark_string_allocation.rb'
|
|
145
|
-
puts ' 2. Run with buffer: rake compile && ruby test/benchmarks/benchmark_string_allocation.rb'
|
|
146
|
-
puts ' 3. Each test will automatically compare buffer vs dynamic'
|
|
147
|
-
puts ''
|
|
148
|
-
puts 'The benchmark verifies the compilation mode via Cataract::STRING_ALLOC_MODE'
|
|
149
|
-
puts ' :dynamic = rb_str_new_cstr (dynamic allocation)'
|
|
150
|
-
puts ' :buffer = rb_str_buf_new (pre-allocated, production default)'
|
|
151
|
-
puts '=' * 80
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'benchmark/ips'
|
|
4
|
-
|
|
5
|
-
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
6
|
-
require 'cataract'
|
|
7
|
-
|
|
8
|
-
puts '=' * 60
|
|
9
|
-
puts 'Stylesheet#to_s: Ruby vs C Implementation'
|
|
10
|
-
puts '=' * 60
|
|
11
|
-
puts ''
|
|
12
|
-
|
|
13
|
-
bootstrap_css = File.read('test/fixtures/bootstrap.css')
|
|
14
|
-
stylesheet = Cataract.parse_css(bootstrap_css)
|
|
15
|
-
|
|
16
|
-
puts "Parsing bootstrap.css: #{stylesheet.size} rules"
|
|
17
|
-
puts ''
|
|
18
|
-
|
|
19
|
-
# Verify both versions produce same output
|
|
20
|
-
ruby_output = stylesheet.to_s
|
|
21
|
-
c_output = Cataract._stylesheet_to_s_c(stylesheet.rules, stylesheet.charset)
|
|
22
|
-
|
|
23
|
-
if ruby_output == c_output
|
|
24
|
-
puts '✓ Ruby and C versions produce identical output'
|
|
25
|
-
else
|
|
26
|
-
puts '✗ WARNING: Ruby and C versions produce different output!'
|
|
27
|
-
puts "Ruby length: #{ruby_output.length}"
|
|
28
|
-
puts "C length: #{c_output.length}"
|
|
29
|
-
|
|
30
|
-
# Find first difference
|
|
31
|
-
ruby_output.chars.each_with_index do |char, i|
|
|
32
|
-
next unless char != c_output[i]
|
|
33
|
-
|
|
34
|
-
puts "First difference at position #{i}:"
|
|
35
|
-
puts " Ruby: #{ruby_output[(i - 20)..(i + 20)].inspect}"
|
|
36
|
-
puts " C: #{c_output[(i - 20)..(i + 20)].inspect}"
|
|
37
|
-
break
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
puts ''
|
|
42
|
-
puts 'Benchmarking to_s only (cache cleared each iteration)...'
|
|
43
|
-
puts ''
|
|
44
|
-
|
|
45
|
-
# Parse once outside benchmark
|
|
46
|
-
PARSER = Cataract.parse_css(bootstrap_css)
|
|
47
|
-
|
|
48
|
-
Benchmark.ips do |x|
|
|
49
|
-
x.config(time: 10, warmup: 3)
|
|
50
|
-
|
|
51
|
-
x.report('Ruby (serialize_to_css)') do
|
|
52
|
-
PARSER.instance_variable_set(:@serialized, nil) # Clear cache
|
|
53
|
-
PARSER.to_s
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
x.report('C (_stylesheet_to_s_c)') do
|
|
57
|
-
PARSER.instance_variable_set(:@serialized, nil) # Clear cache (apples-to-apples)
|
|
58
|
-
Cataract._stylesheet_to_s_c(PARSER.rules, PARSER.charset)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
x.compare!
|
|
62
|
-
end
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'benchmark/ips'
|
|
4
|
-
|
|
5
|
-
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
6
|
-
require 'cataract'
|
|
7
|
-
|
|
8
|
-
puts '=' * 60
|
|
9
|
-
puts 'CACHED to_s BENCHMARK'
|
|
10
|
-
puts '=' * 60
|
|
11
|
-
puts ''
|
|
12
|
-
|
|
13
|
-
bootstrap_css = File.read('test/fixtures/bootstrap.css')
|
|
14
|
-
|
|
15
|
-
puts 'Comparing:'
|
|
16
|
-
puts ' - Fresh parse → to_s (first call, uncached)'
|
|
17
|
-
puts ' - Repeated to_s (cached, should be instant)'
|
|
18
|
-
puts ''
|
|
19
|
-
|
|
20
|
-
Benchmark.ips do |x|
|
|
21
|
-
x.config(time: 10, warmup: 3)
|
|
22
|
-
|
|
23
|
-
x.report('parse + to_s (uncached)') do
|
|
24
|
-
stylesheet = Cataract.parse_css(bootstrap_css)
|
|
25
|
-
stylesheet.to_s
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
x.report('to_s (cached)') do
|
|
29
|
-
# Parse once outside the benchmark
|
|
30
|
-
stylesheet = Cataract.parse_css(bootstrap_css)
|
|
31
|
-
stylesheet.to_s # Prime cache
|
|
32
|
-
|
|
33
|
-
# Now benchmark cached access
|
|
34
|
-
stylesheet.to_s
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
x.compare!
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
puts ''
|
|
41
|
-
puts '=' * 60
|
|
42
|
-
puts 'Real-world scenario: Multiple to_s calls'
|
|
43
|
-
puts '=' * 60
|
|
44
|
-
puts ''
|
|
45
|
-
|
|
46
|
-
stylesheet = Cataract.parse_css(bootstrap_css)
|
|
47
|
-
|
|
48
|
-
puts 'Calling to_s 10 times...'
|
|
49
|
-
require 'benchmark'
|
|
50
|
-
time = Benchmark.measure do
|
|
51
|
-
10.times { stylesheet.to_s }
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
puts "Total time: #{time.real.round(4)}s"
|
|
55
|
-
puts 'First call does all work, next 9 are free!'
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'benchmark/ips'
|
|
4
|
-
|
|
5
|
-
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
6
|
-
require 'cataract'
|
|
7
|
-
|
|
8
|
-
# Get current git branch
|
|
9
|
-
branch = `git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
|
|
10
|
-
branch = 'unknown' if branch.empty?
|
|
11
|
-
|
|
12
|
-
puts '=' * 60
|
|
13
|
-
puts "Value Splitter Benchmark: #{branch}"
|
|
14
|
-
puts '=' * 60
|
|
15
|
-
puts ''
|
|
16
|
-
|
|
17
|
-
# Test cases covering different scenarios
|
|
18
|
-
test_cases = {
|
|
19
|
-
'simple' => '1px 2px 3px 4px',
|
|
20
|
-
'functions' => '10px calc(100% - 20px) 5px',
|
|
21
|
-
'rgb' => 'rgb(255, 0, 0) blue rgba(0, 0, 0, 0.5)',
|
|
22
|
-
'quotes' => "'Helvetica Neue', Arial, sans-serif",
|
|
23
|
-
'complex' => "10px calc(100% - 20px) 'Font Name' rgb(255, 0, 0)",
|
|
24
|
-
'long' => '1px 2px 3px 4px 5px 6px 7px 8px 9px 10px 11px 12px 13px 14px 15px'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
puts 'Test cases:'
|
|
28
|
-
test_cases.each do |name, value|
|
|
29
|
-
result = Cataract.split_value(value)
|
|
30
|
-
puts " #{name}: '#{value}' => #{result.length} tokens"
|
|
31
|
-
end
|
|
32
|
-
puts ''
|
|
33
|
-
|
|
34
|
-
Benchmark.ips do |x|
|
|
35
|
-
x.config(time: 10, warmup: 3)
|
|
36
|
-
|
|
37
|
-
test_cases.each do |name, value|
|
|
38
|
-
x.report("#{branch}:#{name.ljust(12)}") do
|
|
39
|
-
Cataract.split_value(value)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
x.compare!
|
|
44
|
-
|
|
45
|
-
# Save results to file for cross-branch comparison
|
|
46
|
-
x.save! 'test/.benchmark_results/value_splitter.json'
|
|
47
|
-
x.hold! 'test/.benchmark_results/value_splitter.json'
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
puts ''
|
|
51
|
-
puts '=' * 60
|
|
52
|
-
puts 'Results saved to test/.benchmark_results/value_splitter.json'
|
|
53
|
-
puts 'Switch git branches and run again to compare!'
|
|
54
|
-
puts '=' * 60
|