blob: 51b915f1c3df47750678d793fb6f08bcaed6c275 [file] [view]
# JVM JSON byte-array format measurements
This comparison covers numeric byte arrays and Base16 strings on the unchanged
jsoniter-scala `ArrayOfBytesReading`, `ArrayOfBytesWriting`, `Base16Reading`, and
`Base16Writing` workloads at `size=512`. Higher throughput is better.
All 12 final adjacent comparison pairs exceed 10%. Numeric-array reading has a
narrow margin: its smallest measured improvement is 10.05%. These are local
steady-state measurements, not a guarantee for other sizes, data distributions,
machines, or JVMs.
## Results
| Workload | jsoniter-scala median, ops/s | Fory median, ops/s | Median paired improvement | Paired range |
| -------------------------- | ---------------------------: | -----------------: | ------------------------: | -------------------: |
| Numeric byte-array reading | 646,911 | 713,856 | +10.18% | +10.05% to +11.55% |
| Numeric byte-array writing | 1,539,685 | 3,350,581 | +117.93% | +116.07% to +118.36% |
| Base16 reading | 2,914,929 | 3,585,325 | +22.18% | +15.26% to +24.77% |
| Base16 writing | 8,444,372 | 9,639,286 | +15.94% | +13.78% to +16.00% |
Each pair compares the means of five measurement iterations. The improvement
column is the median of those paired ratios, not the ratio of independent medians.
All final samples are retained. Earlier, shorter numeric-reading comparisons
measured +8.59% and +13.01%; they are also included in the evidence and illustrate
why the final narrow margin should not be generalized.
Allocation per operation is approximately 528 B for Fory numeric-array reading
and 1,128 B for jsoniter-scala. Both libraries allocate about 1,888 B for numeric
array output, 528 B for Base16 input, and 1,048 B for Base16 output. Both use APIs
that return independent arrays; neither uses the preallocated-output benchmark.
Startup and retained lookup-table memory are outside these steady-state allocation
figures. Fory's Base16 output table occupies 256 KiB after its first use.
The affected integer-reading path was also compared with the pre-optimization
Fory revision in two adjacent pairs. Changes were +1.41% and +1.01%, within the
range normally treated as noise; no regression was observed in those runs.
[Iteration samples, allocations, paired results, and artifact hashes](byte-array-formats-results.json)
are available as JSON.
## Workloads and configuration
The input bytes are the benchmark's original sequence
`(1 to size).map(_.toByte).toArray`. Numeric arrays keep the original signed decimal
JSON representation. Base16 keeps the original lowercase hexadecimal string.
Models, inputs, expected outputs, and jsoniter-scala implementations are unchanged.
Only the missing Fory adapters and their existing correctness specs are enabled.
Fory uses separate instances configured through its public builder:
```java
import org.apache.fory.json.ForyJson;
import org.apache.fory.json.annotation.JsonByteArray;
ForyJson arrays = ForyJson.builder().byteArrayFormat(JsonByteArray.Format.ARRAY).build();
ForyJson hex = ForyJson.builder().byteArrayFormat(JsonByteArray.Format.BASE16).build();
```
Each reading adapter calls `fromJson(jsonBytes, classOf[Array[Byte]])`; each writing
adapter calls `toJsonBytes(obj)`. These configurations also apply to nested byte
arrays; they are not benchmark-specific codecs.
## Environment and revisions
- Date: September 21, 2026.
- Apple M4 Pro, 12 logical CPUs, 48 GiB RAM; macOS 15.7.2 arm64.
- OpenJDK 25.0.3; JMH 1.37.
- Fory: `13739263ebf31af5fc962184aaccf2833f8ffc9a`, version `1.8.0-SNAPSHOT`.
- Fory pre-optimization baseline: `e1d2f9d67800901392755b1133968d448ab076b8`.
- jsoniter-scala: `2.40.2-SNAPSHOT`, checkout
`71b2ba16f1d5261e2be4e98d5dfe4be452d0e136`; its Fory adapter additions are based on
`84cb277f2c2a16b4018bc61fdeec3eed62252a1e`.
- Benchmark Scala: 3.9.0; Fory JSON Scala: 3.3.8.
- One thread and one fork per run; three 1-second warmups and five 1-second
measurements; `-Xms1g -Xmx1g -XX:+UseParallelGC`; JMH GC profiler.
- Three adjacent pairs per workload. Order is jsoniter/Fory, Fory/jsoniter,
jsoniter/Fory. Runs are serial; normal desktop background activity remains.
The runtime, core, Scala, and benchmark jars are frozen during measurement. Only
the Fory JSON runtime jar changes in baseline/current controls.
## Reproduction
Install Fory core, JSON, and JSON Scala artifacts before building the benchmark
assembly. Enable Maven Local when using locally published snapshots:
```bash
sbt --java-home "$JAVA_HOME" \
'set ThisBuild / resolvers += Resolver.mavenLocal' \
'++3.9.0!' 'jsoniter-scala-benchmarkJVM/assembly'
```
Run each method as a separate process, alternating the library order between
pairs. Use the same frozen classpath for both methods:
```bash
java -cp "$FORY_JSON_JAR:$FORY_SCALA_JAR:$FORY_CORE_JAR:$BENCHMARK_JAR" \
org.openjdk.jmh.Main ".*\.${CASE}\.${METHOD}$" \
-t 1 -f 1 -wi 3 -i 5 -w 1s -r 1s -p size=512 \
-jvmArgs '-Xms1g -Xmx1g -XX:+UseParallelGC' -foe true -prof gc \
-rf json -rff "$RESULT_FILE"
```
`CASE` is one of the four workload names above; `METHOD` is `fory` or
`jsoniterScala`. No GraalVM or native-image performance is measured here.