Search before asking
Version
fory-core / fory-json 1.6.1 (Maven Central), GraalVM CE 25.0.2 (ghcr.io/graalvm/native-image-community:25, aarch64), native-maven-plugin 0.10.6.
Component(s)
Java, Documentation
Minimal reproduce step
Same benchmark harness run twice in the same container — once java -jar (JIT), once as a native image built per docs/object-serialization/java/graalvm.md (static Fory/ThreadLocalFory, register() all types, ensureSerializersCompiled(), bootstrap class in --initialize-at-build-time, -J--add-opens=java.base/java.lang.invoke=ALL-UNNAMED). Payload: a 17-field record holding 50 event records (each: strings, a UUID string, an 8-key Map<String,String>, a 4-item List<String>, a long, a nested record) — i.e. a typical string/collection-heavy application object graph, not a primitive struct. 5 warm-up + 10 measured rounds × 20k iterations, single thread.
| codec |
JIT ser |
JIT deser |
native ser |
native deser |
bytes |
| JDK ObjectOutputStream |
22,333 |
13,597 |
6,808 |
2,488 |
20,745 |
| Jackson 2.18.4 |
41,874 |
26,888 |
15,504 |
14,216 |
25,311 |
| Fory JAVA (registered) |
149,834 |
75,557 |
20,634 |
17,825 |
17,004 |
| Fory XLANG |
147,727 |
84,580 |
21,094 |
19,494 |
16,238 |
| fory-json |
118,686 |
55,229 |
4,454 |
4,188 |
25,311 |
(ops/s; -O2; -O3 -march=native and heap knobs within noise; two native runs each within ~5%.)
What did you expect to see?
The GraalVM docs and the native benchmark page (docs/benchmarks/object-serialization/native/java-graalvm.md, "~45× JDK serialization") to state that runtime codegen is disabled in native images — ForyBuilder.finishCodegen() forces codeGenEnabled=false when GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE, and withCodegen(true) is overridden (only an INFO log says so) — and that the native speedup therefore depends heavily on payload shape.
What did you see instead?
- Fory vs JDK in native on this payload: 3.1× serialize / 7.4× deserialize (vs 6.7×/5.6× under JIT); Fory vs Jackson in native: 1.4× / 1.3×. Native Fory runs at ~0.14× (ser) / ~0.24× (deser) of its own JIT speed because every type goes through the interpreted
ObjectSerializer (the JIT run uses generated *ForyCodec classes).
- The docs benchmark's 46× is for a 12-primitive-field struct under that same interpreted path; readers extrapolating to record graphs will be surprised.
- fory-json in native falls to ~0.3× Jackson unless a
@ForyJsonProvider is reachable in the image (it does warn at runtime — good — but the GraalVM JSON page could say the default is the slow path).
- Minor:
withCodegen(true) in a native image is silently overridden at INFO level; a WARN (or a documented "ignored in native") would save a debugging round.
Anything Else?
Not a bug report about correctness — every mode round-tripped equal. It is a request to (1) state the codegen-disabled-in-native fact and its payload dependence next to the native benchmark numbers, ideally with a non-primitive payload row; (2) mention the two build-time-initialization pitfalls we hit on the way (objects of ForyJson$PooledState / ThreadPoolFory$PooledEntry reachable from a build-time-initialized static → UnsupportedFeatureException; config.Language captured by a ThreadLocalFory factory needs --initialize-at-build-time=org.apache.fory.config.Language) in graalvm.md. Happy to share the harness.
Are you willing to submit a PR?
Search before asking
Version
fory-core / fory-json 1.6.1 (Maven Central), GraalVM CE 25.0.2 (
ghcr.io/graalvm/native-image-community:25, aarch64), native-maven-plugin 0.10.6.Component(s)
Java, Documentation
Minimal reproduce step
Same benchmark harness run twice in the same container — once
java -jar(JIT), once as a native image built perdocs/object-serialization/java/graalvm.md(staticFory/ThreadLocalFory,register()all types,ensureSerializersCompiled(), bootstrap class in--initialize-at-build-time,-J--add-opens=java.base/java.lang.invoke=ALL-UNNAMED). Payload: a 17-field record holding 50 event records (each: strings, a UUID string, an 8-keyMap<String,String>, a 4-itemList<String>, a long, a nested record) — i.e. a typical string/collection-heavy application object graph, not a primitive struct. 5 warm-up + 10 measured rounds × 20k iterations, single thread.(ops/s; -O2;
-O3 -march=nativeand heap knobs within noise; two native runs each within ~5%.)What did you expect to see?
The GraalVM docs and the native benchmark page (
docs/benchmarks/object-serialization/native/java-graalvm.md, "~45× JDK serialization") to state that runtime codegen is disabled in native images —ForyBuilder.finishCodegen()forcescodeGenEnabled=falsewhenGraalvmSupport.IN_GRAALVM_NATIVE_IMAGE, andwithCodegen(true)is overridden (only an INFO log says so) — and that the native speedup therefore depends heavily on payload shape.What did you see instead?
ObjectSerializer(the JIT run uses generated*ForyCodecclasses).@ForyJsonProvideris reachable in the image (it does warn at runtime — good — but the GraalVM JSON page could say the default is the slow path).withCodegen(true)in a native image is silently overridden at INFO level; a WARN (or a documented "ignored in native") would save a debugging round.Anything Else?
Not a bug report about correctness — every mode round-tripped equal. It is a request to (1) state the codegen-disabled-in-native fact and its payload dependence next to the native benchmark numbers, ideally with a non-primitive payload row; (2) mention the two build-time-initialization pitfalls we hit on the way (objects of
ForyJson$PooledState/ThreadPoolFory$PooledEntryreachable from a build-time-initialized static →UnsupportedFeatureException;config.Languagecaptured by aThreadLocalForyfactory needs--initialize-at-build-time=org.apache.fory.config.Language) ingraalvm.md. Happy to share the harness.Are you willing to submit a PR?