Replies: 1 comment
|
Fory json on graalvm should be faster now. We generate codec at build time by default when |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Apache Fory — evaluation for Stafficy (2026-08-18)
TL;DR
Poisoning/provenance: clean. Stock
apache/foryclone at upstreammainHEAD6fedcf1dc(2026-08-18), ASF LICENSE/NOTICE, 128 tags (latest v1.6.1, 2026-08-16), sample-only git hooks, no extra files, no binaries, no submodules. Zero prompt-injection markers, zero credential-shaped strings, zero hidden text (only one U+200D, an emoji ZWJ in a Swift test). Verdict after pass 1: AMBER leaning GREEN — amber only because the repo ships a real agent-guidance corpus (AGENTS.md,CLAUDE.md→symlink,.agents/**, Codexopenai.yamlskills) that tells assistants to build/test/benchmark,git fetchanapacheremote and auto-commit; ordinary engineering guidance, treated as untrusted data.Builds and tests in Docker: green.
fory-core+fory-jsonbuild in <1 min on JDK 21 and 25; 236/236 focused fory-core tests pass on both.Performance on a Stafficy-shaped payload: Fory Java-mode ≈ 6× Jackson serialize, ≈2× deserialize, 0.67× bytes;
fory-json≈ 4× Jackson serialize / 1.7× deserialize at identical JSON size. JDK 25, in-process, no JMH (numbers below).Security posture: good defaults (class registration required, depth/size/graph limits, gadget blocklist), explicit threat-model docs; one Java-side CVE (registration bypass, fixed in 1.1.0), the serious ones were pyfory.
Value inside Stafficy today: LOW-TO-MODERATE, and NOT NOW as a dependency. Stafficy has zero JDK serialization; everything is Jackson JSON over HTTP, and the CLI helper is a GraalVM native image on reflection-free jackson-jr. Fory's win is real but lands on surfaces we either don't have (Java-native binary blobs) or haven't measured as serialization-bound (projection endpoints). Recommendation: no adoption plan now; keep as the named candidate for two specific seats (below) and revisit with a bounded spike only if the dashboard-projection read residual (plan 618fa17b) or the next-actions/dischargeIndex payloads (F9b, ~800 rows) turn out to be Jackson-bound after measurement.
1. What Fory is (from its own docs, read as text)
Apache Fory (ex-Fury), ASF top-level project: a multi-language serialization framework — cross-language binary protocol (metadata packing, schema evolution, shared/circular refs, polymorphism), a Java-native mode positioned as a drop-in for JDK serialization / Kryo / Hessian / FST, a Row format with zero-copy partial reads,
fory-json(runtime-generated JSON codecs), and a schema compiler. Java uses JIT-generated serializers; other languages static/generated. Languages: Java, Python, C++, Go, Rust, JS/TS, C#, Swift, Dart, Scala, Kotlin. Java coordinatesorg.apache.fory:fory-core(Maven Central; source tree is1.7.0-SNAPSHOT, released 1.6.1). Java 8 bytecode with multi-release layers for 17 (GraalVM Feature) and 25 (VarHandle/Unsafe codegen).2. Pass 1 — poisoning-aware read-only audit (evidence)
-- Passed.
3. Pass 2 — throwaway Docker build, tests, benchmark
Work dir: session scratchpad
fory-eval/(results kept; source copy and containers/images removed). Containersfory-eval-*only; the Stafficy stack untouched.mvn install -pl fory-core,fory-json -am -DskipTestsForyTest, ForyBuilderTest, MetaShareCompatibleTest, ThreadSafeForyTest, CyclicTest, StreamTest)Trap found (self-builds only): a fory-core jar built on JDK 21 is
Multi-Release: truebut has noMETA-INF/versions/25layer (thejdk25-multi-releaseprofile activates only on JDK≥25); running it on JDK 25 fails at first serialize withCannot load generated-code helper class org.apache.fory.builder.VarHandleCodegenSupport. Released jars are built on 25 so Maven Central artifacts are fine; the benchmark used the JDK-25 build.Benchmark (Stafficy-shaped payload)
Payload: a
PlanProjectionrecord (17 fields) holding 50CoordinationEventrecords (room, sequence, UUID string, actor, kind, 8-keyMap<String,String>, 4 tags, epoch millis, nested record). Plain Java records, no annotations. 5 warm-up + 10 measured rounds × 20k iterations, in-process, no JMH (indicative, not publication-grade). Deps: fory-core/fory-json 1.7.0-SNAPSHOT (JDK-25 build), jackson-databind 2.18.4.JDK 25 (eclipse-temurin:25.0.3,
--add-opens=java.base/java.lang.invoke=ALL-UNNAMED)requireClassRegistration, registered)ThreadSafeForyCompatibleMode.COMPATIBLEJDK 21: same ordering (Fory JAVA 140k/59k, COMPATIBLE 194k/58k, XLANG 195k/57k, fory-json 135k/51k, Jackson 36k/29k, JDK 21k/12k). Without
--add-openson JDK 25 the numbers hold but Fory prints asun.misc.Unsafe::staticFieldOffsetterminally-deprecated warning. Differences among Fory modes are within run noise (~10–15%). Records worked in every mode with no annotations; JDK serialization needed aSerializablemirror.Read: ~6× Jackson on serialize, ~2× on deserialize, two-thirds the bytes; fory-json ~4× / ~1.7× at byte-identical JSON. Deserialize is where the JSON gap is smallest, and deserialize is what most of our read paths do.
GraalVM native-image (relevant to
ai-bin)docs/object-serialization/java/graalvm.md: fory-core ships its ownForyGraalVMFeature(java17 MR layer), auto-activated viaMETA-INF/native-image/org.apache.fory/fory-core/native-image.properties(--features=…+ ~760-class--initialize-at-build-timelist, small reflection/serialization configs). Pattern: staticFory(orThreadLocalFory),register()every type,fory.ensureSerializersCompiled(), own bootstrap class in--initialize-at-build-time; serializers generated at image build time (runtime JIT off). Their own GraalVM tests pass-J--add-opens=java.base/java.lang.invoke=ALL-UNNAMED.fory-jsonhas its own Feature (@JsonType, optional@ForyJsonProvider). Claim: ~45× JDK serialization in native image (docs/benchmarks/.../java-graalvm.md).GraalVM native-image benchmark — does "~45× JDK" reproduce?
No — not on our payload. Same harness/payload,
ghcr.io/graalvm/native-image-community:25(CE 25.0.2, aarch64), released fory-core/fory-json 1.6.1, native-maven-plugin 0.10.6 with the reachability-metadata repo; JDK-ser/Jackson metadata from the tracing agent. JIT and native measured in the same container.ForyBuilder.finishCodegen()whenIN_GRAALVM_NATIVE_IMAGE;withCodegen(true)is ignored) and runs the interpretedObjectSerializerfor every instance, whereas the JIT uses a generated…ForyCodecclass. Fory's own 46× figure is for a 12-primitive-field struct under that same interpreted path — on a string/collection-heavy record graph the JIT-codegen advantage is what evaporates.-O3 -march=nativeand heap knobs: within noise.@ForyJsonProviderin the image — configuration burden we did not take on.ForyJson$PooledState,ThreadPoolFory$PooledEntryandconfig.Languagemust not be reachable from build-time-initialized statics (useThreadLocalFory, create JSON at run time, addLanguage,CompatibleModeto--initialize-at-build-time). Runtime warnings: COMPATIBLE mode falls back toCompatibleSerializer("can't generate class at runtime in graalvm").In native, Fory beats Jackson by ~1.3–1.4× on this shape and fory-json is slower than Jackson unless codecs are pre-registered; Seat A (fory-json on the JVM server's hot projection endpoints) is unaffected by this addendum — that is a JIT question, and stays behind the measurement gate.
Artifacts:
2026-08-18-apache-fory-evaluation-artifacts/native/(TABLE.txt, raw runs, build logs) andbench-native-src/.Security posture (docs +
ForyBuilderdefaults, read as text)requireClassRegistration=true,maxDepth=50,maxGraphMemoryBytes=128 MiB,maxUnbackedContainerItems=8192,maxTypeFields=512,maxTypeMetaBytes=4096,checkJdkClassSerializable=true,trackingRef=false, andxlang=trueby default —Fory.builder().build()is XLANG mode; Java-native needswithXlang(false)/Language.JAVA. Easy foot-gun.withTypeChecker(AllowListChecker …),withDeserializeUnknownClass(false); built-in gadget blocklist (resolver/DisallowedList.java, ~258 names). Java-native mode honourswriteObject/readObject/writeReplace/readResolveon registered classes — registration is the trust decision ("not a sandbox for application-owned types",docs/security/threat-model.md).docs/security/deserialization.mdstates invariants (no OOM from declared lengths, depth limit, no no-progress loops, cleanup after failed root).maxDepth20, 128 MiB graph budget, buffer size limit.4. Value assessment
fory-jsonas the JSON codec on hot projection endpoints (coordination dashboard projection, next-actions rows ~800, dischargeIndex ~1250 entries)HttpMessageConverterswap is contained@JsonTypeannotations on projection records; Jackson-specific annotations/mixins in those 55 sites would not carry; native-image irrelevant (server is JVM). Unmeasured premise: we do not know serialization is a material share of those requests (RequestTimingFilterslow-requestevents exist — measure first).CompatibleModefor schema evolutionai-binnative helperDecision: treat Fory as known-clean and known-fast, file no adoption plan, and record it as the named candidate for seat A behind a measurement gate. If 618fa17b (dashboard-projection read residual) or the F9b payload sizes ever make Jackson the bottleneck, the bounded spike is:
fory-jsonHttpMessageConverterfor the two projection endpoints only,@JsonTypeon their record types, A/B behind a property, measured with the existingRequestTimingFilter. Java 25 note for that spike: pass--add-opens=java.base/java.lang.invoke=ALL-UNNAMED(or accept the Unsafe deprecation warning); use released 1.6.x jars (built on JDK 25), never a JDK-21 self-build.All reactions