This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new b7c16d0f0 perf(dart): optimize struct deserialize with direct dispatch 
codegen (#3563)
b7c16d0f0 is described below

commit b7c16d0f04c743b910407f49895d5edddc6a060d
Author: Yash Agarwal <[email protected]>
AuthorDate: Tue Apr 14 17:36:41 2026 +0530

    perf(dart): optimize struct deserialize with direct dispatch codegen (#3563)
    
    ## Why?
    Fory struct deserialize in Dart was slower than it should be given
    Fory's wire format.
    
    ## What does this PR do?
    
    - Codegen emits direct read calls for statically safe struct/ext fields,
    skipping runtime
    flag and type switches (mirrors Java's ObjectCodecBuilder).
    - Runtime replaces a one-key HashMap on ReadContext/WriteContext with a
    dedicated field.
    - Fixes benchmark workload file broken by the #3560 constructor
    refactor.
    
    ## Related issues
     #3558
    
    ## AI Contribution Checklist
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence of the final clean AI review results
    from both fresh reviewers on the current PR diff or current HEAD after
    the latest code changes.
    
    ## Does this PR introduce any user-facing change?
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    Co-authored-by: Shawn Yang <[email protected]>
---
 benchmarks/dart/lib/src/workloads.dart             |  2 +-
 .../fory/lib/src/codegen/fory_generator.dart       | 90 +++++++++++++++++-----
 .../fory/lib/src/codegen/generated_support.dart    | 42 ++++++++--
 .../fory/lib/src/context/read_context.dart         | 33 ++------
 .../fory/lib/src/context/write_context.dart        | 34 ++------
 .../fory/lib/src/serializer/struct_serializer.dart | 40 ++++------
 .../fory/lib/src/serializer/struct_slots.dart      |  6 --
 7 files changed, 133 insertions(+), 114 deletions(-)

diff --git a/benchmarks/dart/lib/src/workloads.dart 
b/benchmarks/dart/lib/src/workloads.dart
index 12766c37d..32f598872 100644
--- a/benchmarks/dart/lib/src/workloads.dart
+++ b/benchmarks/dart/lib/src/workloads.dart
@@ -158,7 +158,7 @@ buildBenchmarkDefinitions() {
 }
 
 Fory newBenchmarkFory() {
-  final fory = Fory(config: const Config(compatible: true));
+  final fory = Fory(compatible: true);
   registerBenchmarkTypes(fory);
   return fory;
 }
diff --git a/dart/packages/fory/lib/src/codegen/fory_generator.dart 
b/dart/packages/fory/lib/src/codegen/fory_generator.dart
index 424301df1..563279041 100644
--- a/dart/packages/fory/lib/src/codegen/fory_generator.dart
+++ b/dart/packages/fory/lib/src/codegen/fory_generator.dart
@@ -501,6 +501,14 @@ final class ForyGenerator extends Generator {
             output.writeln(
               '      value.${field.name} = 
${_directGeneratedTypedContainerReadExpression(structSpec.name, field, 
'fields[$index]')};',
             );
+          } else if (_usesDirectGeneratedStructFieldFastPath(field)) {
+            output.writeln(
+              '      value.${field.name} = 
$readerFunctionName(readGeneratedStructDirectValue(context, fields[$index]), 
value.${field.name});',
+            );
+          } else if (_usesDirectGeneratedDeclaredReadFastPath(field)) {
+            output.writeln(
+              '      value.${field.name} = 
$readerFunctionName(readGeneratedStructDeclaredValue(context, fields[$index]), 
value.${field.name});',
+            );
           } else {
             output.writeln(
               '      value.${field.name} = 
$readerFunctionName(readGeneratedStructFieldInfoValue(context, fields[$index], 
value.${field.name}), value.${field.name});',
@@ -531,12 +539,6 @@ final class ForyGenerator extends Generator {
         output.writeln('    return value;');
       case _ConstructorMode.constructor:
         output.writeln('    final slots = generatedStructReadSlots(context);');
-        for (var index = 0; index < structSpec.fields.length; index += 1) {
-          final field = structSpec.fields[index];
-          output.writeln(
-            '    late final ${field.displayType} ${field.localName};',
-          );
-        }
         output.writeln('    if (slots == null) {');
         if (hasDirectPrimitiveFastPath || directCursorRuns.isNotEmpty) {
           output.writeln('      final buffer = context.buffer;');
@@ -555,19 +557,27 @@ final class ForyGenerator extends Generator {
           }
           if (_usesReservedGeneratedFastPath(field)) {
             output.writeln(
-              '      ${field.localName} = 
${_directGeneratedCursorReadExpression(field, 
'cursor${directCursorStartByIndex[index]}')};',
+              '      final ${field.displayType} ${field.localName} = 
${_directGeneratedCursorReadExpression(field, 
'cursor${directCursorStartByIndex[index]}')};',
             );
           } else if (_usesDirectGeneratedBasicFastPath(field)) {
             output.writeln(
-              '      ${field.localName} = 
${_directGeneratedReadExpression(field)};',
+              '      final ${field.displayType} ${field.localName} = 
${_directGeneratedReadExpression(field)};',
             );
           } else if (_usesDirectGeneratedTypedContainerReadFastPath(field)) {
             output.writeln(
-              '      ${field.localName} = 
${_directGeneratedTypedContainerReadExpression(structSpec.name, field, 
'fields[$index]')};',
+              '      final ${field.displayType} ${field.localName} = 
${_directGeneratedTypedContainerReadExpression(structSpec.name, field, 
'fields[$index]')};',
+            );
+          } else if (_usesDirectGeneratedStructFieldFastPath(field)) {
+            output.writeln(
+              '      final ${field.displayType} ${field.localName} = 
$readerFunctionName(readGeneratedStructDirectValue(context, fields[$index]));',
+            );
+          } else if (_usesDirectGeneratedDeclaredReadFastPath(field)) {
+            output.writeln(
+              '      final ${field.displayType} ${field.localName} = 
$readerFunctionName(readGeneratedStructDeclaredValue(context, 
fields[$index]));',
             );
           } else {
             output.writeln(
-              '      ${field.localName} = 
$readerFunctionName(readGeneratedStructFieldInfoValue(context, 
fields[$index]));',
+              '      final ${field.displayType} ${field.localName} = 
$readerFunctionName(readGeneratedStructFieldInfoValue(context, 
fields[$index]));',
             );
           }
           final directCursorEndRun = directCursorRunByEnd[index];
@@ -575,28 +585,45 @@ final class ForyGenerator extends Generator {
             output.writeln('      
cursor${directCursorEndRun.start}.finish();');
           }
         }
-        output.writeln('    } else {');
+        final constructorInvocation = _constructorInvocation(structSpec);
+        output
+          ..writeln('      final value = $constructorInvocation;')
+          ..writeln('      context.reference(value);');
+        for (final fieldName
+            in structSpec.constructorPlan.postConstructionFieldNames) {
+          final field =
+              structSpec.fields.firstWhere((item) => item.name == fieldName);
+          output.writeln('      value.${field.name} = ${field.localName};');
+        }
+        output.writeln('      return value;');
+        // Slow path: schema-evolution slots present. Use `late final` so each
+        // field can be conditionally assigned from its slot or read fresh.
+        output.writeln('    }');
+        for (var index = 0; index < structSpec.fields.length; index += 1) {
+          final field = structSpec.fields[index];
+          output.writeln(
+            '    late final ${field.displayType} ${field.localName};',
+          );
+        }
         for (var index = 0; index < structSpec.fields.length; index += 1) {
           final field = structSpec.fields[index];
           final readerFunctionName = field.readerFunctionName(structSpec.name);
           final rawValueName = 'raw${structSpec.name}$index';
           output.writeln(
-            '      if (slots.containsSlot($index)) {',
+            '    if (slots.containsSlot($index)) {',
           );
           output.writeln(
-            '        final $rawValueName = slots.valueForSlot($index);',
+            '      final $rawValueName = slots.valueForSlot($index);',
           );
           output.writeln(
-            '        ${field.localName} = 
$readerFunctionName(${_slotResolvedRawExpression(rawValueName)});',
+            '      ${field.localName} = 
$readerFunctionName(${_slotResolvedRawExpression(rawValueName)});',
           );
-          output.writeln('      } else {');
+          output.writeln('    } else {');
           output.writeln(
-            '        ${field.localName} = $readerFunctionName(null);',
+            '      ${field.localName} = $readerFunctionName(null);',
           );
-          output.writeln('      }');
+          output.writeln('    }');
         }
-        output.writeln('    }');
-        final constructorInvocation = _constructorInvocation(structSpec);
         output
           ..writeln('    final value = $constructorInvocation;')
           ..writeln('    context.reference(value);');
@@ -939,6 +966,31 @@ GeneratedFieldType(
         field.fieldType.typeId == TypeIds.enumById;
   }
 
+  bool _usesDirectGeneratedDeclaredReadFastPath(
+    _GeneratedFieldSpec field,
+  ) {
+    if (field.fieldType.nullable ||
+        field.fieldType.ref ||
+        field.fieldType.dynamic == true) {
+      return false;
+    }
+    final typeId = field.fieldType.typeId;
+    return typeId == TypeIds.ext || typeId == TypeIds.namedExt;
+  }
+
+  bool _usesDirectGeneratedStructFieldFastPath(_GeneratedFieldSpec field) {
+    if (field.fieldType.nullable ||
+        field.fieldType.ref ||
+        field.fieldType.dynamic == true) {
+      return false;
+    }
+    final typeId = field.fieldType.typeId;
+    return typeId == TypeIds.struct ||
+        typeId == TypeIds.compatibleStruct ||
+        typeId == TypeIds.namedStruct ||
+        typeId == TypeIds.namedCompatibleStruct;
+  }
+
   bool _usesDirectGeneratedTypedContainerReadFastPath(
     _GeneratedFieldSpec field,
   ) {
diff --git a/dart/packages/fory/lib/src/codegen/generated_support.dart 
b/dart/packages/fory/lib/src/codegen/generated_support.dart
index ddaac6335..44ba64105 100644
--- a/dart/packages/fory/lib/src/codegen/generated_support.dart
+++ b/dart/packages/fory/lib/src/codegen/generated_support.dart
@@ -559,16 +559,12 @@ void registerGeneratedStruct<T>(
 
 @internal
 StructWriteSlots? generatedStructWriteSlots(WriteContext context) {
-  return context.getContextObject<StructWriteSlots>(
-    structWriteSlotsKey,
-  );
+  return context.structWriteSlots;
 }
 
 @internal
 StructReadSlots? generatedStructReadSlots(ReadContext context) {
-  return context.getContextObject<StructReadSlots>(
-    structReadSlotsKey,
-  );
+  return context.structReadSlots;
 }
 
 @internal
@@ -687,6 +683,40 @@ Object? readGeneratedStructFieldInfoValue(
   return readFieldValue(context, field, fallback);
 }
 
+@internal
+Object? readGeneratedStructDeclaredValue(
+  ReadContext context,
+  GeneratedStructFieldInfo field,
+) {
+  final resolved = fieldDeclaredTypeInfo(context.typeResolver, field)!;
+  if (fieldUsesDeclaredType(context.typeResolver, field)) {
+    return context.readResolvedValue(resolved, field.fieldType);
+  }
+  final actualResolved = context.readTypeMetaValue(
+    resolved.isNamed ? resolved : null,
+  );
+  return context.readResolvedValue(actualResolved, field.fieldType);
+}
+
+@internal
+Object readGeneratedStructDirectValue(
+  ReadContext context,
+  GeneratedStructFieldInfo field,
+) {
+  final declared = fieldDeclaredTypeInfo(context.typeResolver, field)!;
+  final resolver.TypeInfo resolved;
+  if (fieldUsesDeclaredType(context.typeResolver, field)) {
+    resolved = declared;
+  } else {
+    resolved =
+        context.readTypeMetaValue(declared.isNamed ? declared : null);
+  }
+  context.increaseDepth();
+  final value = resolved.structSerializer!.readValue(context, resolved);
+  context.decreaseDepth();
+  return value;
+}
+
 @internal
 List<T> readGeneratedDirectListValue<T>(
   ReadContext context,
diff --git a/dart/packages/fory/lib/src/context/read_context.dart 
b/dart/packages/fory/lib/src/context/read_context.dart
index ec78c2f7d..f70a79f4e 100644
--- a/dart/packages/fory/lib/src/context/read_context.dart
+++ b/dart/packages/fory/lib/src/context/read_context.dart
@@ -12,6 +12,7 @@ import 'package:fory/src/serializer/map_serializers.dart';
 import 'package:fory/src/serializer/primitive_serializers.dart';
 import 'package:fory/src/serializer/scalar_serializers.dart';
 import 'package:fory/src/serializer/serializer.dart';
+import 'package:fory/src/serializer/struct_slots.dart';
 import 'package:fory/src/serializer/typed_array_serializers.dart';
 import 'package:fory/src/types/float16.dart';
 
@@ -29,7 +30,7 @@ final class ReadContext {
 
   late Buffer _buffer;
   final List<TypeInfo> _sharedTypes = <TypeInfo>[];
-  final Map<Object, Object?> _contextObjects = <Object, Object?>{};
+  StructReadSlots? _structReadSlots;
   int _depth = 0;
 
   @internal
@@ -50,7 +51,7 @@ final class ReadContext {
     _sharedTypes.clear();
     _refReader.reset();
     _metaStringReader.reset();
-    _contextObjects.clear();
+    _structReadSlots = null;
     _depth = 0;
   }
 
@@ -64,33 +65,11 @@ final class ReadContext {
   RefReader get refReader => _refReader;
 
   @internal
-  bool hasContextObject(Object key) {
-    return _contextObjects.containsKey(key);
-  }
-
-  @internal
-  T? getContextObject<T>(Object key) {
-    return _contextObjects[key] as T?;
-  }
+  StructReadSlots? get structReadSlots => _structReadSlots;
 
   @internal
-  Object? replaceContextObject(Object key, Object? next) {
-    final previous = _contextObjects[key];
-    if (next == null) {
-      _contextObjects.remove(key);
-    } else {
-      _contextObjects[key] = next;
-    }
-    return previous;
-  }
-
-  @internal
-  void restoreContextObject(Object key, Object? previous) {
-    if (previous == null) {
-      _contextObjects.remove(key);
-    } else {
-      _contextObjects[key] = previous;
-    }
+  set structReadSlots(StructReadSlots? value) {
+    _structReadSlots = value;
   }
 
   @internal
diff --git a/dart/packages/fory/lib/src/context/write_context.dart 
b/dart/packages/fory/lib/src/context/write_context.dart
index f039e2f42..d803f87c9 100644
--- a/dart/packages/fory/lib/src/context/write_context.dart
+++ b/dart/packages/fory/lib/src/context/write_context.dart
@@ -15,6 +15,7 @@ import 
'package:fory/src/serializer/collection_serializers.dart';
 import 'package:fory/src/serializer/map_serializers.dart';
 import 'package:fory/src/serializer/primitive_serializers.dart';
 import 'package:fory/src/serializer/scalar_serializers.dart';
+import 'package:fory/src/serializer/struct_slots.dart';
 import 'package:fory/src/serializer/typed_array_serializers.dart';
 import 'package:fory/src/types/float16.dart';
 import 'package:fory/src/types/local_date.dart';
@@ -35,8 +36,7 @@ final class WriteContext {
   late Buffer _buffer;
   final LinkedHashMap<TypeDef, int> _typeDefIds =
       LinkedHashMap<TypeDef, int>.identity();
-  final LinkedHashMap<Object, Object?> _contextObjects =
-      LinkedHashMap<Object, Object?>.identity();
+  StructWriteSlots? _structWriteSlots;
   bool _rootTrackRef = false;
   int _depth = 0;
 
@@ -59,7 +59,7 @@ final class WriteContext {
     _typeDefIds.clear();
     _refWriter.reset();
     _metaStringWriter.reset();
-    _contextObjects.clear();
+    _structWriteSlots = null;
     _rootTrackRef = false;
     _depth = 0;
   }
@@ -77,33 +77,11 @@ final class WriteContext {
   bool get rootTrackRef => _rootTrackRef;
 
   @internal
-  bool hasContextObject(Object key) {
-    return _contextObjects.containsKey(key);
-  }
+  StructWriteSlots? get structWriteSlots => _structWriteSlots;
 
   @internal
-  T? getContextObject<T>(Object key) {
-    return _contextObjects[key] as T?;
-  }
-
-  @internal
-  Object? replaceContextObject(Object key, Object? next) {
-    final previous = _contextObjects[key];
-    if (next == null) {
-      _contextObjects.remove(key);
-    } else {
-      _contextObjects[key] = next;
-    }
-    return previous;
-  }
-
-  @internal
-  void restoreContextObject(Object key, Object? previous) {
-    if (previous == null) {
-      _contextObjects.remove(key);
-    } else {
-      _contextObjects[key] = previous;
-    }
+  set structWriteSlots(StructWriteSlots? value) {
+    _structWriteSlots = value;
   }
 
   /// Records entry into one more nested write frame.
diff --git a/dart/packages/fory/lib/src/serializer/struct_serializer.dart 
b/dart/packages/fory/lib/src/serializer/struct_serializer.dart
index 4b5946606..6c7cdde37 100644
--- a/dart/packages/fory/lib/src/serializer/struct_serializer.dart
+++ b/dart/packages/fory/lib/src/serializer/struct_serializer.dart
@@ -92,10 +92,7 @@ final class StructSerializer extends Serializer<Object?> {
     final previousCompatibleFields =
         _replaceWriteSlots(internal, resolved, value);
     _payloadSerializer.write(context, value);
-    internal.restoreContextObject(
-      structWriteSlotsKey,
-      previousCompatibleFields,
-    );
+    internal.structWriteSlots = previousCompatibleFields;
   }
 
   Object readValue(
@@ -122,35 +119,29 @@ final class StructSerializer extends Serializer<Object?> {
         hasCurrentPreservedRef: hasCurrentPreservedRef,
       );
     }
-    final previousReadSlots = internal.replaceContextObject(
-      structReadSlotsKey,
-      null,
-    );
+    final previousReadSlots = internal.structReadSlots;
+    internal.structReadSlots = null;
     final value = internal.readSerializerPayload(
       _payloadSerializer,
       resolved,
       hasCurrentPreservedRef: hasCurrentPreservedRef,
     );
-    internal.restoreContextObject(
-      structReadSlotsKey,
-      previousReadSlots,
-    );
+    internal.structReadSlots = previousReadSlots;
     _rememberRemoteMetadata(internal, resolved, value);
     return value;
   }
 
-  Object? _replaceWriteSlots(
+  StructWriteSlots? _replaceWriteSlots(
     WriteContext context,
     TypeInfo resolved,
     Object value,
   ) {
     final layout = _compatibleWriteLayoutForValue(context, resolved, value);
-    return context.replaceContextObject(
-      structWriteSlotsKey,
-      layout == null
-          ? null
-          : StructWriteSlots(layout.fields, _localFields.length),
-    );
+    final previous = context.structWriteSlots;
+    context.structWriteSlots = layout == null
+        ? null
+        : StructWriteSlots(layout.fields, _localFields.length);
+    return previous;
   }
 
   _CompatibleWriteLayout? _compatibleWriteLayoutForValue(
@@ -256,19 +247,14 @@ final class StructSerializer extends Serializer<Object?> {
       );
       presentSlots[slot] = true;
     }
-    final previousCompatibleFields = context.replaceContextObject(
-      structReadSlotsKey,
-      StructReadSlots(compatibleValues, presentSlots),
-    );
+    final previousCompatibleFields = context.structReadSlots;
+    context.structReadSlots = StructReadSlots(compatibleValues, presentSlots);
     final value = context.readSerializerPayload(
       _payloadSerializer,
       resolved,
       hasCurrentPreservedRef: hasCurrentPreservedRef,
     );
-    context.restoreContextObject(
-      structReadSlotsKey,
-      previousCompatibleFields,
-    );
+    context.structReadSlots = previousCompatibleFields;
     _rememberRemoteMetadata(context, resolved, value);
     return value;
   }
diff --git a/dart/packages/fory/lib/src/serializer/struct_slots.dart 
b/dart/packages/fory/lib/src/serializer/struct_slots.dart
index 73b59f610..8bd2dd56d 100644
--- a/dart/packages/fory/lib/src/serializer/struct_slots.dart
+++ b/dart/packages/fory/lib/src/serializer/struct_slots.dart
@@ -1,12 +1,6 @@
 import 'package:meta/meta.dart';
 import 'package:fory/src/serializer/serialization_field_info.dart';
 
-@internal
-final Object structWriteSlotsKey = Object();
-
-@internal
-final Object structReadSlotsKey = Object();
-
 @internal
 final class StructWriteSlots {
   final List<SerializationFieldInfo> orderedFields;


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

Reply via email to