toon-format 0.1.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/.ruby-version +1 -0
- data/CHANGELOG.md +71 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +138 -0
- data/LICENSE.txt +21 -0
- data/README.md +242 -0
- data/Rakefile +12 -0
- data/benchmark/README.md +206 -0
- data/benchmark/csv_vs_toon_benchmark.rb +71 -0
- data/benchmark/decode_benchmark.rb +63 -0
- data/benchmark/encode_benchmark.rb +82 -0
- data/benchmark/format_comparison_benchmark.rb +161 -0
- data/benchmark/memory_benchmark.rb +97 -0
- data/benchmark/nesting_benchmark.rb +220 -0
- data/benchmark/real_world_benchmark.rb +230 -0
- data/benchmark/round_trip_benchmark.rb +201 -0
- data/benchmark/run_all_benchmarks.rb +165 -0
- data/benchmark/scalability_benchmark.rb +124 -0
- data/benchmark/token_reduction_benchmark.rb +104 -0
- data/benchmark/validation_benchmark.rb +124 -0
- data/exe/toon-format +155 -0
- data/lib/toon_format/decoder.rb +36 -0
- data/lib/toon_format/encoder.rb +221 -0
- data/lib/toon_format/errors.rb +36 -0
- data/lib/toon_format/parser.rb +269 -0
- data/lib/toon_format/rails/extensions.rb +16 -0
- data/lib/toon_format/railtie.rb +15 -0
- data/lib/toon_format/validator.rb +68 -0
- data/lib/toon_format/version.rb +5 -0
- data/lib/toon_format.rb +73 -0
- data/sig/toon/format.rbs +6 -0
- metadata +76 -0
data/benchmark/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# TOON Format Benchmarks
|
|
2
|
+
|
|
3
|
+
Comprehensive performance benchmarks for the TOON Format Ruby gem.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
Run all benchmarks:
|
|
8
|
+
```bash
|
|
9
|
+
ruby benchmark/run_all_benchmarks.rb
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Run individual benchmarks:
|
|
13
|
+
```bash
|
|
14
|
+
# Basic performance
|
|
15
|
+
ruby benchmark/encode_benchmark.rb
|
|
16
|
+
ruby benchmark/decode_benchmark.rb
|
|
17
|
+
|
|
18
|
+
# Analysis
|
|
19
|
+
ruby benchmark/token_reduction_benchmark.rb
|
|
20
|
+
ruby benchmark/scalability_benchmark.rb
|
|
21
|
+
|
|
22
|
+
# Comparisons
|
|
23
|
+
ruby benchmark/format_comparison_benchmark.rb
|
|
24
|
+
ruby benchmark/csv_vs_toon_benchmark.rb
|
|
25
|
+
|
|
26
|
+
# Advanced tests
|
|
27
|
+
ruby benchmark/validation_benchmark.rb
|
|
28
|
+
ruby benchmark/nesting_benchmark.rb
|
|
29
|
+
ruby benchmark/round_trip_benchmark.rb
|
|
30
|
+
ruby benchmark/memory_benchmark.rb
|
|
31
|
+
ruby benchmark/real_world_benchmark.rb
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Benchmark Categories
|
|
35
|
+
|
|
36
|
+
### 1. **Basic Performance**
|
|
37
|
+
- **encode_benchmark.rb** - Basic encoding speed tests
|
|
38
|
+
- **decode_benchmark.rb** - Basic decoding speed tests
|
|
39
|
+
|
|
40
|
+
Tests fundamental encode/decode operations with simple, tabular, and nested data.
|
|
41
|
+
|
|
42
|
+
### 2. **Token Reduction**
|
|
43
|
+
- **token_reduction_benchmark.rb** - Token savings analysis
|
|
44
|
+
|
|
45
|
+
Measures the core value proposition: how much TOON reduces token usage vs JSON for LLM contexts.
|
|
46
|
+
|
|
47
|
+
### 3. **Scalability**
|
|
48
|
+
- **scalability_benchmark.rb** - Performance across data sizes
|
|
49
|
+
|
|
50
|
+
Tests with datasets from 1 to 10,000 records to show how performance scales.
|
|
51
|
+
|
|
52
|
+
### 4. **Format Comparisons**
|
|
53
|
+
- **format_comparison_benchmark.rb** - vs JSON, YAML, MessagePack
|
|
54
|
+
- **csv_vs_toon_benchmark.rb** - vs CSV format
|
|
55
|
+
|
|
56
|
+
Compares TOON against other serialization formats for encoding/decoding speed, size, and readability.
|
|
57
|
+
|
|
58
|
+
### 5. **Real-World Scenarios**
|
|
59
|
+
- **real_world_benchmark.rb** - Practical use cases
|
|
60
|
+
|
|
61
|
+
Tests realistic scenarios:
|
|
62
|
+
- REST API responses
|
|
63
|
+
- Database exports
|
|
64
|
+
- LLM prompt contexts
|
|
65
|
+
- Analytics events
|
|
66
|
+
- Application configuration
|
|
67
|
+
|
|
68
|
+
### 6. **Advanced Testing**
|
|
69
|
+
- **validation_benchmark.rb** - Strict vs lenient mode overhead
|
|
70
|
+
- **nesting_benchmark.rb** - Deep nesting performance
|
|
71
|
+
- **round_trip_benchmark.rb** - Encode → decode fidelity
|
|
72
|
+
- **memory_benchmark.rb** - Memory usage profiling
|
|
73
|
+
|
|
74
|
+
## Requirements
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
# Gemfile
|
|
78
|
+
gem 'benchmark-ips' # For performance testing
|
|
79
|
+
gem 'msgpack' # Optional, for format comparison
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Install dependencies:
|
|
83
|
+
```bash
|
|
84
|
+
bundle install
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Understanding Results
|
|
88
|
+
|
|
89
|
+
### Benchmark-IPS Output
|
|
90
|
+
```
|
|
91
|
+
TOON encode: 50000 i/s
|
|
92
|
+
JSON encode: 25000 i/s
|
|
93
|
+
```
|
|
94
|
+
Higher is better. "i/s" = iterations per second.
|
|
95
|
+
|
|
96
|
+
### Comparison Output
|
|
97
|
+
```
|
|
98
|
+
Comparison:
|
|
99
|
+
TOON encode: 50000.0 i/s
|
|
100
|
+
JSON encode: 25000.0 i/s - 2.00x slower
|
|
101
|
+
```
|
|
102
|
+
TOON is 2x faster than JSON in this example.
|
|
103
|
+
|
|
104
|
+
### Size Comparison
|
|
105
|
+
```
|
|
106
|
+
JSON: 1000 bytes
|
|
107
|
+
TOON: 650 bytes
|
|
108
|
+
Savings: 35.0%
|
|
109
|
+
```
|
|
110
|
+
Negative percentages mean TOON is larger (rare, usually for small objects).
|
|
111
|
+
|
|
112
|
+
## Expected Results
|
|
113
|
+
|
|
114
|
+
Based on typical runs:
|
|
115
|
+
|
|
116
|
+
| Scenario | Encoding Speed | Decoding Speed | Size Savings |
|
|
117
|
+
|----------|---------------|----------------|--------------|
|
|
118
|
+
| Small objects | 1-2x faster | Similar | 10-30% |
|
|
119
|
+
| Tabular arrays | 2-3x faster | 1.5-2x faster | 30-60% |
|
|
120
|
+
| Nested objects | Similar | Similar | 20-40% |
|
|
121
|
+
| Large datasets | 1.5-2x faster | 1-1.5x faster | 40-70% |
|
|
122
|
+
|
|
123
|
+
**Note**: Results vary by Ruby version, CPU, and data characteristics.
|
|
124
|
+
|
|
125
|
+
## Interpreting Performance
|
|
126
|
+
|
|
127
|
+
### When TOON Excels
|
|
128
|
+
- ✅ **Tabular data** (uniform arrays of hashes)
|
|
129
|
+
- ✅ **Large datasets** (> 100 records)
|
|
130
|
+
- ✅ **Repeated field names** (database results)
|
|
131
|
+
- ✅ **API responses** (consistent structure)
|
|
132
|
+
|
|
133
|
+
### When TOON is Similar to JSON
|
|
134
|
+
- 🟡 **Small objects** (< 10 fields)
|
|
135
|
+
- 🟡 **Highly irregular data** (varying structures)
|
|
136
|
+
- 🟡 **Deep nesting** (> 10 levels)
|
|
137
|
+
|
|
138
|
+
### Key Metrics
|
|
139
|
+
|
|
140
|
+
1. **Token Reduction**: Most important for LLM contexts
|
|
141
|
+
- Directly reduces API costs
|
|
142
|
+
- Smaller prompts = faster processing
|
|
143
|
+
|
|
144
|
+
2. **Encoding Speed**: Important for API responses
|
|
145
|
+
- Faster = lower server latency
|
|
146
|
+
- Scales with request volume
|
|
147
|
+
|
|
148
|
+
3. **Decoding Speed**: Important for data ingestion
|
|
149
|
+
- Critical for high-throughput pipelines
|
|
150
|
+
|
|
151
|
+
4. **Memory Usage**: Important for large datasets
|
|
152
|
+
- Lower = more scalable
|
|
153
|
+
|
|
154
|
+
## Custom Benchmarks
|
|
155
|
+
|
|
156
|
+
Create your own benchmark:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
#!/usr/bin/env ruby
|
|
160
|
+
require "bundler/setup"
|
|
161
|
+
require "benchmark/ips"
|
|
162
|
+
require "toon_format"
|
|
163
|
+
require "json"
|
|
164
|
+
|
|
165
|
+
# Your data
|
|
166
|
+
data = { your: "data" }
|
|
167
|
+
|
|
168
|
+
Benchmark.ips do |x|
|
|
169
|
+
x.report("JSON") { JSON.generate(data) }
|
|
170
|
+
x.report("TOON") { ToonFormat.encode(data) }
|
|
171
|
+
x.compare!
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Contributing
|
|
176
|
+
|
|
177
|
+
When adding benchmarks:
|
|
178
|
+
1. Use `benchmark/ips` for speed tests
|
|
179
|
+
2. Include size comparisons
|
|
180
|
+
3. Test with realistic data
|
|
181
|
+
4. Add to `run_all_benchmarks.rb`
|
|
182
|
+
5. Document in this README
|
|
183
|
+
|
|
184
|
+
## Results Storage
|
|
185
|
+
|
|
186
|
+
Benchmark results are saved to `benchmark/results/` with timestamps:
|
|
187
|
+
```
|
|
188
|
+
benchmark/results/summary_20250126_143022.txt
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This allows tracking performance changes over time.
|
|
192
|
+
|
|
193
|
+
## CI/CD Integration
|
|
194
|
+
|
|
195
|
+
Run benchmarks in CI:
|
|
196
|
+
```yaml
|
|
197
|
+
# .github/workflows/benchmark.yml
|
|
198
|
+
- name: Run benchmarks
|
|
199
|
+
run: ruby benchmark/run_all_benchmarks.rb
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Questions?
|
|
203
|
+
|
|
204
|
+
- Check [main README](../README.md) for usage
|
|
205
|
+
- See [CLAUDE.md](../CLAUDE.md) for architecture
|
|
206
|
+
- Open an issue for benchmark requests
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "toon_format"
|
|
6
|
+
require "json"
|
|
7
|
+
require "csv"
|
|
8
|
+
|
|
9
|
+
puts "=" * 80
|
|
10
|
+
puts "TOON vs. CSV Token Comparison"
|
|
11
|
+
puts "=" * 80
|
|
12
|
+
puts
|
|
13
|
+
|
|
14
|
+
# Data structure for testing
|
|
15
|
+
data = Array.new(100) do |i|
|
|
16
|
+
{
|
|
17
|
+
id: i + 1,
|
|
18
|
+
name: "User \#{i + 1}",
|
|
19
|
+
email: "user\#{i + 1}@example.com",
|
|
20
|
+
role: i.even? ? "admin" : "user",
|
|
21
|
+
active: true
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Convert to JSON
|
|
26
|
+
json_string = JSON.pretty_generate(data)
|
|
27
|
+
json_tokens = json_string.length
|
|
28
|
+
|
|
29
|
+
# Convert to TOON
|
|
30
|
+
toon_string = ToonFormat.encode(data)
|
|
31
|
+
toon_tokens = toon_string.length
|
|
32
|
+
|
|
33
|
+
# Convert to CSV
|
|
34
|
+
csv_string = CSV.generate do |csv|
|
|
35
|
+
csv << data.first.keys
|
|
36
|
+
data.each do |row|
|
|
37
|
+
csv << row.values
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
csv_tokens = csv_string.length
|
|
41
|
+
|
|
42
|
+
# Output results
|
|
43
|
+
puts "Comparison for 100 User Records"
|
|
44
|
+
puts "--------------------------------------------------------------------------------"
|
|
45
|
+
|
|
46
|
+
puts "JSON:"
|
|
47
|
+
puts " Size: #{json_tokens} bytes"
|
|
48
|
+
puts " Tokens: ~#{json_tokens}"
|
|
49
|
+
puts
|
|
50
|
+
|
|
51
|
+
puts "TOON:"
|
|
52
|
+
puts " Size: #{toon_tokens} bytes"
|
|
53
|
+
puts " Tokens: ~#{toon_tokens}"
|
|
54
|
+
puts
|
|
55
|
+
|
|
56
|
+
puts "CSV:"
|
|
57
|
+
puts " Size: #{csv_tokens} bytes"
|
|
58
|
+
puts " Tokens: ~#{csv_tokens}"
|
|
59
|
+
puts
|
|
60
|
+
|
|
61
|
+
puts "Savings:"
|
|
62
|
+
json_minus_toon = json_tokens - toon_tokens
|
|
63
|
+
toon_savings_percent = (json_minus_toon / json_tokens.to_f * 100).round(1)
|
|
64
|
+
puts " TOON vs. JSON: #{json_minus_toon} bytes (#{toon_savings_percent}%)"
|
|
65
|
+
|
|
66
|
+
json_minus_csv = json_tokens - csv_tokens
|
|
67
|
+
csv_savings_percent = (json_minus_csv / json_tokens.to_f * 100).round(1)
|
|
68
|
+
puts " CSV vs. JSON: #{json_minus_csv} bytes (#{csv_savings_percent}%)"
|
|
69
|
+
puts
|
|
70
|
+
|
|
71
|
+
puts "================================================================================"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "benchmark/ips"
|
|
6
|
+
require "toon_format"
|
|
7
|
+
require "json"
|
|
8
|
+
|
|
9
|
+
puts "=" * 80
|
|
10
|
+
puts "TOON Format Decoding Benchmark"
|
|
11
|
+
puts "=" * 80
|
|
12
|
+
puts
|
|
13
|
+
|
|
14
|
+
# Test data sets
|
|
15
|
+
simple_object = { name: "Alice", age: 30, email: "alice@example.com" }
|
|
16
|
+
simple_json = JSON.generate(simple_object)
|
|
17
|
+
simple_toon = ToonFormat.encode(simple_object)
|
|
18
|
+
|
|
19
|
+
tabular_data = Array.new(100) do |i|
|
|
20
|
+
{ id: i, name: "User#{i}", email: "user#{i}@example.com", active: i.even? }
|
|
21
|
+
end
|
|
22
|
+
tabular_json = JSON.generate(tabular_data)
|
|
23
|
+
tabular_toon = ToonFormat.encode(tabular_data)
|
|
24
|
+
|
|
25
|
+
nested_object = {
|
|
26
|
+
user: {
|
|
27
|
+
id: 1,
|
|
28
|
+
name: "Alice",
|
|
29
|
+
profile: {
|
|
30
|
+
age: 30,
|
|
31
|
+
city: "NYC"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
nested_json = JSON.generate(nested_object)
|
|
36
|
+
nested_toon = ToonFormat.encode(nested_object)
|
|
37
|
+
|
|
38
|
+
puts "Benchmark 1: Simple Object"
|
|
39
|
+
puts "-" * 80
|
|
40
|
+
Benchmark.ips do |x|
|
|
41
|
+
x.report("JSON.parse") { JSON.parse(simple_json) }
|
|
42
|
+
x.report("ToonFormat.decode") { ToonFormat.decode(simple_toon) }
|
|
43
|
+
x.compare!
|
|
44
|
+
end
|
|
45
|
+
puts
|
|
46
|
+
|
|
47
|
+
puts "Benchmark 2: Tabular Data (100 records)"
|
|
48
|
+
puts "-" * 80
|
|
49
|
+
Benchmark.ips do |x|
|
|
50
|
+
x.report("JSON.parse") { JSON.parse(tabular_json) }
|
|
51
|
+
x.report("ToonFormat.decode") { ToonFormat.decode(tabular_toon) }
|
|
52
|
+
x.compare!
|
|
53
|
+
end
|
|
54
|
+
puts
|
|
55
|
+
|
|
56
|
+
puts "Benchmark 3: Nested Object"
|
|
57
|
+
puts "-" * 80
|
|
58
|
+
Benchmark.ips do |x|
|
|
59
|
+
x.report("JSON.parse") { JSON.parse(nested_json) }
|
|
60
|
+
x.report("ToonFormat.decode") { ToonFormat.decode(nested_toon) }
|
|
61
|
+
x.compare!
|
|
62
|
+
end
|
|
63
|
+
puts
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "benchmark/ips"
|
|
6
|
+
require "toon_format"
|
|
7
|
+
require "json"
|
|
8
|
+
|
|
9
|
+
puts "=" * 80
|
|
10
|
+
puts "TOON Format Encoding Benchmark"
|
|
11
|
+
puts "=" * 80
|
|
12
|
+
puts
|
|
13
|
+
|
|
14
|
+
# Test data sets
|
|
15
|
+
simple_object = { name: "Alice", age: 30, email: "alice@example.com" }
|
|
16
|
+
|
|
17
|
+
tabular_data = Array.new(100) do |i|
|
|
18
|
+
{ id: i, name: "User#{i}", email: "user#{i}@example.com", active: i.even? }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
nested_object = {
|
|
22
|
+
user: {
|
|
23
|
+
id: 1,
|
|
24
|
+
name: "Alice",
|
|
25
|
+
profile: {
|
|
26
|
+
age: 30,
|
|
27
|
+
city: "NYC",
|
|
28
|
+
interests: %w[ruby python javascript]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
metadata: {
|
|
32
|
+
created_at: "2025-01-01",
|
|
33
|
+
updated_at: "2025-01-15"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
puts "Benchmark 1: Simple Object (#{simple_object.size} fields)"
|
|
38
|
+
puts "-" * 80
|
|
39
|
+
Benchmark.ips do |x|
|
|
40
|
+
x.report("JSON.generate") { JSON.generate(simple_object) }
|
|
41
|
+
x.report("ToonFormat.encode") { ToonFormat.encode(simple_object) }
|
|
42
|
+
x.compare!
|
|
43
|
+
end
|
|
44
|
+
puts
|
|
45
|
+
|
|
46
|
+
puts "Benchmark 2: Tabular Data (#{tabular_data.size} records)"
|
|
47
|
+
puts "-" * 80
|
|
48
|
+
Benchmark.ips do |x|
|
|
49
|
+
x.report("JSON.generate") { JSON.generate(tabular_data) }
|
|
50
|
+
x.report("ToonFormat.encode") { ToonFormat.encode(tabular_data) }
|
|
51
|
+
x.compare!
|
|
52
|
+
end
|
|
53
|
+
puts
|
|
54
|
+
|
|
55
|
+
puts "Benchmark 3: Nested Object"
|
|
56
|
+
puts "-" * 80
|
|
57
|
+
Benchmark.ips do |x|
|
|
58
|
+
x.report("JSON.generate") { JSON.generate(nested_object) }
|
|
59
|
+
x.report("ToonFormat.encode") { ToonFormat.encode(nested_object) }
|
|
60
|
+
x.compare!
|
|
61
|
+
end
|
|
62
|
+
puts
|
|
63
|
+
|
|
64
|
+
puts "=" * 80
|
|
65
|
+
puts "Size Comparison"
|
|
66
|
+
puts "=" * 80
|
|
67
|
+
|
|
68
|
+
[
|
|
69
|
+
["Simple Object", simple_object],
|
|
70
|
+
["Tabular Data", tabular_data],
|
|
71
|
+
["Nested Object", nested_object]
|
|
72
|
+
].each do |name, data|
|
|
73
|
+
json_size = JSON.generate(data).bytesize
|
|
74
|
+
toon_size = ToonFormat.encode(data).bytesize
|
|
75
|
+
savings = ((json_size - toon_size) / json_size.to_f * 100).round(1)
|
|
76
|
+
|
|
77
|
+
puts "#{name}:"
|
|
78
|
+
puts " JSON: #{json_size} bytes"
|
|
79
|
+
puts " TOON: #{toon_size} bytes"
|
|
80
|
+
puts " Savings: #{savings}%"
|
|
81
|
+
puts
|
|
82
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "benchmark/ips"
|
|
6
|
+
require "toon_format"
|
|
7
|
+
require "json"
|
|
8
|
+
require "yaml"
|
|
9
|
+
|
|
10
|
+
# Try to load MessagePack if available
|
|
11
|
+
begin
|
|
12
|
+
require "msgpack"
|
|
13
|
+
MSGPACK_AVAILABLE = true
|
|
14
|
+
rescue LoadError
|
|
15
|
+
MSGPACK_AVAILABLE = false
|
|
16
|
+
puts "Note: MessagePack not available. Install with: gem install msgpack"
|
|
17
|
+
puts
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
puts "=" * 80
|
|
21
|
+
puts "Format Comparison Benchmark"
|
|
22
|
+
puts "Comparing TOON with JSON, YAML#{MSGPACK_AVAILABLE ? ', and MessagePack' : ''}"
|
|
23
|
+
puts "=" * 80
|
|
24
|
+
puts
|
|
25
|
+
|
|
26
|
+
# Test datasets
|
|
27
|
+
datasets = {
|
|
28
|
+
"Small Object" => {
|
|
29
|
+
id: 1,
|
|
30
|
+
name: "Alice Smith",
|
|
31
|
+
email: "alice@example.com",
|
|
32
|
+
active: true
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
"Tabular Data (100 records)" => Array.new(100) do |i|
|
|
36
|
+
{
|
|
37
|
+
id: i,
|
|
38
|
+
name: "User#{i}",
|
|
39
|
+
email: "user#{i}@example.com",
|
|
40
|
+
role: i.even? ? "admin" : "user",
|
|
41
|
+
active: true,
|
|
42
|
+
score: rand(100)
|
|
43
|
+
}
|
|
44
|
+
end,
|
|
45
|
+
|
|
46
|
+
"Nested Object" => {
|
|
47
|
+
user: {
|
|
48
|
+
id: 1,
|
|
49
|
+
name: "Alice",
|
|
50
|
+
profile: {
|
|
51
|
+
age: 30,
|
|
52
|
+
city: "NYC",
|
|
53
|
+
preferences: {
|
|
54
|
+
theme: "dark",
|
|
55
|
+
language: "en",
|
|
56
|
+
notifications: true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
posts: [
|
|
61
|
+
{ id: 1, title: "First post", likes: 10 },
|
|
62
|
+
{ id: 2, title: "Second post", likes: 25 }
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
"Large Tabular (1000 records)" => Array.new(1000) do |i|
|
|
67
|
+
{
|
|
68
|
+
id: i,
|
|
69
|
+
name: "User#{i}",
|
|
70
|
+
email: "user#{i}@example.com",
|
|
71
|
+
score: rand(100)
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
datasets.each do |name, data|
|
|
77
|
+
puts "\n#{name}"
|
|
78
|
+
puts "=" * 80
|
|
79
|
+
|
|
80
|
+
# Encoding benchmark
|
|
81
|
+
puts "\nEncoding Speed:"
|
|
82
|
+
puts "-" * 80
|
|
83
|
+
Benchmark.ips do |x|
|
|
84
|
+
x.config(time: 2, warmup: 1)
|
|
85
|
+
|
|
86
|
+
x.report("JSON") { JSON.generate(data) }
|
|
87
|
+
x.report("YAML") { YAML.dump(data) }
|
|
88
|
+
x.report("TOON") { ToonFormat.encode(data) }
|
|
89
|
+
x.report("MessagePack") { MessagePack.pack(data) } if MSGPACK_AVAILABLE
|
|
90
|
+
|
|
91
|
+
x.compare!
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Generate encoded strings for decoding and size comparison
|
|
95
|
+
json_str = JSON.generate(data)
|
|
96
|
+
yaml_str = YAML.dump(data)
|
|
97
|
+
toon_str = ToonFormat.encode(data)
|
|
98
|
+
msgpack_str = MessagePack.pack(data) if MSGPACK_AVAILABLE
|
|
99
|
+
|
|
100
|
+
# Decoding benchmark
|
|
101
|
+
puts "\nDecoding Speed:"
|
|
102
|
+
puts "-" * 80
|
|
103
|
+
Benchmark.ips do |x|
|
|
104
|
+
x.config(time: 2, warmup: 1)
|
|
105
|
+
|
|
106
|
+
x.report("JSON") { JSON.parse(json_str) }
|
|
107
|
+
x.report("YAML") { YAML.safe_load(yaml_str, permitted_classes: [Symbol]) }
|
|
108
|
+
x.report("TOON") { ToonFormat.decode(toon_str) }
|
|
109
|
+
x.report("MessagePack") { MessagePack.unpack(msgpack_str) } if MSGPACK_AVAILABLE
|
|
110
|
+
|
|
111
|
+
x.compare!
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Size comparison
|
|
115
|
+
puts "\nSize Comparison:"
|
|
116
|
+
puts "-" * 80
|
|
117
|
+
json_size = json_str.bytesize
|
|
118
|
+
yaml_size = yaml_str.bytesize
|
|
119
|
+
toon_size = toon_str.bytesize
|
|
120
|
+
msgpack_size = msgpack_str.bytesize if MSGPACK_AVAILABLE
|
|
121
|
+
|
|
122
|
+
puts "JSON: #{json_size} bytes (baseline)"
|
|
123
|
+
puts "YAML: #{yaml_size} bytes (#{((yaml_size - json_size) / json_size.to_f * 100).round(1)}% vs JSON)"
|
|
124
|
+
puts "TOON: #{toon_size} bytes (#{((toon_size - json_size) / json_size.to_f * 100).round(1)}% vs JSON)"
|
|
125
|
+
if MSGPACK_AVAILABLE
|
|
126
|
+
puts "MessagePack: #{msgpack_size} bytes (#{((msgpack_size - json_size) / json_size.to_f * 100).round(1)}% vs JSON)"
|
|
127
|
+
end
|
|
128
|
+
puts
|
|
129
|
+
|
|
130
|
+
# Readability (tokens approximation for LLM contexts)
|
|
131
|
+
puts "Human Readability & LLM Token Estimate:"
|
|
132
|
+
puts "-" * 80
|
|
133
|
+
json_tokens = (json_size / 4.0).ceil
|
|
134
|
+
yaml_tokens = (yaml_size / 4.0).ceil
|
|
135
|
+
toon_tokens = (toon_size / 4.0).ceil
|
|
136
|
+
|
|
137
|
+
puts "JSON: ~#{json_tokens} tokens (human-readable)"
|
|
138
|
+
puts "YAML: ~#{yaml_tokens} tokens (human-readable)"
|
|
139
|
+
puts "TOON: ~#{toon_tokens} tokens (human-readable, optimized)"
|
|
140
|
+
puts "MessagePack: N/A (binary format - not human-readable)" if MSGPACK_AVAILABLE
|
|
141
|
+
puts
|
|
142
|
+
|
|
143
|
+
puts "=" * 80
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
puts "\nSummary:"
|
|
147
|
+
puts "=" * 80
|
|
148
|
+
puts "TOON Format Advantages:"
|
|
149
|
+
puts " ✓ Human-readable (unlike MessagePack)"
|
|
150
|
+
puts " ✓ 30-60% token reduction vs JSON (better for LLMs)"
|
|
151
|
+
puts " ✓ Faster than YAML for encoding/decoding"
|
|
152
|
+
puts " ✓ Optimal for tabular data (database exports, API responses)"
|
|
153
|
+
puts
|
|
154
|
+
puts "When to use each format:"
|
|
155
|
+
puts " • JSON: Universal compatibility, well-established"
|
|
156
|
+
puts " • YAML: Configuration files, human editing priority"
|
|
157
|
+
puts " • TOON: LLM contexts, API responses, token optimization"
|
|
158
|
+
if MSGPACK_AVAILABLE
|
|
159
|
+
puts " • MessagePack: Maximum compression, binary protocols"
|
|
160
|
+
end
|
|
161
|
+
puts "=" * 80
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "toon_format"
|
|
6
|
+
require "json"
|
|
7
|
+
|
|
8
|
+
# Memory profiling helper
|
|
9
|
+
def measure_memory
|
|
10
|
+
GC.start
|
|
11
|
+
GC.disable
|
|
12
|
+
memory_before = `ps -o rss= -p #{Process.pid}`.to_i
|
|
13
|
+
yield
|
|
14
|
+
GC.start
|
|
15
|
+
memory_after = `ps -o rss= -p #{Process.pid}`.to_i
|
|
16
|
+
GC.enable
|
|
17
|
+
memory_after - memory_before
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
puts "=" * 80
|
|
21
|
+
puts "TOON Format Memory Usage Benchmark"
|
|
22
|
+
puts "=" * 80
|
|
23
|
+
puts
|
|
24
|
+
|
|
25
|
+
# Test data sets with varying sizes
|
|
26
|
+
data_sets = {
|
|
27
|
+
"Small (10 records)" => Array.new(10) { |i|
|
|
28
|
+
{ id: i, name: "User#{i}", email: "user#{i}@example.com", active: i.even? }
|
|
29
|
+
},
|
|
30
|
+
"Medium (100 records)" => Array.new(100) { |i|
|
|
31
|
+
{ id: i, name: "User#{i}", email: "user#{i}@example.com", active: i.even? }
|
|
32
|
+
},
|
|
33
|
+
"Large (1,000 records)" => Array.new(1000) { |i|
|
|
34
|
+
{ id: i, name: "User#{i}", email: "user#{i}@example.com", active: i.even? }
|
|
35
|
+
},
|
|
36
|
+
"Very Large (10,000 records)" => Array.new(10_000) { |i|
|
|
37
|
+
{ id: i, name: "User#{i}", email: "user#{i}@example.com", active: i.even? }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
data_sets.each do |name, data|
|
|
42
|
+
puts name
|
|
43
|
+
puts "-" * 80
|
|
44
|
+
|
|
45
|
+
# Measure JSON encoding memory
|
|
46
|
+
json_memory = measure_memory do
|
|
47
|
+
1000.times { JSON.generate(data) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Measure TOON encoding memory
|
|
51
|
+
toon_memory = measure_memory do
|
|
52
|
+
1000.times { ToonFormat.encode(data) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
puts "JSON encoding (1000 iterations): #{json_memory} KB"
|
|
56
|
+
puts "TOON encoding (1000 iterations): #{toon_memory} KB"
|
|
57
|
+
|
|
58
|
+
diff = json_memory - toon_memory
|
|
59
|
+
if diff > 0
|
|
60
|
+
puts "Memory saved: #{diff} KB (#{((diff / json_memory.to_f) * 100).round(1)}%)"
|
|
61
|
+
elsif diff < 0
|
|
62
|
+
puts "Memory overhead: #{diff.abs} KB (#{((diff.abs / json_memory.to_f) * 100).round(1)}%)"
|
|
63
|
+
else
|
|
64
|
+
puts "Memory usage: equivalent"
|
|
65
|
+
end
|
|
66
|
+
puts
|
|
67
|
+
|
|
68
|
+
# Measure decoding memory
|
|
69
|
+
json_str = JSON.generate(data)
|
|
70
|
+
toon_str = ToonFormat.encode(data)
|
|
71
|
+
|
|
72
|
+
json_decode_memory = measure_memory do
|
|
73
|
+
1000.times { JSON.parse(json_str) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
toon_decode_memory = measure_memory do
|
|
77
|
+
1000.times { ToonFormat.decode(toon_str) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
puts "JSON decoding (1000 iterations): #{json_decode_memory} KB"
|
|
81
|
+
puts "TOON decoding (1000 iterations): #{toon_decode_memory} KB"
|
|
82
|
+
|
|
83
|
+
diff = json_decode_memory - toon_decode_memory
|
|
84
|
+
if diff > 0
|
|
85
|
+
puts "Memory saved: #{diff} KB (#{((diff / json_decode_memory.to_f) * 100).round(1)}%)"
|
|
86
|
+
elsif diff < 0
|
|
87
|
+
puts "Memory overhead: #{diff.abs} KB (#{((diff.abs / json_decode_memory.to_f) * 100).round(1)}%)"
|
|
88
|
+
else
|
|
89
|
+
puts "Memory usage: equivalent"
|
|
90
|
+
end
|
|
91
|
+
puts
|
|
92
|
+
puts "=" * 80
|
|
93
|
+
puts
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
puts "Note: Memory measurements show RSS (Resident Set Size) difference"
|
|
97
|
+
puts "Actual memory usage may vary based on Ruby GC behavior and system state"
|