Skip to content

Commit 95ecea3

Browse files
authored
Merge pull request #15 from hossain-khan/copilot/fix-14
Add benchmark module for comparing JSON5 vs JSON serialization performance
2 parents e3bd54d + 19fb05b commit 95ecea3

File tree

9 files changed

+796
-1
lines changed

9 files changed

+796
-1
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ replay_pid*
2929
.gradle
3030
.idea
3131
build/
32+
33+
# Benchmark result files
34+
benchmark_results_*.csv
35+
benchmark_summary_*.txt

benchmark/README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# JSON5 vs JSON Performance Benchmark
2+
3+
This module provides benchmarking tools to compare the performance of JSON5 serialization/deserialization against standard JSON using kotlinx.serialization.
4+
5+
## Running the Benchmark
6+
7+
To run the complete benchmark suite:
8+
9+
```bash
10+
./gradlew :benchmark:run
11+
```
12+
13+
This will:
14+
1. Warm up the JVM
15+
2. Run serialization and deserialization benchmarks for various data types
16+
3. Generate CSV and summary reports
17+
18+
## Generated Reports
19+
20+
The benchmark generates two types of reports:
21+
22+
### CSV Report
23+
- File: `benchmark_results_YYYY-MM-DD_HH-mm-ss.csv`
24+
- Contains detailed timing data for each test
25+
- Suitable for importing into spreadsheet applications or data analysis tools
26+
- Columns: Operation, DataType, Format, Iterations, TotalTimeNanos, AverageTimeNanos, AverageTimeMicros, AverageTimeMillis
27+
28+
### Summary Report
29+
- File: `benchmark_summary_YYYY-MM-DD_HH-mm-ss.txt`
30+
- Contains human-readable performance comparisons
31+
- Shows which format is faster and by how much
32+
- Includes overall statistics
33+
34+
## Test Data Types
35+
36+
The benchmark tests the following data structures:
37+
38+
- **SimplePerson**: Basic data class with name, age, and boolean
39+
- **ComplexPerson**: Complex nested object with address, phone numbers, skills, etc.
40+
- **Company**: Large nested structure with employees and departments
41+
- **NumberTypes**: Various numeric types (int, long, double, float, byte, short)
42+
- **CollectionTypes**: Lists, maps, and nested collections
43+
- **Lists**: Collections of 50-100 complex objects
44+
45+
## Configuration
46+
47+
You can modify the benchmark parameters in `BenchmarkRunner.kt`:
48+
49+
- `iterations`: Number of operations per test (default: 1000)
50+
- `warmupIterations`: Number of warmup iterations (default: 100)
51+
52+
## Running Tests
53+
54+
To run the benchmark module tests:
55+
56+
```bash
57+
./gradlew :benchmark:test
58+
```
59+
60+
## Sample Results
61+
62+
Based on typical runs, JSON standard library generally performs 2-6x faster than JSON5 for both serialization and deserialization, with the performance gap being larger for more complex data structures.
63+
64+
Example output:
65+
```
66+
SimplePerson Serialization: JSON5=0.027ms, JSON=0.013ms
67+
ComplexPerson Serialization: JSON5=0.083ms, JSON=0.015ms
68+
Company Serialization: JSON5=0.200ms, JSON=0.032ms
69+
```

benchmark/build.gradle.kts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
plugins {
2+
// Apply the shared build logic from a convention plugin.
3+
id("buildsrc.convention.kotlin-jvm")
4+
alias(libs.plugins.kotlinPluginSerialization)
5+
application
6+
}
7+
8+
dependencies {
9+
// Depend on the lib module for JSON5 implementation
10+
implementation(project(":lib"))
11+
12+
// kotlinx.serialization for JSON comparison
13+
implementation(libs.bundles.kotlinxEcosystem)
14+
15+
// Test dependencies
16+
testImplementation(kotlin("test"))
17+
testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
18+
}
19+
20+
application {
21+
// Set the main class for the benchmark application
22+
mainClass.set("dev.hossain.json5kt.benchmark.BenchmarkRunner")
23+
}
24+
25+
tasks.test {
26+
useJUnitPlatform()
27+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package dev.hossain.json5kt.benchmark
2+
3+
import java.io.File
4+
import java.time.LocalDateTime
5+
import java.time.format.DateTimeFormatter
6+
7+
/**
8+
* Main class to run JSON5 vs JSON serialization benchmarks and generate CSV reports.
9+
*/
10+
object BenchmarkRunner {
11+
12+
@JvmStatic
13+
fun main(args: Array<String>) {
14+
println("Starting JSON5 vs JSON Serialization Benchmarks...")
15+
println("===============================================")
16+
17+
val benchmark = SerializationBenchmark()
18+
val allResults = mutableListOf<SerializationBenchmark.BenchmarkResult>()
19+
20+
// Configuration
21+
val iterations = 1000
22+
val warmupIterations = 100
23+
24+
println("Configuration:")
25+
println("- Iterations per test: $iterations")
26+
println("- Warmup iterations: $warmupIterations")
27+
println()
28+
29+
// Warmup JVM
30+
println("Warming up JVM...")
31+
runWarmup(benchmark, warmupIterations)
32+
33+
// Run benchmarks for different data types
34+
println("Running benchmarks...")
35+
36+
// Simple Person
37+
println("Benchmarking SimplePerson...")
38+
val simplePerson = TestDataGenerator.createSimplePerson()
39+
allResults.addAll(benchmark.benchmarkComplete(simplePerson, "SimplePerson", iterations, SimplePerson.serializer()))
40+
41+
// Complex Person
42+
println("Benchmarking ComplexPerson...")
43+
val complexPerson = TestDataGenerator.createComplexPerson()
44+
allResults.addAll(benchmark.benchmarkComplete(complexPerson, "ComplexPerson", iterations, ComplexPerson.serializer()))
45+
46+
// Company (large nested object)
47+
println("Benchmarking Company...")
48+
val company = TestDataGenerator.createCompany()
49+
allResults.addAll(benchmark.benchmarkComplete(company, "Company", iterations, Company.serializer()))
50+
51+
// Number Types
52+
println("Benchmarking NumberTypes...")
53+
val numberTypes = TestDataGenerator.createNumberTypes()
54+
allResults.addAll(benchmark.benchmarkComplete(numberTypes, "NumberTypes", iterations, NumberTypes.serializer()))
55+
56+
// Collection Types
57+
println("Benchmarking CollectionTypes...")
58+
val collectionTypes = TestDataGenerator.createCollectionTypes()
59+
allResults.addAll(benchmark.benchmarkComplete(collectionTypes, "CollectionTypes", iterations, CollectionTypes.serializer()))
60+
61+
// List of Simple Persons (100 items)
62+
println("Benchmarking List of 100 SimplePersons...")
63+
val simplePersonList = TestDataGenerator.createSimplePersonList(100)
64+
allResults.addAll(benchmark.benchmarkComplete(simplePersonList, "SimplePersonList100", iterations, kotlinx.serialization.builtins.ListSerializer(SimplePerson.serializer())))
65+
66+
// List of Complex Persons (50 items)
67+
println("Benchmarking List of 50 ComplexPersons...")
68+
val complexPersonList = TestDataGenerator.createComplexPersonList(50)
69+
allResults.addAll(benchmark.benchmarkComplete(complexPersonList, "ComplexPersonList50", iterations, kotlinx.serialization.builtins.ListSerializer(ComplexPerson.serializer())))
70+
71+
// Generate reports
72+
println()
73+
println("Generating reports...")
74+
generateCsvReport(allResults)
75+
generateSummaryReport(allResults)
76+
77+
println("Benchmarks completed!")
78+
}
79+
80+
private fun runWarmup(benchmark: SerializationBenchmark, warmupIterations: Int) {
81+
val warmupData = TestDataGenerator.createSimplePerson()
82+
benchmark.benchmarkComplete(warmupData, "Warmup", warmupIterations, SimplePerson.serializer())
83+
}
84+
85+
private fun generateCsvReport(results: List<SerializationBenchmark.BenchmarkResult>) {
86+
val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"))
87+
val filename = "benchmark_results_$timestamp.csv"
88+
89+
File(filename).writeText(
90+
buildString {
91+
appendLine(SerializationBenchmark.BenchmarkResult.csvHeader())
92+
results.forEach { result ->
93+
appendLine(result.toCsvRow())
94+
}
95+
}
96+
)
97+
98+
println("CSV report generated: $filename")
99+
}
100+
101+
private fun generateSummaryReport(results: List<SerializationBenchmark.BenchmarkResult>) {
102+
val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"))
103+
val filename = "benchmark_summary_$timestamp.txt"
104+
105+
val report = buildString {
106+
appendLine("JSON5 vs JSON Serialization Benchmark Summary")
107+
appendLine("===========================================")
108+
appendLine("Generated: ${LocalDateTime.now()}")
109+
appendLine()
110+
111+
// Group results by data type and operation
112+
val groupedResults = results.groupBy { "${it.dataType}_${it.operation}" }
113+
114+
groupedResults.forEach { (key, resultList) ->
115+
val (dataType, operation) = key.split("_")
116+
appendLine("$dataType - $operation:")
117+
118+
val json5Result = resultList.find { it.format == "JSON5" }
119+
val jsonResult = resultList.find { it.format == "JSON" }
120+
121+
if (json5Result != null && jsonResult != null) {
122+
appendLine(" JSON5: ${String.format("%.3f", json5Result.averageTimeMillis)} ms avg")
123+
appendLine(" JSON: ${String.format("%.3f", jsonResult.averageTimeMillis)} ms avg")
124+
125+
val speedup = json5Result.averageTimeMillis / jsonResult.averageTimeMillis
126+
if (speedup > 1.0) {
127+
appendLine(" → JSON is ${String.format("%.2f", speedup)}x faster than JSON5")
128+
} else {
129+
appendLine(" → JSON5 is ${String.format("%.2f", 1.0 / speedup)}x faster than JSON")
130+
}
131+
}
132+
appendLine()
133+
}
134+
135+
// Overall statistics
136+
appendLine("Overall Statistics:")
137+
val json5Results = results.filter { it.format == "JSON5" }
138+
val jsonResults = results.filter { it.format == "JSON" }
139+
140+
val avgJson5Time = json5Results.map { it.averageTimeMillis }.average()
141+
val avgJsonTime = jsonResults.map { it.averageTimeMillis }.average()
142+
143+
appendLine("Average JSON5 time: ${String.format("%.3f", avgJson5Time)} ms")
144+
appendLine("Average JSON time: ${String.format("%.3f", avgJsonTime)} ms")
145+
146+
val overallSpeedup = avgJson5Time / avgJsonTime
147+
if (overallSpeedup > 1.0) {
148+
appendLine("Overall: JSON is ${String.format("%.2f", overallSpeedup)}x faster than JSON5")
149+
} else {
150+
appendLine("Overall: JSON5 is ${String.format("%.2f", 1.0 / overallSpeedup)}x faster than JSON")
151+
}
152+
}
153+
154+
File(filename).writeText(report)
155+
println("Summary report generated: $filename")
156+
157+
// Also print summary to console
158+
println()
159+
println("=== BENCHMARK SUMMARY ===")
160+
results.groupBy { "${it.dataType}_${it.operation}" }.forEach { (key, resultList) ->
161+
val (dataType, operation) = key.split("_")
162+
val json5Result = resultList.find { it.format == "JSON5" }
163+
val jsonResult = resultList.find { it.format == "JSON" }
164+
165+
if (json5Result != null && jsonResult != null) {
166+
println("$dataType $operation: JSON5=${String.format("%.3f", json5Result.averageTimeMillis)}ms, JSON=${String.format("%.3f", jsonResult.averageTimeMillis)}ms")
167+
}
168+
}
169+
}
170+
}

0 commit comments

Comments
 (0)