Skip to content

[Java][Docs] GraalVM native-image: document that runtime codegen is disabled (withCodegen(true) overridden) and that the ~45x native benchmark is payload-dependent (measured 3x/7x vs JDK, 1.4x vs Jackson on a record graph) #3950

Description

@supsup

Search before asking

  • I searched in the issues and found nothing similar.

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 imagesForyBuilder.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?

  • I'm willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions