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 b71db8a18 perf(dart): typed-container write fast path with scan 
elimination (#3609)
b71db8a18 is described below

commit b71db8a187e2b56c82e572daea72120c0b359b68
Author: Yash Agarwal <[email protected]>
AuthorDate: Thu Apr 23 18:04:08 2026 +0530

    perf(dart): typed-container write fast path with scan elimination (#3609)
    
    ## Why?
    Writing `List<T>` / `Set<T>` struct fields paid an O(n) scan to detect
    heterogeneous types and nulls. Codegen already knows the element type is
    concrete and non-nullable — the scan is pure overhead.
    ## What does this PR do?
    - Adds `writeTypedListPayload<T>` / `writeTypedSetPayload<T>` that
    resolve `TypeInfo` once and write the header/type-meta without scanning.
    - Adds `writeGeneratedDirectListValue<T>` /
    `writeGeneratedDirectSetValue<T>` generated-code entry points.
    - Adds a codegen predicate (gated on real Dart `DartType` nullability)
    that emits the fast path for non-nullable, non-ref, non-dynamic list/set
    fields; everything else keeps the existing slow path.
    ## 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?
    No user-facing or wire-format change. Serialized bytes are
    byte-identical to main.
    
    ## Benchmark
    
    | Datatype | Operation | Fory TPS | Protobuf TPS | Fastest |
    | ---------------- | ----------- | --------: | -----------: |
    ------------ |
    | Struct | Serialize | 4,696,615 | 1,998,625 | fory (2.35x) |
    | Struct | Deserialize | 5,815,245 | 4,173,568 | fory (1.39x) |
    | Sample | Serialize | 1,744,871 | 481,801 | fory (3.62x) |
    | Sample | Deserialize | 2,007,877 | 780,317 | fory (2.57x) |
    | MediaContent | Serialize | 944,111 | 398,324 | fory (2.37x) |
    | MediaContent | Deserialize | 1,457,065 | 675,724 | fory (2.16x) |
    | StructList | Serialize | 1,981,716 | 351,853 | fory (5.63x) |
    | StructList | Deserialize | 2,261,436 | 596,027 | fory (3.79x) |
    | SampleList | Serialize | 426,153 | 46,590 | fory (9.15x) |
    | SampleList | Deserialize | 479,900 | 99,694 | fory (4.81x) |
    | MediaContentList | Serialize | 220,342 | 76,330 | fory (2.89x) |
    | MediaContentList | Deserialize | 341,839 | 131,730 | fory (2.60x) |
---
 .../fory/lib/src/codegen/fory_generator.dart       |  42 ++++++++++++++++++
 .../fory/lib/src/codegen/generated_support.dart    |  42 ++++++++++++++++++
 .../lib/src/serializer/collection_serializers.dart |  48 +++++++++++++++++++++
 docs/benchmarks/dart/README.md                     |  46 ++++++++++----------
 docs/benchmarks/dart/mediacontent.png              | Bin 42586 -> 44517 bytes
 docs/benchmarks/dart/mediacontentlist.png          | Bin 53715 -> 51468 bytes
 docs/benchmarks/dart/sample.png                    | Bin 46415 -> 46918 bytes
 docs/benchmarks/dart/samplelist.png                | Bin 45351 -> 53733 bytes
 docs/benchmarks/dart/struct.png                    | Bin 32985 -> 32100 bytes
 docs/benchmarks/dart/structlist.png                | Bin 48417 -> 42590 bytes
 docs/benchmarks/dart/throughput.png                | Bin 87138 -> 79300 bytes
 11 files changed, 155 insertions(+), 23 deletions(-)

diff --git a/dart/packages/fory/lib/src/codegen/fory_generator.dart 
b/dart/packages/fory/lib/src/codegen/fory_generator.dart
index c6e9ad581..ae20f05ab 100644
--- a/dart/packages/fory/lib/src/codegen/fory_generator.dart
+++ b/dart/packages/fory/lib/src/codegen/fory_generator.dart
@@ -542,6 +542,10 @@ final class ForyGenerator extends Generator {
         output.writeln(
           '      ${_directGeneratedWriteStatement(field, 
'value.${field.name}')};',
         );
+      } else if (_usesDirectGeneratedTypedContainerWriteFastPath(field)) {
+        output.writeln(
+          '      ${_directGeneratedTypedContainerWriteStatement(field, index, 
'value.${field.name}')};',
+        );
       } else {
         final fieldValue = _generatedFieldInfoWriteValueExpression(
           field,
@@ -1150,6 +1154,44 @@ GeneratedFieldType(
         field.fieldType.typeId == TypeIds.map;
   }
 
+  bool _usesDirectGeneratedTypedContainerWriteFastPath(
+    _GeneratedFieldSpec field,
+  ) {
+    if (field.fieldType.nullable ||
+        field.fieldType.ref ||
+        field.fieldType.dynamic == true) {
+      return false;
+    }
+    final typeId = field.fieldType.typeId;
+    if (typeId != TypeIds.list && typeId != TypeIds.set) {
+      return false;
+    }
+    final elementFieldType = field.fieldType.arguments.single;
+    if (elementFieldType.ref || elementFieldType.dynamic == true) {
+      return false;
+    }
+    final elementType = (field.type as InterfaceType).typeArguments.single;
+    return !_isNullable(elementType);
+  }
+
+  String _directGeneratedTypedContainerWriteStatement(
+    _GeneratedFieldSpec field,
+    int fieldIndex,
+    String valueExpression,
+  ) {
+    if (_isList(field.type)) {
+      final elementType = (field.type as InterfaceType).typeArguments.single;
+      return 
'writeGeneratedDirectListValue<${_typeCodeString(elementType)}>(context, 
fields[$fieldIndex], $valueExpression)';
+    }
+    if (_isSet(field.type)) {
+      final elementType = (field.type as InterfaceType).typeArguments.single;
+      return 
'writeGeneratedDirectSetValue<${_typeCodeString(elementType)}>(context, 
fields[$fieldIndex], $valueExpression)';
+    }
+    throw StateError(
+      'Unsupported generated typed container write fast path for 
${field.name}.',
+    );
+  }
+
   List<_DirectGeneratedWriteReservationRun>
       _directGeneratedWriteReservationRuns(List<_GeneratedFieldSpec> fields) {
     final runs = <_DirectGeneratedWriteReservationRun>[];
diff --git a/dart/packages/fory/lib/src/codegen/generated_support.dart 
b/dart/packages/fory/lib/src/codegen/generated_support.dart
index 645a0b86f..d3ff7290d 100644
--- a/dart/packages/fory/lib/src/codegen/generated_support.dart
+++ b/dart/packages/fory/lib/src/codegen/generated_support.dart
@@ -458,6 +458,48 @@ Object readGeneratedStructDirectValue(
   return value;
 }
 
+@internal
+void writeGeneratedDirectListValue<T>(
+  WriteContext context,
+  GeneratedStructFieldInfo field,
+  List<T> value,
+) {
+  final fieldType = field.fieldType;
+  if (fieldType.typeId != TypeIds.list ||
+      fieldType.nullable ||
+      fieldType.ref ||
+      fieldType.isDynamic) {
+    throw StateError('Field ${field.name} is not a direct list path.');
+  }
+  final elementFieldType = fieldType.arguments.single;
+  if (elementFieldType.ref || elementFieldType.isDynamic) {
+    throw StateError(
+        'Field ${field.name} element type is not a direct list path.');
+  }
+  writeTypedListPayload<T>(context, value, elementFieldType);
+}
+
+@internal
+void writeGeneratedDirectSetValue<T>(
+  WriteContext context,
+  GeneratedStructFieldInfo field,
+  Set<T> value,
+) {
+  final fieldType = field.fieldType;
+  if (fieldType.typeId != TypeIds.set ||
+      fieldType.nullable ||
+      fieldType.ref ||
+      fieldType.isDynamic) {
+    throw StateError('Field ${field.name} is not a direct set path.');
+  }
+  final elementFieldType = fieldType.arguments.single;
+  if (elementFieldType.ref || elementFieldType.isDynamic) {
+    throw StateError(
+        'Field ${field.name} element type is not a direct set path.');
+  }
+  writeTypedSetPayload<T>(context, value, elementFieldType);
+}
+
 @internal
 @pragma('vm:prefer-inline')
 List<T> readGeneratedDirectListValue<T>(
diff --git a/dart/packages/fory/lib/src/serializer/collection_serializers.dart 
b/dart/packages/fory/lib/src/serializer/collection_serializers.dart
index f65feb723..acec8fce4 100644
--- a/dart/packages/fory/lib/src/serializer/collection_serializers.dart
+++ b/dart/packages/fory/lib/src/serializer/collection_serializers.dart
@@ -475,6 +475,54 @@ Set<T> readTypedSetPayload<T>(
   return Set<T>.of(readTypedListPayload(context, elementFieldType, convert));
 }
 
+void writeTypedListPayload<T>(
+  WriteContext context,
+  List<T> values,
+  FieldType elementFieldType,
+) {
+  final size = values.length;
+  if (size > context.config.maxCollectionSize) {
+    throw StateError(
+      'Collection size $size exceeds ${context.config.maxCollectionSize}.',
+    );
+  }
+  context.buffer.writeVarUint32(size);
+  if (size == 0) return;
+  final declaredTypeInfo = 
context.typeResolver.resolveFieldType(elementFieldType);
+  final usesDeclaredType = usesDeclaredTypeInfo(
+    context.config.compatible,
+    elementFieldType,
+    declaredTypeInfo,
+  );
+  context.buffer.writeUint8(
+    _buildCollectionHeader(
+      trackRef: elementFieldType.ref,
+      hasNull: false,
+      usesDeclaredType: usesDeclaredType,
+      sameType: true,
+    ),
+  );
+  if (!usesDeclaredType) {
+    context.writeTypeMetaValue(declaredTypeInfo, values.first as Object);
+  }
+  _writeSameTypeElements(
+    context,
+    values,
+    declaredTypeInfo,
+    usesDeclaredType ? elementFieldType : null,
+    elementFieldType.ref,
+    false,
+  );
+}
+
+void writeTypedSetPayload<T>(
+  WriteContext context,
+  Set<T> values,
+  FieldType elementFieldType,
+) {
+  writeTypedListPayload<T>(context, values.toList(growable: false), 
elementFieldType);
+}
+
 int _buildCollectionHeader({
   required bool trackRef,
   required bool hasNull,
diff --git a/docs/benchmarks/dart/README.md b/docs/benchmarks/dart/README.md
index 2348854a2..cc8ae23ef 100644
--- a/docs/benchmarks/dart/README.md
+++ b/docs/benchmarks/dart/README.md
@@ -4,17 +4,17 @@ This benchmark compares serialization and deserialization 
throughput for Apache
 
 ## Hardware and Runtime Info
 
-| Key                   | Value                                                
             |
-| --------------------- | 
----------------------------------------------------------------- |
-| Timestamp             | 2026-04-23T10:50:07.751368Z                          
             |
-| OS                    | Version 15.7.2 (Build 24G325)                        
             |
-| Host                  | MacBook-Pro.local                                    
             |
-| CPU Cores (Logical)   | 12                                                   
             |
-| Memory (GB)           | 48.00                                                
             |
-| Dart                  | 3.10.7 (stable) (Tue Dec 23 00:01:57 2025 -0800) on 
"macos_arm64" |
-| Samples per case      | 5                                                    
             |
-| Warmup per case (s)   | 1.0                                                  
             |
-| Duration per case (s) | 1.5                                                  
             |
+| Key                   | Value                                                
            |
+| --------------------- | 
---------------------------------------------------------------- |
+| Timestamp             | 2026-04-23T12:21:28Z                                 
            |
+| OS                    | Version 26.2 (Build 25C56)                           
            |
+| Host                  | Macbook-Air.local                                    
            |
+| CPU Cores (Logical)   | 8                                                    
            |
+| Memory (GB)           | 8.00                                                 
            |
+| Dart                  | 3.10.4 (stable) (Tue Dec 9 00:01:55 2025 -0800) on 
"macos_arm64" |
+| Samples per case      | 5                                                    
            |
+| Warmup per case (s)   | 1.0                                                  
            |
+| Duration per case (s) | 1.5                                                  
            |
 
 ## Throughput Results
 
@@ -22,18 +22,18 @@ This benchmark compares serialization and deserialization 
throughput for Apache
 
 | Datatype         | Operation   |  Fory TPS | Protobuf TPS | Fastest      |
 | ---------------- | ----------- | --------: | -----------: | ------------ |
-| Struct           | Serialize   | 5,041,693 |    2,073,839 | fory (2.43x) |
-| Struct           | Deserialize | 6,395,290 |    4,991,881 | fory (1.28x) |
-| Sample           | Serialize   | 1,783,688 |      552,140 | fory (3.23x) |
-| Sample           | Deserialize | 2,124,197 |      934,794 | fory (2.27x) |
-| MediaContent     | Serialize   |   952,498 |      438,419 | fory (2.17x) |
-| MediaContent     | Deserialize | 1,649,039 |      737,340 | fory (2.24x) |
-| StructList       | Serialize   | 1,945,119 |      399,007 | fory (4.87x) |
-| StructList       | Deserialize | 2,119,403 |      764,832 | fory (2.77x) |
-| SampleList       | Serialize   |   475,413 |       52,512 | fory (9.05x) |
-| SampleList       | Deserialize |   508,939 |      116,236 | fory (4.38x) |
-| MediaContentList | Serialize   |   224,925 |       84,860 | fory (2.65x) |
-| MediaContentList | Deserialize |   387,070 |      154,392 | fory (2.51x) |
+| Struct           | Serialize   | 4,696,615 |    1,998,625 | fory (2.35x) |
+| Struct           | Deserialize | 5,815,245 |    4,173,568 | fory (1.39x) |
+| Sample           | Serialize   | 1,744,871 |      481,801 | fory (3.62x) |
+| Sample           | Deserialize | 2,007,877 |      780,317 | fory (2.57x) |
+| MediaContent     | Serialize   |   944,111 |      398,324 | fory (2.37x) |
+| MediaContent     | Deserialize | 1,457,065 |      675,724 | fory (2.16x) |
+| StructList       | Serialize   | 1,981,716 |      351,853 | fory (5.63x) |
+| StructList       | Deserialize | 2,261,436 |      596,027 | fory (3.79x) |
+| SampleList       | Serialize   |   426,153 |       46,590 | fory (9.15x) |
+| SampleList       | Deserialize |   479,900 |       99,694 | fory (4.81x) |
+| MediaContentList | Serialize   |   220,342 |       76,330 | fory (2.89x) |
+| MediaContentList | Deserialize |   341,839 |      131,730 | fory (2.60x) |
 
 ## Serialized Size (bytes)
 
diff --git a/docs/benchmarks/dart/mediacontent.png 
b/docs/benchmarks/dart/mediacontent.png
index ba6e2d3ae..16f9b3a67 100644
Binary files a/docs/benchmarks/dart/mediacontent.png and 
b/docs/benchmarks/dart/mediacontent.png differ
diff --git a/docs/benchmarks/dart/mediacontentlist.png 
b/docs/benchmarks/dart/mediacontentlist.png
index 02f785e42..f88d84a9f 100644
Binary files a/docs/benchmarks/dart/mediacontentlist.png and 
b/docs/benchmarks/dart/mediacontentlist.png differ
diff --git a/docs/benchmarks/dart/sample.png b/docs/benchmarks/dart/sample.png
index f274160b4..bd182feab 100644
Binary files a/docs/benchmarks/dart/sample.png and 
b/docs/benchmarks/dart/sample.png differ
diff --git a/docs/benchmarks/dart/samplelist.png 
b/docs/benchmarks/dart/samplelist.png
index 72e6cf7b0..a3b763501 100644
Binary files a/docs/benchmarks/dart/samplelist.png and 
b/docs/benchmarks/dart/samplelist.png differ
diff --git a/docs/benchmarks/dart/struct.png b/docs/benchmarks/dart/struct.png
index f04b50825..84ac444fe 100644
Binary files a/docs/benchmarks/dart/struct.png and 
b/docs/benchmarks/dart/struct.png differ
diff --git a/docs/benchmarks/dart/structlist.png 
b/docs/benchmarks/dart/structlist.png
index 0bf1fd0df..1ed88798e 100644
Binary files a/docs/benchmarks/dart/structlist.png and 
b/docs/benchmarks/dart/structlist.png differ
diff --git a/docs/benchmarks/dart/throughput.png 
b/docs/benchmarks/dart/throughput.png
index d41f05638..f3e6ace25 100644
Binary files a/docs/benchmarks/dart/throughput.png and 
b/docs/benchmarks/dart/throughput.png differ


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

Reply via email to