bank-ro opened a new issue, #3999:
URL: https://github.com/apache/fory/issues/3999

   ### Search before asking
   
   - [x] I had searched in the [issues](https://github.com/apache/fory/issues) 
and found no similar issues.
   
   
   ### Version
   
   1.7.0
   
   ### Component(s)
   
   Java
   
   ### Minimal reproduce step
   
    a
   
   ### What did you expect to see?
   
    a
   
   ### What did you see instead?
   
    a
   
   ### Anything Else?
   
   ## Summary
   
   Upgrading `fory-core` **1.6.1 → 1.7.0**, Java deserialization throughput in 
`CompatibleMode.COMPATIBLE` collapses by **~100–900×** for object graphs that 
reference more than one distinct registered struct type. The generated per-type 
deserializer codec appears to be **regenerated (re-JIT-compiled) on every 
`deserialize()` call** instead of being cached across calls on the same `Fory` 
instance.
   
   `SCHEMA_CONSISTENT` mode is **not** affected. This is a performance 
regression only — values still deserialize correctly, and the wire format is 
unchanged between 1.6.1 and 1.7.0.
   
   ## Environment
   
   - fory-core **1.6.1** (baseline) vs **1.7.0** (regressed)
   - JDK 26 (Temurin), Windows 11
   - `Language.JAVA`, `requireClassRegistration(true)`, `withRefTracking(false)`
   - Reproduces with both `withAsyncCompilation(false)` and 
`withAsyncCompilation(true)`
   
   ## Reproduction
   
   Compile once (the class uses only Fory's stable public API, so the same 
compiled class runs under either jar); run under each version. Deps: 
`fory-core`, `janino`, `commons-compiler`, `slf4j-api`.
   
   ```java
   import org.apache.fory.Fory;
   import org.apache.fory.ThreadSafeFory;
   import org.apache.fory.config.CompatibleMode;
   import org.apache.fory.config.Language;
   
   public class ForyCompatibleRegression {
   
       public static class A { public long a; public int b; public String c; 
public A() {} }
       public static class B { public long a; public int b; public String c; 
public B() {} }
       public static class C { public long a; public int b; public String c; 
public C() {} }
       public static class D { public long a; public int b; public String c; 
public D() {} }
       public static class E { public long a; public int b; public String c; 
public E() {} }
       public static class F { public long a; public int b; public String c; 
public F() {} }
       public static class G { public long a; public int b; public String c; 
public G() {} }
       public static class H { public long a; public int b; public String c; 
public H() {} }
   
       public static class Container {
           public A a; public B b; public C c; public D d; public E e; public F 
f; public G g; public H h;
           public Container() {}
       }
   
       public static void main(String[] args) {
           CompatibleMode mode = 
CompatibleMode.valueOf(System.getProperty("fory.mode", "COMPATIBLE"));
           ThreadSafeFory fory = Fory.builder()
                   .withLanguage(Language.JAVA)
                   .withCompatibleMode(mode)
                   .withRefTracking(false)
                   .requireClassRegistration(true)
                   .withAsyncCompilation(false)
                   .buildThreadSafeFory();
           fory.register(A.class, 201); fory.register(B.class, 202); 
fory.register(C.class, 203);
           fory.register(D.class, 204); fory.register(E.class, 205); 
fory.register(F.class, 206);
           fory.register(G.class, 207); fory.register(H.class, 208);
           fory.register(Container.class, 200);
   
           Container obj = new Container();
           obj.a = new A(); obj.b = new B(); obj.c = new C(); obj.d = new D();
           obj.e = new E(); obj.f = new F(); obj.g = new G(); obj.h = new H();
           obj.a.c = "x";
           byte[] payload = fory.serialize(obj);
   
           long warmEnd = System.nanoTime() + 2_000_000_000L;
           while (System.nanoTime() < warmEnd) sink += 
id(fory.deserialize(payload, Container.class));
           long ops = 0, start = System.nanoTime(), end = start + 
3_000_000_000L;
           while (System.nanoTime() < end) { sink += 
id(fory.deserialize(payload, Container.class)); ops++; }
           double opsPerSec = ops / ((System.nanoTime() - start) / 1e9);
   
           System.out.printf("fory-core %s  mode=%s  Container(8 distinct 
types)=%d bytes%n",
                   version(), mode, payload.length);
           System.out.printf("  deserialize: %,.0f ops/s  (%.2f us/op)%n", 
opsPerSec, 1e6 / opsPerSec);
           System.exit(0);
       }
   
       private static long sink;
       private static int id(Object o) { return o == null ? 0 : 
System.identityHashCode(o); }
       private static String version() {
           Package p = Fory.class.getPackage();
           String v = p == null ? null : p.getImplementationVersion();
           return v == null ? "<unknown>" : v;
       }
   }
   ```
   
   ```
   javac -cp fory-core-1.7.0.jar ForyCompatibleRegression.java
   java --add-opens=java.base/java.lang.invoke=ALL-UNNAMED -cp 
".:fory-core-1.6.1.jar:janino-3.1.12.jar:commons-compiler-3.1.12.jar:slf4j-api-2.0.18.jar"
 ForyCompatibleRegression
   java --add-opens=java.base/java.lang.invoke=ALL-UNNAMED -cp 
".:fory-core-1.7.0.jar:janino-3.1.12.jar:commons-compiler-3.1.12.jar:slf4j-api-2.0.18.jar"
 ForyCompatibleRegression
   # add -Dfory.mode=SCHEMA_CONSISTENT to confirm that mode is unaffected
   ```
   
   ## Results
   
   Same compiled classes, same payload bytes, only the `fory-core` jar differs. 
Deserialize throughput:
   
   | mode | 1.6.1 | 1.7.0 | delta |
   |---|---|---|---|
   | COMPATIBLE | 5,779,968 ops/s (0.17 µs/op) | 19,978 ops/s (50.06 µs/op) | 
**~289× slower** |
   | SCHEMA_CONSISTENT | 7,860,811 ops/s (0.13 µs/op) | 7,823,780 ops/s (0.13 
µs/op) | none |
   
   The slowdown scales with the number of **distinct registered types** in the 
graph (COMPATIBLE, deserialize ops/s):
   
   | distinct types in graph | 1.6.1 | 1.7.0 | delta |
   |---|---|---|---|
   | 1 | 13.6M | 9.6M | ~1.4× |
   | 4 | 7.1M | 41.4k | ~172× |
   | 8 | 3.7M | 20.6k | ~178× |
   | 20 | 1.5M | 7.8k | ~192× |
   | 40 | 0.78M | 3.2k | ~242× |
   
   The 1.7.0 cost grows ~6–8 µs **per distinct type per deserialize call**, 
which points at per-call codec resolution/regeneration. Running the same repro 
with `withAsyncCompilation(true)` makes the mechanism explicit — the JIT 
compile pool is flooded with per-call codec-generation tasks:
   
   ```
   WARN CodeGenerator - Task org.apache.fory.builder.JITContext$$Lambda/... 
rejected from
   java.util.concurrent.ThreadPoolExecutor@...[Shutting down, pool size = 16, 
active threads = 16,
   queued tasks = 69648, completed tasks = 113347]
   ```
   
   i.e. ~113k completed + ~70k queued `CodeGenerator` tasks accumulated during 
a ~2 s measurement window for a graph that has only 9 types — one codec 
(re)generation per deserialize call.
   
   ## Suspected cause
   
   Appears related to **#3963 "refactor java generated codec cache 
granularity."** The regression is COMPATIBLE-mode only (the meta-share path), 
is absent for a single-type graph (N=1), scales with distinct-type count, and 
affects both async settings — consistent with the generated deserializer codec 
no longer being cached (or being cached under a per-call key) on the 
meta-shared read path.
   
   ## Impact note
   
   Real-world impact depends on payload composition: records dominated by large 
primitive arrays hide it (bulk copy dominates, ~20–30% observed), while 
structure-heavy records with many nested types are hit hardest (100×+). Any 
COMPATIBLE-mode workload that deserializes such graphs repeatedly on a shared 
`Fory` instance is affected.
   
   ### Are you willing to submit a PR?
   
   - [ ] I'm willing to submit a PR!


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to