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 6f5974822 perf: optimize schema evolution mode performance (#3260)
6f5974822 is described below
commit 6f5974822db4a9396957bac8cc01b77d1e00025c
Author: Shawn Yang <[email protected]>
AuthorDate: Thu Feb 5 15:54:14 2026 +0800
perf: optimize schema evolution mode performance (#3260)
## Why?
Improve schema evolution/compatible mode throughput by reducing
type-meta lookup overhead and enabling fast-path reads when schemas
match, and extend benchmarks to cover list workloads.
## What does this PR do?
- C++: optimize compatible-mode type-meta sharing/caching and add a
fast-path struct read when schema hashes match; align benchmarks with
field tags and compatible mode.
- Go: speed up compatible-mode type-meta sharing (uintptr key +
first-type fast path), cache TypeInfo for struct/pointer fields and
typedefs, and extend benchmarks/reporting for list workloads.
- Rust: add schema hash and fast type/meta lookup caches, use a faster
vec reader and type-info path, and tag benchmark models + add a
benchmark runner script.
## Related issues
#1017
#2906
#2982
## 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
---
.gitignore | 2 +
benchmarks/cpp_benchmark/benchmark.cc | 506 +++++++++++++++++++--
benchmarks/cpp_benchmark/benchmark_report.py | 39 +-
benchmarks/go_benchmark/README.md | 18 +-
benchmarks/go_benchmark/benchmark_report.py | 107 ++++-
benchmarks/go_benchmark/benchmark_test.go | 334 ++++++++++++++
benchmarks/go_benchmark/models.go | 134 ++++--
benchmarks/go_benchmark/proto_convert.go | 63 +++
benchmarks/go_benchmark/run.sh | 15 +-
benchmarks/proto/bench.proto | 8 +
.../rust_benchmark/benches/threaded_bench.rs | 9 +
benchmarks/rust_benchmark/run.sh | 202 ++++++++
benchmarks/rust_benchmark/src/models/complex.rs | 26 ++
benchmarks/rust_benchmark/src/models/medium.rs | 14 +
benchmarks/rust_benchmark/src/models/realworld.rs | 25 +
benchmarks/rust_benchmark/src/models/simple.rs | 8 +
cpp/fory/serialization/context.cc | 105 ++++-
cpp/fory/serialization/context.h | 17 +-
cpp/fory/serialization/struct_serializer.h | 127 +++++-
cpp/fory/serialization/struct_test.cc | 48 ++
cpp/fory/serialization/type_resolver.cc | 20 +-
cpp/fory/serialization/type_resolver.h | 6 +
cpp/fory/serialization/xlang_test_main.cc | 19 -
go/fory/field_info.go | 5 +-
go/fory/fory.go | 2 +-
go/fory/pointer.go | 41 ++
go/fory/struct.go | 52 +++
go/fory/struct_init.go | 40 +-
go/fory/type_def.go | 13 +
go/fory/type_resolver.go | 123 +++--
rust/fory-core/src/error.rs | 17 +-
rust/fory-core/src/meta/type_meta.rs | 56 +++
rust/fory-core/src/resolver/meta_resolver.rs | 15 +
rust/fory-core/src/resolver/type_resolver.rs | 109 ++++-
rust/fory-core/src/serializer/collection.rs | 91 ++++
rust/fory-core/src/serializer/list.rs | 4 +-
rust/fory-core/src/serializer/struct_.rs | 33 +-
rust/fory-derive/src/object/read.rs | 41 +-
38 files changed, 2275 insertions(+), 219 deletions(-)
diff --git a/.gitignore b/.gitignore
index 61c8731f9..f36ebadb6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -104,3 +104,5 @@ examples/cpp/cmake_example/build
**/results/
benchmarks/**/report/
ignored/**
+ci-logs/**
+**/*.log
\ No newline at end of file
diff --git a/benchmarks/cpp_benchmark/benchmark.cc
b/benchmarks/cpp_benchmark/benchmark.cc
index 4972a5462..3cfa461c2 100644
--- a/benchmarks/cpp_benchmark/benchmark.cc
+++ b/benchmarks/cpp_benchmark/benchmark.cc
@@ -48,6 +48,8 @@ struct NumericStruct {
}
};
FORY_STRUCT(NumericStruct, f1, f2, f3, f4, f5, f6, f7, f8);
+FORY_FIELD_TAGS(NumericStruct, (f1, 1), (f2, 2), (f3, 3), (f4, 4), (f5, 5),
+ (f6, 6), (f7, 7), (f8, 8));
struct Sample {
int32_t int_value;
@@ -99,6 +101,14 @@ FORY_STRUCT(Sample, int_value, long_value, float_value,
double_value,
short_value_boxed, char_value_boxed, boolean_value_boxed,
int_array,
long_array, float_array, double_array, short_array, char_array,
boolean_array, string);
+FORY_FIELD_TAGS(Sample, (int_value, 1), (long_value, 2), (float_value, 3),
+ (double_value, 4), (short_value, 5), (char_value, 6),
+ (boolean_value, 7), (int_value_boxed, 8), (long_value_boxed,
9),
+ (float_value_boxed, 10), (double_value_boxed, 11),
+ (short_value_boxed, 12), (char_value_boxed, 13),
+ (boolean_value_boxed, 14), (int_array, 15), (long_array, 16),
+ (float_array, 17), (double_array, 18), (short_array, 19),
+ (char_array, 20), (boolean_array, 21), (string, 22));
// Enums for MediaContent benchmark
enum class Player : int32_t { JAVA = 0, FLASH = 1 };
@@ -130,6 +140,9 @@ struct Media {
};
FORY_STRUCT(Media, uri, title, width, height, format, duration, size, bitrate,
has_bitrate, persons, player, copyright);
+FORY_FIELD_TAGS(Media, (uri, 1), (title, 2), (width, 3), (height, 4),
+ (format, 5), (duration, 6), (size, 7), (bitrate, 8),
+ (has_bitrate, 9), (persons, 10), (player, 11), (copyright,
12));
struct Image {
std::string uri;
@@ -144,6 +157,8 @@ struct Image {
}
};
FORY_STRUCT(Image, uri, title, width, height, size);
+FORY_FIELD_TAGS(Image, (uri, 1), (title, 2), (width, 3), (height, 4),
+ (size, 5));
struct MediaContent {
Media media;
@@ -154,6 +169,37 @@ struct MediaContent {
}
};
FORY_STRUCT(MediaContent, media, images);
+FORY_FIELD_TAGS(MediaContent, (media, 1), (images, 2));
+
+struct StructList {
+ std::vector<NumericStruct> struct_list;
+
+ bool operator==(const StructList &other) const {
+ return struct_list == other.struct_list;
+ }
+};
+FORY_STRUCT(StructList, struct_list);
+FORY_FIELD_TAGS(StructList, (struct_list, 1));
+
+struct SampleList {
+ std::vector<Sample> sample_list;
+
+ bool operator==(const SampleList &other) const {
+ return sample_list == other.sample_list;
+ }
+};
+FORY_STRUCT(SampleList, sample_list);
+FORY_FIELD_TAGS(SampleList, (sample_list, 1));
+
+struct MediaContentList {
+ std::vector<MediaContent> media_content_list;
+
+ bool operator==(const MediaContentList &other) const {
+ return media_content_list == other.media_content_list;
+ }
+};
+FORY_STRUCT(MediaContentList, media_content_list);
+FORY_FIELD_TAGS(MediaContentList, (media_content_list, 1));
// ============================================================================
// Test data creation
@@ -173,6 +219,8 @@ NumericStruct create_numeric_struct() {
};
}
+constexpr int kListSize = 5;
+
// ============================================================================
// Protobuf conversion functions (like Java benchmark's
// buildPBStruct/fromPBObject)
@@ -245,53 +293,85 @@ Sample create_sample() {
return sample;
}
-protobuf::Sample create_proto_sample() {
- // Consistent with Java Sample.populate() for fair cross-language comparison
+inline protobuf::Sample to_pb_sample(const Sample &obj) {
protobuf::Sample sample;
- sample.set_int_value(123);
- sample.set_long_value(1230000LL);
- sample.set_float_value(12.345f);
- sample.set_double_value(1.234567);
- sample.set_short_value(12345);
- sample.set_char_value('!'); // 33
- sample.set_boolean_value(true);
-
- sample.set_int_value_boxed(321);
- sample.set_long_value_boxed(3210000LL);
- sample.set_float_value_boxed(54.321f);
- sample.set_double_value_boxed(7.654321);
- sample.set_short_value_boxed(32100);
- sample.set_char_value_boxed('$'); // 36
- sample.set_boolean_value_boxed(false);
-
- // Arrays with mixed positive/negative values (same as Java)
- for (int v : {-1234, -123, -12, -1, 0, 1, 12, 123, 1234}) {
+ sample.set_int_value(obj.int_value);
+ sample.set_long_value(obj.long_value);
+ sample.set_float_value(obj.float_value);
+ sample.set_double_value(obj.double_value);
+ sample.set_short_value(obj.short_value);
+ sample.set_char_value(obj.char_value);
+ sample.set_boolean_value(obj.boolean_value);
+
+ sample.set_int_value_boxed(obj.int_value_boxed);
+ sample.set_long_value_boxed(obj.long_value_boxed);
+ sample.set_float_value_boxed(obj.float_value_boxed);
+ sample.set_double_value_boxed(obj.double_value_boxed);
+ sample.set_short_value_boxed(obj.short_value_boxed);
+ sample.set_char_value_boxed(obj.char_value_boxed);
+ sample.set_boolean_value_boxed(obj.boolean_value_boxed);
+
+ for (int32_t v : obj.int_array) {
sample.add_int_array(v);
}
- for (int64_t v : {-123400LL, -12300LL, -1200LL, -100LL, 0LL, 100LL, 1200LL,
- 12300LL, 123400LL}) {
+ for (int64_t v : obj.long_array) {
sample.add_long_array(v);
}
- for (float v :
- {-12.34f, -12.3f, -12.0f, -1.0f, 0.0f, 1.0f, 12.0f, 12.3f, 12.34f}) {
+ for (float v : obj.float_array) {
sample.add_float_array(v);
}
- for (double v : {-1.234, -1.23, -12.0, -1.0, 0.0, 1.0, 12.0, 1.23, 1.234}) {
+ for (double v : obj.double_array) {
sample.add_double_array(v);
}
- for (int v : {-1234, -123, -12, -1, 0, 1, 12, 123, 1234}) {
+ for (int32_t v : obj.short_array) {
sample.add_short_array(v);
}
- for (int v : {'a', 's', 'd', 'f', 'A', 'S', 'D', 'F'}) { // "asdfASDF"
+ for (int32_t v : obj.char_array) {
sample.add_char_array(v);
}
- for (bool v : {true, false, false, true}) {
+ for (bool v : obj.boolean_array) {
sample.add_boolean_array(v);
}
- sample.set_string("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
+ sample.set_string(obj.string);
+ return sample;
+}
+
+inline Sample from_pb_sample(const protobuf::Sample &pb) {
+ Sample sample;
+ sample.int_value = pb.int_value();
+ sample.long_value = pb.long_value();
+ sample.float_value = pb.float_value();
+ sample.double_value = pb.double_value();
+ sample.short_value = pb.short_value();
+ sample.char_value = pb.char_value();
+ sample.boolean_value = pb.boolean_value();
+
+ sample.int_value_boxed = pb.int_value_boxed();
+ sample.long_value_boxed = pb.long_value_boxed();
+ sample.float_value_boxed = pb.float_value_boxed();
+ sample.double_value_boxed = pb.double_value_boxed();
+ sample.short_value_boxed = pb.short_value_boxed();
+ sample.char_value_boxed = pb.char_value_boxed();
+ sample.boolean_value_boxed = pb.boolean_value_boxed();
+
+ sample.int_array.assign(pb.int_array().begin(), pb.int_array().end());
+ sample.long_array.assign(pb.long_array().begin(), pb.long_array().end());
+ sample.float_array.assign(pb.float_array().begin(), pb.float_array().end());
+ sample.double_array.assign(pb.double_array().begin(),
+ pb.double_array().end());
+ sample.short_array.assign(pb.short_array().begin(), pb.short_array().end());
+ sample.char_array.assign(pb.char_array().begin(), pb.char_array().end());
+ sample.boolean_array.assign(pb.boolean_array().begin(),
+ pb.boolean_array().end());
+ sample.string = pb.string();
return sample;
}
+protobuf::Sample create_proto_sample() {
+ // Consistent with Java Sample.populate() for fair cross-language comparison
+ return to_pb_sample(create_sample());
+}
+
MediaContent create_media_content() {
// Matches Java MediaContent.populate(false) - no circular reference
MediaContent content;
@@ -410,6 +490,98 @@ protobuf::MediaContent create_proto_media_content() {
return to_pb_mediaContent(create_media_content());
}
+StructList create_struct_list() {
+ StructList list;
+ list.struct_list.reserve(kListSize);
+ for (int i = 0; i < kListSize; ++i) {
+ list.struct_list.push_back(create_numeric_struct());
+ }
+ return list;
+}
+
+SampleList create_sample_list() {
+ SampleList list;
+ list.sample_list.reserve(kListSize);
+ for (int i = 0; i < kListSize; ++i) {
+ list.sample_list.push_back(create_sample());
+ }
+ return list;
+}
+
+MediaContentList create_media_content_list() {
+ MediaContentList list;
+ list.media_content_list.reserve(kListSize);
+ for (int i = 0; i < kListSize; ++i) {
+ list.media_content_list.push_back(create_media_content());
+ }
+ return list;
+}
+
+inline protobuf::StructList to_pb_struct_list(const StructList &obj) {
+ protobuf::StructList pb;
+ for (const auto &item : obj.struct_list) {
+ *pb.add_struct_list() = to_pb_struct(item);
+ }
+ return pb;
+}
+
+inline StructList from_pb_struct_list(const protobuf::StructList &pb) {
+ StructList list;
+ list.struct_list.reserve(pb.struct_list_size());
+ for (const auto &item : pb.struct_list()) {
+ list.struct_list.push_back(from_pb_struct(item));
+ }
+ return list;
+}
+
+inline protobuf::SampleList to_pb_sample_list(const SampleList &obj) {
+ protobuf::SampleList pb;
+ for (const auto &item : obj.sample_list) {
+ *pb.add_sample_list() = to_pb_sample(item);
+ }
+ return pb;
+}
+
+inline SampleList from_pb_sample_list(const protobuf::SampleList &pb) {
+ SampleList list;
+ list.sample_list.reserve(pb.sample_list_size());
+ for (const auto &item : pb.sample_list()) {
+ list.sample_list.push_back(from_pb_sample(item));
+ }
+ return list;
+}
+
+inline protobuf::MediaContentList
+to_pb_media_content_list(const MediaContentList &obj) {
+ protobuf::MediaContentList pb;
+ for (const auto &item : obj.media_content_list) {
+ *pb.add_media_content_list() = to_pb_mediaContent(item);
+ }
+ return pb;
+}
+
+inline MediaContentList
+from_pb_media_content_list(const protobuf::MediaContentList &pb) {
+ MediaContentList list;
+ list.media_content_list.reserve(pb.media_content_list_size());
+ for (const auto &item : pb.media_content_list()) {
+ list.media_content_list.push_back(from_pb_mediaContent(item));
+ }
+ return list;
+}
+
+protobuf::StructList create_proto_struct_list() {
+ return to_pb_struct_list(create_struct_list());
+}
+
+protobuf::SampleList create_proto_sample_list() {
+ return to_pb_sample_list(create_sample_list());
+}
+
+protobuf::MediaContentList create_proto_media_content_list() {
+ return to_pb_media_content_list(create_media_content_list());
+}
+
// ============================================================================
// Helper to configure Fory instance
// ============================================================================
@@ -420,6 +592,9 @@ void register_fory_types(fory::serialization::Fory &fory) {
fory.register_struct<Media>(3);
fory.register_struct<Image>(4);
fory.register_struct<MediaContent>(5);
+ fory.register_struct<StructList>(6);
+ fory.register_struct<SampleList>(7);
+ fory.register_struct<MediaContentList>(8);
}
// ============================================================================
@@ -429,8 +604,8 @@ void register_fory_types(fory::serialization::Fory &fory) {
static void BM_Fory_Struct_Serialize(benchmark::State &state) {
auto fory = fory::serialization::Fory::builder()
.xlang(true)
+ .compatible(true)
.track_ref(false)
- .check_struct_version(false)
.build();
register_fory_types(fory);
NumericStruct obj = create_numeric_struct();
@@ -466,8 +641,8 @@ BENCHMARK(BM_Protobuf_Struct_Serialize);
static void BM_Fory_Struct_Deserialize(benchmark::State &state) {
auto fory = fory::serialization::Fory::builder()
.xlang(true)
+ .compatible(true)
.track_ref(false)
- .check_struct_version(false)
.build();
register_fory_types(fory);
NumericStruct obj = create_numeric_struct();
@@ -516,8 +691,8 @@ BENCHMARK(BM_Protobuf_Struct_Deserialize);
static void BM_Fory_Sample_Serialize(benchmark::State &state) {
auto fory = fory::serialization::Fory::builder()
.xlang(true)
+ .compatible(true)
.track_ref(false)
- .check_struct_version(false)
.build();
register_fory_types(fory);
Sample obj = create_sample();
@@ -550,8 +725,8 @@ BENCHMARK(BM_Protobuf_Sample_Serialize);
static void BM_Fory_Sample_Deserialize(benchmark::State &state) {
auto fory = fory::serialization::Fory::builder()
.xlang(true)
+ .compatible(true)
.track_ref(false)
- .check_struct_version(false)
.build();
register_fory_types(fory);
Sample obj = create_sample();
@@ -596,8 +771,8 @@ BENCHMARK(BM_Protobuf_Sample_Deserialize);
static void BM_Fory_MediaContent_Serialize(benchmark::State &state) {
auto fory = fory::serialization::Fory::builder()
.xlang(true)
+ .compatible(true)
.track_ref(false)
- .check_struct_version(false)
.build();
register_fory_types(fory);
MediaContent obj = create_media_content();
@@ -632,8 +807,8 @@ BENCHMARK(BM_Protobuf_MediaContent_Serialize);
static void BM_Fory_MediaContent_Deserialize(benchmark::State &state) {
auto fory = fory::serialization::Fory::builder()
.xlang(true)
+ .compatible(true)
.track_ref(false)
- .check_struct_version(false)
.build();
register_fory_types(fory);
MediaContent obj = create_media_content();
@@ -672,6 +847,243 @@ static void
BM_Protobuf_MediaContent_Deserialize(benchmark::State &state) {
}
BENCHMARK(BM_Protobuf_MediaContent_Deserialize);
+// ============================================================================
+// List benchmarks (StructList, SampleList, MediaContentList)
+// ============================================================================
+
+static void BM_Fory_StructList_Serialize(benchmark::State &state) {
+ auto fory = fory::serialization::Fory::builder()
+ .xlang(true)
+ .compatible(true)
+ .track_ref(false)
+ .build();
+ register_fory_types(fory);
+ StructList obj = create_struct_list();
+
+ fory::Buffer buffer;
+ buffer.reserve(65536);
+
+ for (auto _ : state) {
+ buffer.writer_index(0);
+ auto result = fory.serialize_to(buffer, obj);
+ benchmark::DoNotOptimize(result);
+ benchmark::DoNotOptimize(buffer.data());
+ }
+}
+BENCHMARK(BM_Fory_StructList_Serialize);
+
+static void BM_Protobuf_StructList_Serialize(benchmark::State &state) {
+ StructList obj = create_struct_list();
+ protobuf::StructList pb = to_pb_struct_list(obj);
+ std::vector<uint8_t> output;
+ output.resize(pb.ByteSizeLong());
+
+ for (auto _ : state) {
+ pb = to_pb_struct_list(obj);
+ pb.SerializeToArray(output.data(), static_cast<int>(output.size()));
+ benchmark::DoNotOptimize(output);
+ }
+}
+BENCHMARK(BM_Protobuf_StructList_Serialize);
+
+static void BM_Fory_StructList_Deserialize(benchmark::State &state) {
+ auto fory = fory::serialization::Fory::builder()
+ .xlang(true)
+ .compatible(true)
+ .track_ref(false)
+ .build();
+ register_fory_types(fory);
+ StructList obj = create_struct_list();
+ auto serialized = fory.serialize(obj);
+ if (!serialized.ok()) {
+ state.SkipWithError("Serialization failed");
+ return;
+ }
+ auto &bytes = serialized.value();
+
+ auto test_result = fory.deserialize<StructList>(bytes.data(), bytes.size());
+ if (!test_result.ok()) {
+ state.SkipWithError("Deserialization test failed");
+ return;
+ }
+
+ for (auto _ : state) {
+ auto result = fory.deserialize<StructList>(bytes.data(), bytes.size());
+ benchmark::DoNotOptimize(result);
+ }
+}
+BENCHMARK(BM_Fory_StructList_Deserialize);
+
+static void BM_Protobuf_StructList_Deserialize(benchmark::State &state) {
+ protobuf::StructList obj = create_proto_struct_list();
+ std::string serialized;
+ obj.SerializeToString(&serialized);
+
+ for (auto _ : state) {
+ protobuf::StructList pb_result;
+ pb_result.ParseFromString(serialized);
+ StructList result = from_pb_struct_list(pb_result);
+ benchmark::DoNotOptimize(result);
+ }
+}
+BENCHMARK(BM_Protobuf_StructList_Deserialize);
+
+static void BM_Fory_SampleList_Serialize(benchmark::State &state) {
+ auto fory = fory::serialization::Fory::builder()
+ .xlang(true)
+ .compatible(true)
+ .track_ref(false)
+ .build();
+ register_fory_types(fory);
+ SampleList obj = create_sample_list();
+
+ fory::Buffer buffer;
+ buffer.reserve(131072);
+
+ for (auto _ : state) {
+ buffer.writer_index(0);
+ auto result = fory.serialize_to(buffer, obj);
+ benchmark::DoNotOptimize(result);
+ benchmark::DoNotOptimize(buffer.data());
+ }
+}
+BENCHMARK(BM_Fory_SampleList_Serialize);
+
+static void BM_Protobuf_SampleList_Serialize(benchmark::State &state) {
+ SampleList obj = create_sample_list();
+ protobuf::SampleList pb = to_pb_sample_list(obj);
+ std::vector<uint8_t> output;
+ output.resize(pb.ByteSizeLong());
+
+ for (auto _ : state) {
+ pb = to_pb_sample_list(obj);
+ pb.SerializeToArray(output.data(), static_cast<int>(output.size()));
+ benchmark::DoNotOptimize(output);
+ }
+}
+BENCHMARK(BM_Protobuf_SampleList_Serialize);
+
+static void BM_Fory_SampleList_Deserialize(benchmark::State &state) {
+ auto fory = fory::serialization::Fory::builder()
+ .xlang(true)
+ .compatible(true)
+ .track_ref(false)
+ .build();
+ register_fory_types(fory);
+ SampleList obj = create_sample_list();
+ auto serialized = fory.serialize(obj);
+ if (!serialized.ok()) {
+ state.SkipWithError("Serialization failed");
+ return;
+ }
+ auto &bytes = serialized.value();
+
+ auto test_result = fory.deserialize<SampleList>(bytes.data(), bytes.size());
+ if (!test_result.ok()) {
+ state.SkipWithError("Deserialization test failed");
+ return;
+ }
+
+ for (auto _ : state) {
+ auto result = fory.deserialize<SampleList>(bytes.data(), bytes.size());
+ benchmark::DoNotOptimize(result);
+ }
+}
+BENCHMARK(BM_Fory_SampleList_Deserialize);
+
+static void BM_Protobuf_SampleList_Deserialize(benchmark::State &state) {
+ protobuf::SampleList obj = create_proto_sample_list();
+ std::string serialized;
+ obj.SerializeToString(&serialized);
+
+ for (auto _ : state) {
+ protobuf::SampleList pb_result;
+ pb_result.ParseFromString(serialized);
+ SampleList result = from_pb_sample_list(pb_result);
+ benchmark::DoNotOptimize(result);
+ }
+}
+BENCHMARK(BM_Protobuf_SampleList_Deserialize);
+
+static void BM_Fory_MediaContentList_Serialize(benchmark::State &state) {
+ auto fory = fory::serialization::Fory::builder()
+ .xlang(true)
+ .compatible(true)
+ .track_ref(false)
+ .build();
+ register_fory_types(fory);
+ MediaContentList obj = create_media_content_list();
+
+ fory::Buffer buffer;
+ buffer.reserve(131072);
+
+ for (auto _ : state) {
+ buffer.writer_index(0);
+ auto result = fory.serialize_to(buffer, obj);
+ benchmark::DoNotOptimize(result);
+ benchmark::DoNotOptimize(buffer.data());
+ }
+}
+BENCHMARK(BM_Fory_MediaContentList_Serialize);
+
+static void BM_Protobuf_MediaContentList_Serialize(benchmark::State &state) {
+ MediaContentList obj = create_media_content_list();
+ protobuf::MediaContentList pb = to_pb_media_content_list(obj);
+ std::vector<uint8_t> output;
+ output.resize(pb.ByteSizeLong());
+
+ for (auto _ : state) {
+ pb = to_pb_media_content_list(obj);
+ pb.SerializeToArray(output.data(), static_cast<int>(output.size()));
+ benchmark::DoNotOptimize(output);
+ }
+}
+BENCHMARK(BM_Protobuf_MediaContentList_Serialize);
+
+static void BM_Fory_MediaContentList_Deserialize(benchmark::State &state) {
+ auto fory = fory::serialization::Fory::builder()
+ .xlang(true)
+ .compatible(true)
+ .track_ref(false)
+ .build();
+ register_fory_types(fory);
+ MediaContentList obj = create_media_content_list();
+ auto serialized = fory.serialize(obj);
+ if (!serialized.ok()) {
+ state.SkipWithError("Serialization failed");
+ return;
+ }
+ auto &bytes = serialized.value();
+
+ auto test_result =
+ fory.deserialize<MediaContentList>(bytes.data(), bytes.size());
+ if (!test_result.ok()) {
+ state.SkipWithError("Deserialization test failed");
+ return;
+ }
+
+ for (auto _ : state) {
+ auto result =
+ fory.deserialize<MediaContentList>(bytes.data(), bytes.size());
+ benchmark::DoNotOptimize(result);
+ }
+}
+BENCHMARK(BM_Fory_MediaContentList_Deserialize);
+
+static void BM_Protobuf_MediaContentList_Deserialize(benchmark::State &state) {
+ protobuf::MediaContentList obj = create_proto_media_content_list();
+ std::string serialized;
+ obj.SerializeToString(&serialized);
+
+ for (auto _ : state) {
+ protobuf::MediaContentList pb_result;
+ pb_result.ParseFromString(serialized);
+ MediaContentList result = from_pb_media_content_list(pb_result);
+ benchmark::DoNotOptimize(result);
+ }
+}
+BENCHMARK(BM_Protobuf_MediaContentList_Deserialize);
+
// ============================================================================
// Serialized size comparison (printed once at the end)
// ============================================================================
@@ -680,25 +1092,39 @@ static void BM_PrintSerializedSizes(benchmark::State
&state) {
// Fory
auto fory = fory::serialization::Fory::builder()
.xlang(true)
+ .compatible(true)
.track_ref(false)
- .check_struct_version(false)
.build();
register_fory_types(fory);
NumericStruct fory_struct = create_numeric_struct();
Sample fory_sample = create_sample();
MediaContent fory_media = create_media_content();
+ StructList fory_struct_list = create_struct_list();
+ SampleList fory_sample_list = create_sample_list();
+ MediaContentList fory_media_list = create_media_content_list();
auto fory_struct_bytes = fory.serialize(fory_struct).value();
auto fory_sample_bytes = fory.serialize(fory_sample).value();
auto fory_media_bytes = fory.serialize(fory_media).value();
+ auto fory_struct_list_bytes = fory.serialize(fory_struct_list).value();
+ auto fory_sample_list_bytes = fory.serialize(fory_sample_list).value();
+ auto fory_media_list_bytes = fory.serialize(fory_media_list).value();
// Protobuf
protobuf::Struct proto_struct = create_proto_struct();
protobuf::Sample proto_sample = create_proto_sample();
protobuf::MediaContent proto_media = create_proto_media_content();
- std::string proto_struct_bytes, proto_sample_bytes, proto_media_bytes;
+ protobuf::StructList proto_struct_list = create_proto_struct_list();
+ protobuf::SampleList proto_sample_list = create_proto_sample_list();
+ protobuf::MediaContentList proto_media_list =
+ create_proto_media_content_list();
+ std::string proto_struct_bytes, proto_sample_bytes, proto_media_bytes,
+ proto_struct_list_bytes, proto_sample_list_bytes, proto_media_list_bytes;
proto_struct.SerializeToString(&proto_struct_bytes);
proto_sample.SerializeToString(&proto_sample_bytes);
proto_media.SerializeToString(&proto_media_bytes);
+ proto_struct_list.SerializeToString(&proto_struct_list_bytes);
+ proto_sample_list.SerializeToString(&proto_sample_list_bytes);
+ proto_media_list.SerializeToString(&proto_media_list_bytes);
for (auto _ : state) {
// Just run once to print sizes
@@ -710,5 +1136,11 @@ static void BM_PrintSerializedSizes(benchmark::State
&state) {
state.counters["proto_sample_size"] = proto_sample_bytes.size();
state.counters["fory_media_size"] = fory_media_bytes.size();
state.counters["proto_media_size"] = proto_media_bytes.size();
+ state.counters["fory_struct_list_size"] = fory_struct_list_bytes.size();
+ state.counters["proto_struct_list_size"] = proto_struct_list_bytes.size();
+ state.counters["fory_sample_list_size"] = fory_sample_list_bytes.size();
+ state.counters["proto_sample_list_size"] = proto_sample_list_bytes.size();
+ state.counters["fory_media_list_size"] = fory_media_list_bytes.size();
+ state.counters["proto_media_list_size"] = proto_media_list_bytes.size();
}
BENCHMARK(BM_PrintSerializedSizes)->Iterations(1);
diff --git a/benchmarks/cpp_benchmark/benchmark_report.py
b/benchmarks/cpp_benchmark/benchmark_report.py
index 4d9846c3b..e9dbd832f 100644
--- a/benchmarks/cpp_benchmark/benchmark_report.py
+++ b/benchmarks/cpp_benchmark/benchmark_report.py
@@ -97,6 +97,19 @@ def parse_benchmark_name(name):
return None, None, None
+def format_datatype_label(datatype):
+ if not datatype:
+ return ""
+ if datatype.endswith("list"):
+ base = datatype[: -len("list")]
+ if base == "mediacontent":
+ return "MediaContent\nList"
+ return f"{base.capitalize()}\nList"
+ if datatype == "mediacontent":
+ return "MediaContent"
+ return datatype.capitalize()
+
+
# === Read and parse benchmark JSON ===
def load_benchmark_data(json_file):
with open(json_file, "r", encoding="utf-8") as f:
@@ -127,6 +140,14 @@ for bench in benchmark_data.get("benchmarks", []):
"proto_struct_size",
"fory_sample_size",
"proto_sample_size",
+ "fory_media_size",
+ "proto_media_size",
+ "fory_struct_list_size",
+ "proto_struct_list_size",
+ "fory_sample_list_size",
+ "proto_sample_list_size",
+ "fory_media_list_size",
+ "proto_media_list_size",
]:
if key in bench:
sizes[key] = int(bench[key])
@@ -235,7 +256,7 @@ for idx, op in enumerate(operations):
ax.set_ylabel("Throughput (ops/sec)")
ax.set_title(f"{op.capitalize()} Throughput (higher is better)")
ax.set_xticks(x)
- ax.set_xticklabels([dt.capitalize() for dt in datatypes])
+ ax.set_xticklabels([format_datatype_label(dt) for dt in datatypes])
ax.legend()
ax.grid(True, axis="y", linestyle="--", alpha=0.5)
@@ -330,6 +351,22 @@ if sizes:
md_report.append(
f"| Sample | {sizes['fory_sample_size']} |
{sizes['proto_sample_size']} |\n"
)
+ if "fory_media_size" in sizes and "proto_media_size" in sizes:
+ md_report.append(
+ f"| MediaContent | {sizes['fory_media_size']} |
{sizes['proto_media_size']} |\n"
+ )
+ if "fory_struct_list_size" in sizes and "proto_struct_list_size" in sizes:
+ md_report.append(
+ f"| StructList | {sizes['fory_struct_list_size']} |
{sizes['proto_struct_list_size']} |\n"
+ )
+ if "fory_sample_list_size" in sizes and "proto_sample_list_size" in sizes:
+ md_report.append(
+ f"| SampleList | {sizes['fory_sample_list_size']} |
{sizes['proto_sample_list_size']} |\n"
+ )
+ if "fory_media_list_size" in sizes and "proto_media_list_size" in sizes:
+ md_report.append(
+ f"| MediaContentList | {sizes['fory_media_list_size']} |
{sizes['proto_media_list_size']} |\n"
+ )
# Save Markdown
report_path = os.path.join(output_dir, "REPORT.md")
diff --git a/benchmarks/go_benchmark/README.md
b/benchmarks/go_benchmark/README.md
index fbc467b4d..8c008711e 100644
--- a/benchmarks/go_benchmark/README.md
+++ b/benchmarks/go_benchmark/README.md
@@ -10,11 +10,14 @@ This directory contains benchmarks comparing [Apache
Fory](https://github.com/ap
## Data Types Benchmarked
-| Data Type | Description |
-| ------------- | ------------------------------------------------ |
-| NumericStruct | Simple struct with 8 int32 fields |
-| Sample | Complex struct with primitives and 7 array types |
-| MediaContent | Nested objects with strings, enums, and lists |
+| Data Type | Description |
+| ---------------- | ------------------------------------------------ |
+| NumericStruct | Simple struct with 8 int32 fields |
+| Sample | Complex struct with primitives and 7 array types |
+| MediaContent | Nested objects with strings, enums, and lists |
+| StructList | List of NumericStruct (20 elements) |
+| SampleList | List of Sample (20 elements) |
+| MediaContentList | List of MediaContent (20 elements) |
The benchmark data matches the C++ benchmark for cross-language comparison.
@@ -47,6 +50,9 @@ apt-get install protobuf-compiler
./run.sh --data struct
./run.sh --data sample
./run.sh --data mediacontent
+./run.sh --data structlist
+./run.sh --data samplelist
+./run.sh --data mediacontentlist
# Run specific serializer
./run.sh --serializer fory
@@ -96,7 +102,7 @@ Example results on Apple M1 Pro:
| Mediacontent | Serialize | 4.93M | 2.30M | 1.17M
| 2.14x | 4.21x |
| Mediacontent | Deserialize | 2.74M | 1.87M | 751K
| 1.46x | 3.65x |
-_Note: Results vary by hardware. Run benchmarks on your own system for
accurate comparisons._
+_Note: Results vary by hardware. Run benchmarks on your own system for
accurate comparisons. List benchmarks are included in the generated report._
## Benchmark Methodology
diff --git a/benchmarks/go_benchmark/benchmark_report.py
b/benchmarks/go_benchmark/benchmark_report.py
index 2fd76fa01..d00418a2b 100755
--- a/benchmarks/go_benchmark/benchmark_report.py
+++ b/benchmarks/go_benchmark/benchmark_report.py
@@ -45,6 +45,16 @@ COLORS = {
"protobuf": "#55BCC2", # Teal
"msgpack": "#9B59B6", # Purple
}
+DATATYPES = [
+ "struct",
+ "structlist",
+ "sample",
+ "samplelist",
+ "mediacontent",
+ "mediacontentlist",
+]
+OPERATIONS = ["serialize", "deserialize"]
+SERIALIZERS = ["fory", "protobuf", "msgpack"]
def parse_benchmark_txt(filepath):
@@ -107,16 +117,49 @@ def parse_benchmark_json(filepath):
return final_results
+def parse_serialized_sizes(text):
+ sizes = {}
+ current = None
+ for line in text.splitlines():
+ line = line.strip()
+ if not line or line.startswith("="):
+ continue
+ if line.endswith(":") and not line.startswith(
+ ("Fory:", "Protobuf:", "Msgpack:")
+ ):
+ current = line.rstrip(":")
+ sizes[current] = {}
+ continue
+ if current is None:
+ continue
+ match = re.match(r"^(Fory|Protobuf|Msgpack):\s+(\d+)\s+bytes$", line)
+ if match:
+ serializer = match.group(1).lower()
+ size = int(match.group(2))
+ sizes[current][serializer] = size
+ return sizes
+
+
+def load_serialized_sizes(output_dir):
+ size_files = [
+ Path(output_dir) / "serialized_sizes.txt",
+ Path(output_dir) / "benchmark_results.txt",
+ ]
+ for path in size_files:
+ if not path.exists():
+ continue
+ text = path.read_text(encoding="utf-8", errors="ignore")
+ if "Serialized Sizes (bytes):" in text:
+ return parse_serialized_sizes(text)
+ return {}
+
+
def generate_plots(results, output_dir):
"""Generate comparison plots for each data type."""
if not HAS_MATPLOTLIB:
return
- datatypes = ["struct", "sample", "mediacontent"]
- operations = ["serialize", "deserialize"]
- serializers = ["fory", "protobuf", "msgpack"]
-
- for datatype in datatypes:
+ for datatype in DATATYPES:
if datatype not in results:
continue
@@ -127,14 +170,14 @@ def generate_plots(results, output_dir):
fontweight="bold",
)
- for idx, op in enumerate(operations):
+ for idx, op in enumerate(OPERATIONS):
ax = axes[idx]
if op not in results[datatype]:
continue
data = results[datatype][op]
- available_serializers = [s for s in serializers if s in data]
+ available_serializers = [s for s in SERIALIZERS if s in data]
if not available_serializers:
continue
@@ -195,11 +238,15 @@ def generate_combined_plot(results, output_dir):
if not HAS_MATPLOTLIB:
return
- datatypes = ["struct", "sample", "mediacontent"]
- operations = ["serialize", "deserialize"]
- serializers = ["fory", "protobuf", "msgpack"]
+ datatypes = DATATYPES
+ operations = OPERATIONS
+ serializers = SERIALIZERS
- fig, axes = plt.subplots(2, 3, figsize=(15, 10))
+ cols = len(datatypes)
+ fig_width = max(12, 3.5 * cols)
+ fig, axes = plt.subplots(len(operations), cols, figsize=(fig_width, 10))
+ if cols == 1:
+ axes = [[axes[row]] for row in range(len(operations))]
fig.suptitle(
"Go Serialization Benchmark: Fory vs Protobuf vs Msgpack",
fontsize=14,
@@ -208,7 +255,7 @@ def generate_combined_plot(results, output_dir):
for row, op in enumerate(operations):
for col, datatype in enumerate(datatypes):
- ax = axes[row, col]
+ ax = axes[row][col]
if datatype not in results or op not in results[datatype]:
ax.text(
@@ -259,8 +306,8 @@ def generate_combined_plot(results, output_dir):
def generate_markdown_report(results, output_dir):
"""Generate markdown report."""
- datatypes = ["struct", "sample", "mediacontent"]
- operations = ["serialize", "deserialize"]
+ datatypes = DATATYPES
+ operations = OPERATIONS
report = []
report.append("# Go Serialization Benchmark Report\n")
@@ -336,6 +383,38 @@ def generate_markdown_report(results, output_dir):
report.append("")
+ # Serialized size section
+ sizes = load_serialized_sizes(output_dir)
+ report.append("### Serialized Data Sizes (bytes)\n")
+ if sizes:
+ report.append("| Data Type | Fory | Protobuf | Msgpack |")
+ report.append("|-----------|------|----------|---------|")
+ name_map = {
+ "NumericStruct": "Struct",
+ "Sample": "Sample",
+ "MediaContent": "MediaContent",
+ "StructList": "StructList",
+ "SampleList": "SampleList",
+ "MediaContentList": "MediaContentList",
+ }
+ ordered = [
+ "NumericStruct",
+ "Sample",
+ "MediaContent",
+ "StructList",
+ "SampleList",
+ "MediaContentList",
+ ]
+ for key in ordered:
+ if key not in sizes:
+ continue
+ entry = sizes[key]
+ report.append(
+ f"| {name_map.get(key, key)} | {entry.get('fory', 'N/A')} |
{entry.get('protobuf', 'N/A')} | {entry.get('msgpack', 'N/A')} |"
+ )
+ else:
+ report.append("No serialized size data found.\n")
+
# Plots section
if HAS_MATPLOTLIB:
report.append("## Performance Charts\n")
diff --git a/benchmarks/go_benchmark/benchmark_test.go
b/benchmarks/go_benchmark/benchmark_test.go
index 2366d0a11..e0e1039c6 100644
--- a/benchmarks/go_benchmark/benchmark_test.go
+++ b/benchmarks/go_benchmark/benchmark_test.go
@@ -35,6 +35,7 @@ func newFory() *fory.Fory {
f := fory.New(
fory.WithXlang(true),
fory.WithTrackRef(false),
+ fory.WithCompatible(true),
)
// Register types with IDs matching C++ benchmark
if err := f.RegisterStruct(NumericStruct{}, 1); err != nil {
@@ -52,6 +53,15 @@ func newFory() *fory.Fory {
if err := f.RegisterStruct(MediaContent{}, 5); err != nil {
panic(err)
}
+ if err := f.RegisterStruct(StructList{}, 8); err != nil {
+ panic(err)
+ }
+ if err := f.RegisterStruct(SampleList{}, 9); err != nil {
+ panic(err)
+ }
+ if err := f.RegisterStruct(MediaContentList{}, 10); err != nil {
+ panic(err)
+ }
if err := f.RegisterEnum(Player(0), 6); err != nil {
panic(err)
}
@@ -162,6 +172,104 @@ func BenchmarkMsgpack_Struct_Deserialize(b *testing.B) {
}
}
+// ============================================================================
+// StructList Benchmarks
+// ============================================================================
+
+func BenchmarkFory_StructList_Serialize(b *testing.B) {
+ f := newFory()
+ obj := CreateStructList()
+ buf := fory.NewByteBuffer(make([]byte, 0, 65536))
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ buf.Reset()
+ err := f.SerializeTo(buf, &obj)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkProtobuf_StructList_Serialize(b *testing.B) {
+ obj := CreateStructList()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ pbObj := ToPbStructList(obj)
+ _, err := proto.Marshal(pbObj)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkMsgpack_StructList_Serialize(b *testing.B) {
+ obj := CreateStructList()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := msgpack.Marshal(obj)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkFory_StructList_Deserialize(b *testing.B) {
+ f := newFory()
+ obj := CreateStructList()
+ data, err := f.Serialize(&obj)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ var result StructList
+ err := f.Deserialize(data, &result)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkProtobuf_StructList_Deserialize(b *testing.B) {
+ obj := CreateStructList()
+ pbObj := ToPbStructList(obj)
+ data, err := proto.Marshal(pbObj)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ var pbResult pb.StructList
+ err := proto.Unmarshal(data, &pbResult)
+ if err != nil {
+ b.Fatal(err)
+ }
+ _ = FromPbStructList(&pbResult)
+ }
+}
+
+func BenchmarkMsgpack_StructList_Deserialize(b *testing.B) {
+ obj := CreateStructList()
+ data, err := msgpack.Marshal(obj)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ var result StructList
+ err := msgpack.Unmarshal(data, &result)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
// ============================================================================
// Sample Benchmarks
// ============================================================================
@@ -260,6 +368,104 @@ func BenchmarkMsgpack_Sample_Deserialize(b *testing.B) {
}
}
+// ============================================================================
+// SampleList Benchmarks
+// ============================================================================
+
+func BenchmarkFory_SampleList_Serialize(b *testing.B) {
+ f := newFory()
+ obj := CreateSampleList()
+ buf := fory.NewByteBuffer(make([]byte, 0, 131072))
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ buf.Reset()
+ err := f.SerializeTo(buf, &obj)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkProtobuf_SampleList_Serialize(b *testing.B) {
+ obj := CreateSampleList()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ pbObj := ToPbSampleList(obj)
+ _, err := proto.Marshal(pbObj)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkMsgpack_SampleList_Serialize(b *testing.B) {
+ obj := CreateSampleList()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := msgpack.Marshal(obj)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkFory_SampleList_Deserialize(b *testing.B) {
+ f := newFory()
+ obj := CreateSampleList()
+ data, err := f.Serialize(&obj)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ var result SampleList
+ err := f.Deserialize(data, &result)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkProtobuf_SampleList_Deserialize(b *testing.B) {
+ obj := CreateSampleList()
+ pbObj := ToPbSampleList(obj)
+ data, err := proto.Marshal(pbObj)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ var pbResult pb.SampleList
+ err := proto.Unmarshal(data, &pbResult)
+ if err != nil {
+ b.Fatal(err)
+ }
+ _ = FromPbSampleList(&pbResult)
+ }
+}
+
+func BenchmarkMsgpack_SampleList_Deserialize(b *testing.B) {
+ obj := CreateSampleList()
+ data, err := msgpack.Marshal(obj)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ var result SampleList
+ err := msgpack.Unmarshal(data, &result)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
// ============================================================================
// MediaContent Benchmarks
// ============================================================================
@@ -358,6 +564,104 @@ func BenchmarkMsgpack_MediaContent_Deserialize(b
*testing.B) {
}
}
+// ============================================================================
+// MediaContentList Benchmarks
+// ============================================================================
+
+func BenchmarkFory_MediaContentList_Serialize(b *testing.B) {
+ f := newFory()
+ obj := CreateMediaContentList()
+ buf := fory.NewByteBuffer(make([]byte, 0, 131072))
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ buf.Reset()
+ err := f.SerializeTo(buf, &obj)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkProtobuf_MediaContentList_Serialize(b *testing.B) {
+ obj := CreateMediaContentList()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ pbObj := ToPbMediaContentList(obj)
+ _, err := proto.Marshal(pbObj)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkMsgpack_MediaContentList_Serialize(b *testing.B) {
+ obj := CreateMediaContentList()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := msgpack.Marshal(obj)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkFory_MediaContentList_Deserialize(b *testing.B) {
+ f := newFory()
+ obj := CreateMediaContentList()
+ data, err := f.Serialize(&obj)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ var result MediaContentList
+ err := f.Deserialize(data, &result)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkProtobuf_MediaContentList_Deserialize(b *testing.B) {
+ obj := CreateMediaContentList()
+ pbObj := ToPbMediaContentList(obj)
+ data, err := proto.Marshal(pbObj)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ var pbResult pb.MediaContentList
+ err := proto.Unmarshal(data, &pbResult)
+ if err != nil {
+ b.Fatal(err)
+ }
+ _ = FromPbMediaContentList(&pbResult)
+ }
+}
+
+func BenchmarkMsgpack_MediaContentList_Deserialize(b *testing.B) {
+ obj := CreateMediaContentList()
+ data, err := msgpack.Marshal(obj)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ var result MediaContentList
+ err := msgpack.Unmarshal(data, &result)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
// ============================================================================
// Size Comparison (run once to print sizes)
// ============================================================================
@@ -383,6 +687,24 @@ func TestPrintSerializedSizes(t *testing.T) {
pbMediaData, _ := proto.Marshal(ToPbMediaContent(mediaContent))
msgpackMediaData, _ := msgpack.Marshal(mediaContent)
+ // StructList sizes
+ structList := CreateStructList()
+ foryStructListData, _ := f.Serialize(&structList)
+ pbStructListData, _ := proto.Marshal(ToPbStructList(structList))
+ msgpackStructListData, _ := msgpack.Marshal(structList)
+
+ // SampleList sizes
+ sampleList := CreateSampleList()
+ forySampleListData, _ := f.Serialize(&sampleList)
+ pbSampleListData, _ := proto.Marshal(ToPbSampleList(sampleList))
+ msgpackSampleListData, _ := msgpack.Marshal(sampleList)
+
+ // MediaContentList sizes
+ mediaContentList := CreateMediaContentList()
+ foryMediaContentListData, _ := f.Serialize(&mediaContentList)
+ pbMediaContentListData, _ :=
proto.Marshal(ToPbMediaContentList(mediaContentList))
+ msgpackMediaContentListData, _ := msgpack.Marshal(mediaContentList)
+
fmt.Println("============================================")
fmt.Println("Serialized Sizes (bytes):")
fmt.Println("============================================")
@@ -398,5 +720,17 @@ func TestPrintSerializedSizes(t *testing.T) {
fmt.Printf(" Fory: %d bytes\n", len(foryMediaData))
fmt.Printf(" Protobuf: %d bytes\n", len(pbMediaData))
fmt.Printf(" Msgpack: %d bytes\n", len(msgpackMediaData))
+ fmt.Printf("StructList:\n")
+ fmt.Printf(" Fory: %d bytes\n", len(foryStructListData))
+ fmt.Printf(" Protobuf: %d bytes\n", len(pbStructListData))
+ fmt.Printf(" Msgpack: %d bytes\n", len(msgpackStructListData))
+ fmt.Printf("SampleList:\n")
+ fmt.Printf(" Fory: %d bytes\n", len(forySampleListData))
+ fmt.Printf(" Protobuf: %d bytes\n", len(pbSampleListData))
+ fmt.Printf(" Msgpack: %d bytes\n", len(msgpackSampleListData))
+ fmt.Printf("MediaContentList:\n")
+ fmt.Printf(" Fory: %d bytes\n", len(foryMediaContentListData))
+ fmt.Printf(" Protobuf: %d bytes\n", len(pbMediaContentListData))
+ fmt.Printf(" Msgpack: %d bytes\n", len(msgpackMediaContentListData))
fmt.Println("============================================")
}
diff --git a/benchmarks/go_benchmark/models.go
b/benchmarks/go_benchmark/models.go
index 5556182e8..c80107b6b 100644
--- a/benchmarks/go_benchmark/models.go
+++ b/benchmarks/go_benchmark/models.go
@@ -20,41 +20,41 @@ package benchmark
// NumericStruct is a simple struct with 8 int32 fields
// Matches the C++ NumericStruct and protobuf Struct message
type NumericStruct struct {
- F1 int32 `msgpack:"f1"`
- F2 int32 `msgpack:"f2"`
- F3 int32 `msgpack:"f3"`
- F4 int32 `msgpack:"f4"`
- F5 int32 `msgpack:"f5"`
- F6 int32 `msgpack:"f6"`
- F7 int32 `msgpack:"f7"`
- F8 int32 `msgpack:"f8"`
+ F1 int32 `msgpack:"1" fory:"id=1"`
+ F2 int32 `msgpack:"2" fory:"id=2"`
+ F3 int32 `msgpack:"3" fory:"id=3"`
+ F4 int32 `msgpack:"4" fory:"id=4"`
+ F5 int32 `msgpack:"5" fory:"id=5"`
+ F6 int32 `msgpack:"6" fory:"id=6"`
+ F7 int32 `msgpack:"7" fory:"id=7"`
+ F8 int32 `msgpack:"8" fory:"id=8"`
}
// Sample is a complex struct with various types and arrays
// Matches the C++ Sample and protobuf Sample message
type Sample struct {
- IntValue int32 `msgpack:"int_value"`
- LongValue int64 `msgpack:"long_value"`
- FloatValue float32 `msgpack:"float_value"`
- DoubleValue float64 `msgpack:"double_value"`
- ShortValue int32 `msgpack:"short_value"`
- CharValue int32 `msgpack:"char_value"`
- BooleanValue bool `msgpack:"boolean_value"`
- IntValueBoxed int32 `msgpack:"int_value_boxed"`
- LongValueBoxed int64 `msgpack:"long_value_boxed"`
- FloatValueBoxed float32 `msgpack:"float_value_boxed"`
- DoubleValueBoxed float64 `msgpack:"double_value_boxed"`
- ShortValueBoxed int32 `msgpack:"short_value_boxed"`
- CharValueBoxed int32 `msgpack:"char_value_boxed"`
- BooleanValueBoxed bool `msgpack:"boolean_value_boxed"`
- IntArray []int32 `msgpack:"int_array"`
- LongArray []int64 `msgpack:"long_array"`
- FloatArray []float32 `msgpack:"float_array"`
- DoubleArray []float64 `msgpack:"double_array"`
- ShortArray []int32 `msgpack:"short_array"`
- CharArray []int32 `msgpack:"char_array"`
- BooleanArray []bool `msgpack:"boolean_array"`
- String string `msgpack:"string"`
+ IntValue int32 `msgpack:"1" fory:"id=1"`
+ LongValue int64 `msgpack:"2" fory:"id=2"`
+ FloatValue float32 `msgpack:"3" fory:"id=3"`
+ DoubleValue float64 `msgpack:"4" fory:"id=4"`
+ ShortValue int32 `msgpack:"5" fory:"id=5"`
+ CharValue int32 `msgpack:"6" fory:"id=6"`
+ BooleanValue bool `msgpack:"7" fory:"id=7"`
+ IntValueBoxed int32 `msgpack:"8" fory:"id=8"`
+ LongValueBoxed int64 `msgpack:"9" fory:"id=9"`
+ FloatValueBoxed float32 `msgpack:"10" fory:"id=10"`
+ DoubleValueBoxed float64 `msgpack:"11" fory:"id=11"`
+ ShortValueBoxed int32 `msgpack:"12" fory:"id=12"`
+ CharValueBoxed int32 `msgpack:"13" fory:"id=13"`
+ BooleanValueBoxed bool `msgpack:"14" fory:"id=14"`
+ IntArray []int32 `msgpack:"15" fory:"id=15"`
+ LongArray []int64 `msgpack:"16" fory:"id=16"`
+ FloatArray []float32 `msgpack:"17" fory:"id=17"`
+ DoubleArray []float64 `msgpack:"18" fory:"id=18"`
+ ShortArray []int32 `msgpack:"19" fory:"id=19"`
+ CharArray []int32 `msgpack:"20" fory:"id=20"`
+ BooleanArray []bool `msgpack:"21" fory:"id=21"`
+ String string `msgpack:"22" fory:"id=22"`
}
// Player enum type
@@ -75,33 +75,45 @@ const (
// Media represents media metadata
type Media struct {
- URI string `msgpack:"uri"`
- Title string `msgpack:"title"`
- Width int32 `msgpack:"width"`
- Height int32 `msgpack:"height"`
- Format string `msgpack:"format"`
- Duration int64 `msgpack:"duration"`
- Size int64 `msgpack:"size"`
- Bitrate int32 `msgpack:"bitrate"`
- HasBitrate bool `msgpack:"has_bitrate"`
- Persons []string `msgpack:"persons"`
- Player Player `msgpack:"player"`
- Copyright string `msgpack:"copyright"`
+ URI string `msgpack:"1" fory:"id=1"`
+ Title string `msgpack:"2" fory:"id=2"`
+ Width int32 `msgpack:"3" fory:"id=3"`
+ Height int32 `msgpack:"4" fory:"id=4"`
+ Format string `msgpack:"5" fory:"id=5"`
+ Duration int64 `msgpack:"6" fory:"id=6"`
+ Size int64 `msgpack:"7" fory:"id=7"`
+ Bitrate int32 `msgpack:"8" fory:"id=8"`
+ HasBitrate bool `msgpack:"9" fory:"id=9"`
+ Persons []string `msgpack:"10" fory:"id=10"`
+ Player Player `msgpack:"11" fory:"id=11"`
+ Copyright string `msgpack:"12" fory:"id=12"`
}
// Image represents image metadata
type Image struct {
- URI string `msgpack:"uri"`
- Title string `msgpack:"title"`
- Width int32 `msgpack:"width"`
- Height int32 `msgpack:"height"`
- Size Size `msgpack:"size"`
+ URI string `msgpack:"1" fory:"id=1"`
+ Title string `msgpack:"2" fory:"id=2"`
+ Width int32 `msgpack:"3" fory:"id=3"`
+ Height int32 `msgpack:"4" fory:"id=4"`
+ Size Size `msgpack:"5" fory:"id=5"`
}
// MediaContent contains media and images
type MediaContent struct {
- Media Media `msgpack:"media"`
- Images []Image `msgpack:"images"`
+ Media Media `msgpack:"1" fory:"id=1"`
+ Images []Image `msgpack:"2" fory:"id=2"`
+}
+
+type StructList struct {
+ StructList []NumericStruct `msgpack:"1" fory:"id=1"`
+}
+
+type SampleList struct {
+ SampleList []Sample `msgpack:"1" fory:"id=1"`
+}
+
+type MediaContentList struct {
+ MediaContentList []MediaContent `msgpack:"1" fory:"id=1"`
}
// CreateNumericStruct creates test data matching C++ benchmark
@@ -188,3 +200,27 @@ func CreateMediaContent() MediaContent {
},
}
}
+
+func CreateStructList() StructList {
+ list := make([]NumericStruct, 20)
+ for i := range list {
+ list[i] = CreateNumericStruct()
+ }
+ return StructList{StructList: list}
+}
+
+func CreateSampleList() SampleList {
+ list := make([]Sample, 20)
+ for i := range list {
+ list[i] = CreateSample()
+ }
+ return SampleList{SampleList: list}
+}
+
+func CreateMediaContentList() MediaContentList {
+ list := make([]MediaContent, 20)
+ for i := range list {
+ list[i] = CreateMediaContent()
+ }
+ return MediaContentList{MediaContentList: list}
+}
diff --git a/benchmarks/go_benchmark/proto_convert.go
b/benchmarks/go_benchmark/proto_convert.go
index d1e4d50b4..fd2a6841b 100644
--- a/benchmarks/go_benchmark/proto_convert.go
+++ b/benchmarks/go_benchmark/proto_convert.go
@@ -49,6 +49,27 @@ func FromPbStruct(pb *pb.Struct) NumericStruct {
}
}
+// ToPbStructList converts StructList to protobuf StructList
+func ToPbStructList(obj StructList) *pb.StructList {
+ list := make([]*pb.Struct, len(obj.StructList))
+ for i, item := range obj.StructList {
+ list[i] = ToPbStruct(item)
+ }
+ return &pb.StructList{StructList: list}
+}
+
+// FromPbStructList converts protobuf StructList to StructList
+func FromPbStructList(pbList *pb.StructList) StructList {
+ if pbList == nil {
+ return StructList{}
+ }
+ list := make([]NumericStruct, len(pbList.StructList))
+ for i, item := range pbList.StructList {
+ list[i] = FromPbStruct(item)
+ }
+ return StructList{StructList: list}
+}
+
// ToPbSample converts Sample to protobuf Sample
func ToPbSample(obj Sample) *pb.Sample {
return &pb.Sample{
@@ -105,6 +126,27 @@ func FromPbSample(pb *pb.Sample) Sample {
}
}
+// ToPbSampleList converts SampleList to protobuf SampleList
+func ToPbSampleList(obj SampleList) *pb.SampleList {
+ list := make([]*pb.Sample, len(obj.SampleList))
+ for i, item := range obj.SampleList {
+ list[i] = ToPbSample(item)
+ }
+ return &pb.SampleList{SampleList: list}
+}
+
+// FromPbSampleList converts protobuf SampleList to SampleList
+func FromPbSampleList(pbList *pb.SampleList) SampleList {
+ if pbList == nil {
+ return SampleList{}
+ }
+ list := make([]Sample, len(pbList.SampleList))
+ for i, item := range pbList.SampleList {
+ list[i] = FromPbSample(item)
+ }
+ return SampleList{SampleList: list}
+}
+
// ToPbImage converts Image to protobuf Image
func ToPbImage(obj Image) *pb.Image {
pbImg := &pb.Image{
@@ -200,3 +242,24 @@ func FromPbMediaContent(pbMC *pb.MediaContent)
MediaContent {
Images: images,
}
}
+
+// ToPbMediaContentList converts MediaContentList to protobuf MediaContentList
+func ToPbMediaContentList(obj MediaContentList) *pb.MediaContentList {
+ list := make([]*pb.MediaContent, len(obj.MediaContentList))
+ for i, item := range obj.MediaContentList {
+ list[i] = ToPbMediaContent(item)
+ }
+ return &pb.MediaContentList{MediaContentList: list}
+}
+
+// FromPbMediaContentList converts protobuf MediaContentList to
MediaContentList
+func FromPbMediaContentList(pbList *pb.MediaContentList) MediaContentList {
+ if pbList == nil {
+ return MediaContentList{}
+ }
+ list := make([]MediaContent, len(pbList.MediaContentList))
+ for i, item := range pbList.MediaContentList {
+ list[i] = FromPbMediaContent(item)
+ }
+ return MediaContentList{MediaContentList: list}
+}
diff --git a/benchmarks/go_benchmark/run.sh b/benchmarks/go_benchmark/run.sh
index e0fb0216d..448da66a5 100755
--- a/benchmarks/go_benchmark/run.sh
+++ b/benchmarks/go_benchmark/run.sh
@@ -59,7 +59,7 @@ while [[ $# -gt 0 ]]; do
echo "Usage: $0 [options]"
echo ""
echo "Options:"
- echo " --data <type> Filter by data type: struct, sample,
mediacontent"
+ echo " --data <type> Filter by data type: struct, sample,
mediacontent, structlist, samplelist, mediacontentlist"
echo " --serializer <name> Filter by serializer: fory, protobuf,
msgpack"
echo " --count <n> Number of benchmark runs (default: 5)"
echo " --benchtime <dur> Time for each benchmark (default: 1s)"
@@ -87,12 +87,21 @@ if [[ -n "$DATA_TYPE" ]]; then
struct)
FILTER="Struct"
;;
+ structlist|struct-list)
+ FILTER="StructList"
+ ;;
sample)
FILTER="Sample"
;;
+ samplelist|sample-list)
+ FILTER="SampleList"
+ ;;
mediacontent|media)
FILTER="MediaContent"
;;
+ mediacontentlist|medialist|media-list)
+ FILTER="MediaContentList"
+ ;;
*)
echo "Unknown data type: $DATA_TYPE"
exit 1
@@ -189,7 +198,9 @@ go test $BENCH_ARGS -benchmem -count=1
-benchtime=$BENCHTIME -json > "$OUTPUT_DI
# Print serialized sizes
echo ""
-go test -run TestPrintSerializedSizes -v 2>&1 | grep -A 20 "Serialized Sizes"
+go test -run TestPrintSerializedSizes -v 2>&1 | \
+ grep -A 20 "Serialized Sizes" | \
+ tee "$OUTPUT_DIR/serialized_sizes.txt"
# Generate report
if $GENERATE_REPORT; then
diff --git a/benchmarks/proto/bench.proto b/benchmarks/proto/bench.proto
index d94056a44..ce13eeace 100644
--- a/benchmarks/proto/bench.proto
+++ b/benchmarks/proto/bench.proto
@@ -66,11 +66,19 @@ message Sample {
string string = 22;
}
+message SampleList {
+ repeated Sample sample_list = 1;
+}
+
message MediaContent {
Media media = 1;
repeated Image images = 2;
}
+message MediaContentList {
+ repeated MediaContent media_content_list = 1;
+}
+
message Media {
string uri = 1;
optional string title = 2;
diff --git a/benchmarks/rust_benchmark/benches/threaded_bench.rs
b/benchmarks/rust_benchmark/benches/threaded_bench.rs
index 404d7720a..b2a2710ab 100644
--- a/benchmarks/rust_benchmark/benches/threaded_bench.rs
+++ b/benchmarks/rust_benchmark/benches/threaded_bench.rs
@@ -27,14 +27,23 @@ use pprof::criterion::{Output, PProfProfiler};
#[derive(Debug, ForyObject)]
pub struct UserSessionMetrics {
+ #[fory(id = 0)]
pub request_count: u64,
+ #[fory(id = 1)]
pub unique_ip_count: u64,
+ #[fory(id = 2)]
pub unique_user_agent_count: u64,
+ #[fory(id = 3)]
pub unique_url_count: u64,
+ #[fory(id = 4)]
pub unique_resource_count: u64,
+ #[fory(id = 5)]
pub active_duration_secs: u64,
+ #[fory(id = 6)]
pub first_seen_time: u64,
+ #[fory(id = 7)]
pub last_seen_time: u64,
+ #[fory(id = 8)]
pub updated_at: u64,
}
diff --git a/benchmarks/rust_benchmark/run.sh b/benchmarks/rust_benchmark/run.sh
new file mode 100644
index 000000000..6bc062731
--- /dev/null
+++ b/benchmarks/rust_benchmark/run.sh
@@ -0,0 +1,202 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -e
+export ENABLE_FORY_DEBUG_OUTPUT=0
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "$SCRIPT_DIR"
+
+# Output directory for results
+OUTPUT_DIR="$SCRIPT_DIR/results"
+mkdir -p "$OUTPUT_DIR"
+
+# Default values
+DATA_FILTER=""
+SERIALIZER_FILTER=""
+CUSTOM_FILTER=""
+GENERATE_REPORT=true
+
+usage() {
+ echo "Usage: $0 [options]"
+ echo ""
+ echo "Build and run Rust benchmarks (criterion)"
+ echo ""
+ echo "Options:"
+ echo " --data <type> Filter by data type: simple_struct,
simple_list, simple_map,"
+ echo " person, company, ecommerce_data, system_data,
all"
+ echo " --serializer <name> Filter by serializer: fory, json, protobuf"
+ echo " --filter <regex> Custom criterion filter regex (overrides
--data/--serializer)"
+ echo " --no-report Skip report generation"
+ echo " -h, --help Show this help message"
+ echo ""
+ echo "Examples:"
+ echo " $0 # Default:
simple_struct/ecommerce_data/system_data"
+ echo " $0 --data simple_struct # Only simple_struct benchmarks"
+ echo " $0 --data ecommerce_data,system_data"
+ echo " $0 --serializer fory # Only Fory benchmarks (all data
types)"
+ echo " $0 --data simple_struct --serializer json"
+ echo " $0 --filter 'simple_struct|person'"
+ exit 0
+}
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ --data)
+ DATA_FILTER="$2"
+ shift 2
+ ;;
+ --serializer)
+ SERIALIZER_FILTER="$2"
+ shift 2
+ ;;
+ --filter)
+ CUSTOM_FILTER="$2"
+ shift 2
+ ;;
+ --no-report)
+ GENERATE_REPORT=false
+ shift
+ ;;
+ -h|--help)
+ usage
+ ;;
+ *)
+ echo "Unknown option: $1"
+ usage
+ ;;
+ esac
+done
+
+normalize_data_filter() {
+ local input="$1"
+ if [[ -z "$input" ]]; then
+ echo ""
+ return
+ fi
+ if [[ "$input" == "all" ]]; then
+ echo ""
+ return
+ fi
+ local result=()
+ IFS=',' read -ra parts <<< "$input"
+ for part in "${parts[@]}"; do
+ case "$part" in
+ simple_struct|struct) result+=("simple_struct") ;;
+ simple_list|list) result+=("simple_list") ;;
+ simple_map|map) result+=("simple_map") ;;
+ person) result+=("person") ;;
+ company) result+=("company") ;;
+ ecommerce_data|ecommerce|ecommerce-data)
result+=("ecommerce_data") ;;
+ system_data|system|system-data) result+=("system_data") ;;
+ *)
+ echo "Unknown data type: $part"
+ exit 1
+ ;;
+ esac
+ done
+ local joined
+ joined="$(IFS='|'; echo "${result[*]}")"
+ echo "$joined"
+}
+
+build_filter() {
+ if [[ -n "$CUSTOM_FILTER" ]]; then
+ echo "$CUSTOM_FILTER"
+ return
+ fi
+
+ local data_regex
+ if [[ -n "$DATA_FILTER" ]]; then
+ data_regex="$(normalize_data_filter "$DATA_FILTER")"
+ else
+ data_regex="simple_struct|ecommerce_data|system_data"
+ fi
+
+ if [[ -n "$SERIALIZER_FILTER" ]]; then
+ case "$SERIALIZER_FILTER" in
+ fory|json|protobuf)
+ ;;
+ *)
+ echo "Unknown serializer: $SERIALIZER_FILTER"
+ exit 1
+ ;;
+ esac
+ fi
+
+ if [[ -n "$data_regex" && -n "$SERIALIZER_FILTER" ]]; then
+ echo "(${data_regex})/${SERIALIZER_FILTER}_"
+ elif [[ -n "$data_regex" ]]; then
+ echo "(${data_regex})"
+ elif [[ -n "$SERIALIZER_FILTER" ]]; then
+ echo "${SERIALIZER_FILTER}_"
+ else
+ echo ""
+ fi
+}
+
+FILTER_REGEX="$(build_filter)"
+
+LOG_FILE="$OUTPUT_DIR/cargo_bench.log"
+BENCH_CMD=(cargo bench --bench serialization_bench)
+if [[ -n "$FILTER_REGEX" ]]; then
+ BENCH_CMD+=(-- "$FILTER_REGEX")
+fi
+
+echo "============================================"
+echo "Fory Rust Benchmark"
+echo "============================================"
+if [[ -n "$FILTER_REGEX" ]]; then
+ echo "Filter: $FILTER_REGEX"
+else
+ echo "Filter: (all benchmarks)"
+fi
+echo "Log: $LOG_FILE"
+echo ""
+
+echo "============================================"
+echo "Running benchmarks..."
+echo "============================================"
+echo "Running: ${BENCH_CMD[*]}"
+echo ""
+"${BENCH_CMD[@]}" 2>&1 | tee "$LOG_FILE"
+
+if $GENERATE_REPORT; then
+ echo ""
+ echo "============================================"
+ echo "Generating report..."
+ echo "============================================"
+ if command -v python3 &> /dev/null; then
+ python3 "$SCRIPT_DIR/benchmark_report.py" --log-file "$LOG_FILE"
--output-dir "$OUTPUT_DIR" || \
+ echo "Warning: Report generation failed. Install matplotlib and
numpy for reports."
+ elif command -v python &> /dev/null; then
+ python "$SCRIPT_DIR/benchmark_report.py" --log-file "$LOG_FILE"
--output-dir "$OUTPUT_DIR" || \
+ echo "Warning: Report generation failed. Install matplotlib and
numpy for reports."
+ else
+ echo "Warning: Python not found. Skipping report generation."
+ fi
+fi
+
+echo ""
+echo "============================================"
+echo "Benchmark complete!"
+echo "============================================"
+echo "Results saved to: $OUTPUT_DIR/"
+echo " - cargo_bench.log"
+if $GENERATE_REPORT; then
+ echo " - REPORT.md and plots (if dependencies are available)"
+fi
diff --git a/benchmarks/rust_benchmark/src/models/complex.rs
b/benchmarks/rust_benchmark/src/models/complex.rs
index e70003b36..2ea4ca589 100644
--- a/benchmarks/rust_benchmark/src/models/complex.rs
+++ b/benchmarks/rust_benchmark/src/models/complex.rs
@@ -24,47 +24,73 @@ use std::collections::HashMap;
// Fory models
#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
pub struct ForyProduct {
+ #[fory(id = 0)]
pub id: String,
+ #[fory(id = 1)]
pub name: String,
+ #[fory(id = 2)]
pub price: f64,
+ #[fory(id = 3)]
pub categories: Vec<String>,
+ #[fory(id = 4)]
pub attributes: HashMap<String, String>,
}
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub struct ForyOrderItem {
+ #[fory(id = 0)]
pub product: ForyProduct,
+ #[fory(id = 1)]
pub quantity: i32,
+ #[fory(id = 2)]
pub unit_price: f64,
+ #[fory(id = 3)]
pub customizations: HashMap<String, String>,
}
#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
pub struct ForyCustomer {
+ #[fory(id = 0)]
pub id: String,
+ #[fory(id = 1)]
pub name: String,
+ #[fory(id = 2)]
pub email: String,
+ #[fory(id = 3)]
pub phone_numbers: Vec<String>,
+ #[fory(id = 4)]
pub preferences: HashMap<String, String>,
+ #[fory(id = 5)]
pub member_since: NaiveDateTime,
}
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub struct ForyOrder {
+ #[fory(id = 0)]
pub id: String,
+ #[fory(id = 1)]
pub customer: ForyCustomer,
+ #[fory(id = 2)]
pub items: Vec<ForyOrderItem>,
+ #[fory(id = 3)]
pub total_amount: f64,
+ #[fory(id = 4)]
pub status: String,
+ #[fory(id = 5)]
pub order_date: NaiveDateTime,
+ #[fory(id = 6)]
pub metadata: HashMap<String, String>,
}
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub struct ECommerceData {
+ #[fory(id = 0)]
pub orders: Vec<ForyOrder>,
+ #[fory(id = 1)]
pub customers: Vec<ForyCustomer>,
+ #[fory(id = 2)]
pub products: Vec<ForyProduct>,
+ #[fory(id = 3)]
pub order_lookup: HashMap<String, ForyOrder>,
}
diff --git a/benchmarks/rust_benchmark/src/models/medium.rs
b/benchmarks/rust_benchmark/src/models/medium.rs
index 1a718bc16..d2d1a40f9 100644
--- a/benchmarks/rust_benchmark/src/models/medium.rs
+++ b/benchmarks/rust_benchmark/src/models/medium.rs
@@ -24,27 +24,41 @@ use std::collections::HashMap;
// Fory models
#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
pub struct ForyAddress {
+ #[fory(id = 0)]
pub street: String,
+ #[fory(id = 1)]
pub city: String,
+ #[fory(id = 2)]
pub country: String,
+ #[fory(id = 3)]
pub zip_code: String,
}
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub struct Person {
+ #[fory(id = 0)]
pub name: String,
+ #[fory(id = 1)]
pub age: i32,
+ #[fory(id = 2)]
pub address: ForyAddress,
+ #[fory(id = 3)]
pub hobbies: Vec<String>,
+ #[fory(id = 4)]
pub metadata: HashMap<String, String>,
+ #[fory(id = 5)]
pub created_at: NaiveDateTime,
}
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub struct Company {
+ #[fory(id = 0)]
pub name: String,
+ #[fory(id = 1)]
pub employees: Vec<Person>,
+ #[fory(id = 2)]
pub offices: HashMap<String, ForyAddress>,
+ #[fory(id = 3)]
pub is_public: bool,
}
diff --git a/benchmarks/rust_benchmark/src/models/realworld.rs
b/benchmarks/rust_benchmark/src/models/realworld.rs
index 854010e92..fdc5dcf11 100644
--- a/benchmarks/rust_benchmark/src/models/realworld.rs
+++ b/benchmarks/rust_benchmark/src/models/realworld.rs
@@ -24,42 +24,67 @@ use std::collections::HashMap;
// Fory models
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub struct ForyLogEntry {
+ #[fory(id = 0)]
pub id: String,
+ #[fory(id = 1)]
pub level: i32, // 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR, 4=FATAL
+ #[fory(id = 2)]
pub message: String,
+ #[fory(id = 3)]
pub service: String,
+ #[fory(id = 4)]
pub timestamp: NaiveDateTime,
+ #[fory(id = 5)]
pub context: HashMap<String, String>,
+ #[fory(id = 6)]
pub tags: Vec<String>,
+ #[fory(id = 7)]
pub duration_ms: f64,
}
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub struct ForyUserProfile {
+ #[fory(id = 0)]
pub user_id: String,
+ #[fory(id = 1)]
pub username: String,
+ #[fory(id = 2)]
pub email: String,
+ #[fory(id = 3)]
pub preferences: HashMap<String, String>,
+ #[fory(id = 4)]
pub permissions: Vec<String>,
+ #[fory(id = 5)]
pub last_login: NaiveDateTime,
+ #[fory(id = 6)]
pub is_active: bool,
}
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub struct ForyAPIMetrics {
+ #[fory(id = 0)]
pub endpoint: String,
+ #[fory(id = 1)]
pub request_count: i64,
+ #[fory(id = 2)]
pub avg_response_time: f64,
+ #[fory(id = 3)]
pub error_count: i64,
+ #[fory(id = 4)]
pub status_codes: HashMap<String, i64>,
+ #[fory(id = 5)]
pub measured_at: NaiveDateTime,
}
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub struct SystemData {
+ #[fory(id = 0)]
pub logs: Vec<ForyLogEntry>,
+ #[fory(id = 1)]
pub users: Vec<ForyUserProfile>,
+ #[fory(id = 2)]
pub metrics: Vec<ForyAPIMetrics>,
+ #[fory(id = 3)]
pub system_info: HashMap<String, String>,
}
diff --git a/benchmarks/rust_benchmark/src/models/simple.rs
b/benchmarks/rust_benchmark/src/models/simple.rs
index 4635721d8..eb6439940 100644
--- a/benchmarks/rust_benchmark/src/models/simple.rs
+++ b/benchmarks/rust_benchmark/src/models/simple.rs
@@ -23,21 +23,29 @@ use std::collections::HashMap;
// Fory models
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub struct SimpleStruct {
+ #[fory(id = 0)]
pub id: i32,
+ #[fory(id = 1)]
pub name: String,
+ #[fory(id = 2)]
pub active: bool,
+ #[fory(id = 3)]
pub score: f64,
}
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub struct SimpleList {
+ #[fory(id = 0)]
pub numbers: Vec<i32>,
+ #[fory(id = 1)]
pub names: Vec<String>,
}
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub struct SimpleMap {
+ #[fory(id = 0)]
pub string_to_int: HashMap<String, i32>,
+ #[fory(id = 1)]
pub int_to_string: HashMap<i32, String>,
}
diff --git a/cpp/fory/serialization/context.cc
b/cpp/fory/serialization/context.cc
index 5cc78b7a2..12c22deb7 100644
--- a/cpp/fory/serialization/context.cc
+++ b/cpp/fory/serialization/context.cc
@@ -60,7 +60,7 @@ static const std::vector<MetaEncoding> k_type_name_encodings
= {
WriteContext::WriteContext(const Config &config,
std::unique_ptr<TypeResolver> type_resolver)
: buffer_(), config_(&config), type_resolver_(std::move(type_resolver)),
- current_dyn_depth_(0) {}
+ current_dyn_depth_(0), write_type_info_index_map_(8) {}
WriteContext::~WriteContext() = default;
@@ -75,17 +75,51 @@ WriteContext::write_type_meta(const std::type_index
&type_id) {
}
void WriteContext::write_type_meta(const TypeInfo *type_info) {
- auto it = write_type_info_index_map_.find(type_info);
- if (it != write_type_info_index_map_.end()) {
+ const uint64_t key =
+ static_cast<uint64_t>(reinterpret_cast<uintptr_t>(type_info));
+ if (!type_info_index_map_active_) {
+ if (!has_first_type_info_) {
+ has_first_type_info_ = true;
+ first_type_info_ = type_info;
+ buffer_.write_uint8(0); // (index << 1), index=0
+ buffer_.write_bytes(type_info->type_def.data(),
+ type_info->type_def.size());
+ return;
+ }
+ if (type_info == first_type_info_) {
+ buffer_.write_uint8(1); // (index << 1) | 1, index=0
+ return;
+ }
+ type_info_index_map_active_ = true;
+ write_type_info_index_map_.clear();
+ const uint64_t first_key =
+ static_cast<uint64_t>(reinterpret_cast<uintptr_t>(first_type_info_));
+ write_type_info_index_map_.put(first_key, 0);
+ } else if (type_info == first_type_info_) {
+ buffer_.write_uint8(1); // (index << 1) | 1, index=0
+ return;
+ }
+
+ if (auto *entry = write_type_info_index_map_.find(key)) {
// Reference to previously written type: (index << 1) | 1, LSB=1
- buffer_.write_var_uint32(static_cast<uint32_t>((it->second << 1) | 1));
+ uint32_t marker = static_cast<uint32_t>((entry->value << 1) | 1);
+ if (marker < 0x80) {
+ buffer_.write_uint8(static_cast<uint8_t>(marker));
+ } else {
+ buffer_.write_var_uint32(marker);
+ }
return;
}
// New type: index << 1, LSB=0, followed by TypeDef bytes inline
- size_t index = write_type_info_index_map_.size();
- buffer_.write_var_uint32(static_cast<uint32_t>(index << 1));
- write_type_info_index_map_[type_info] = index;
+ uint32_t index = static_cast<uint32_t>(write_type_info_index_map_.size());
+ uint32_t marker = static_cast<uint32_t>(index << 1);
+ if (marker < 0x80) {
+ buffer_.write_uint8(static_cast<uint8_t>(marker));
+ } else {
+ buffer_.write_var_uint32(marker);
+ }
+ write_type_info_index_map_.put(key, index);
// write TypeDef bytes inline
buffer_.write_bytes(type_info->type_def.data(), type_info->type_def.size());
@@ -368,6 +402,9 @@ void WriteContext::reset() {
ref_writer_.reset();
// Clear meta map for streaming TypeMeta (size is used as counter)
write_type_info_index_map_.clear();
+ first_type_info_ = nullptr;
+ has_first_type_info_ = false;
+ type_info_index_map_active_ = false;
current_dyn_depth_ = 0;
// reset buffer indices for reuse - no memory operations needed
buffer_.writer_index(0);
@@ -460,12 +497,50 @@ Result<const TypeInfo *, Error>
ReadContext::read_type_meta() {
}
// Check if we already parsed this type meta (cache lookup by header)
+ if (has_last_meta_header_ && meta_header == last_meta_header_) {
+ // Fast path: same header as last parsed
+ const TypeInfo *cached = last_meta_type_info_;
+ reading_type_infos_.push_back(cached);
+ if (cached && !cached->type_def.empty()) {
+ const size_t type_def_size = cached->type_def.size();
+ if (type_def_size >= sizeof(int64_t) &&
+ type_def_size <= std::numeric_limits<uint32_t>::max()) {
+ Error skip_error;
+ buffer_->skip(static_cast<uint32_t>(type_def_size - sizeof(int64_t)),
+ skip_error);
+ if (FORY_PREDICT_FALSE(!skip_error.ok())) {
+ return Unexpected(std::move(skip_error));
+ }
+ return cached;
+ }
+ }
+ FORY_RETURN_NOT_OK(TypeMeta::skip_bytes(*buffer_, meta_header));
+ return cached;
+ }
+
auto cache_it = parsed_type_infos_.find(meta_header);
if (cache_it != parsed_type_infos_.end()) {
// Found in cache - reuse and skip the bytes
- reading_type_infos_.push_back(cache_it->second);
+ const TypeInfo *cached = cache_it->second;
+ reading_type_infos_.push_back(cached);
+ has_last_meta_header_ = true;
+ last_meta_header_ = meta_header;
+ last_meta_type_info_ = cached;
+ if (cached && !cached->type_def.empty()) {
+ const size_t type_def_size = cached->type_def.size();
+ if (type_def_size >= sizeof(int64_t) &&
+ type_def_size <= std::numeric_limits<uint32_t>::max()) {
+ Error skip_error;
+ buffer_->skip(static_cast<uint32_t>(type_def_size - sizeof(int64_t)),
+ skip_error);
+ if (FORY_PREDICT_FALSE(!skip_error.ok())) {
+ return Unexpected(std::move(skip_error));
+ }
+ return cached;
+ }
+ }
FORY_RETURN_NOT_OK(TypeMeta::skip_bytes(*buffer_, meta_header));
- return cache_it->second;
+ return cached;
}
// Not in cache - parse the TypeMeta
@@ -525,11 +600,16 @@ Result<const TypeInfo *, Error>
ReadContext::read_type_meta() {
const TypeInfo *raw_ptr = type_info.get();
// Store in primary storage
- owned_reading_type_infos_.push_back(std::move(type_info));
-
- // Cache the parsed TypeInfo (with size limit to prevent OOM)
if (parsed_type_infos_.size() < k_max_parsed_num_type_defs) {
+ cached_type_infos_.push_back(std::move(type_info));
+ raw_ptr = cached_type_infos_.back().get();
parsed_type_infos_[meta_header] = raw_ptr;
+ has_last_meta_header_ = true;
+ last_meta_header_ = meta_header;
+ last_meta_type_info_ = raw_ptr;
+ } else {
+ owned_reading_type_infos_.push_back(std::move(type_info));
+ raw_ptr = owned_reading_type_infos_.back().get();
}
reading_type_infos_.push_back(raw_ptr);
@@ -607,7 +687,6 @@ void ReadContext::reset() {
error_ = Error();
ref_reader_.reset();
reading_type_infos_.clear();
- parsed_type_infos_.clear();
owned_reading_type_infos_.clear();
current_dyn_depth_ = 0;
meta_string_table_.reset();
diff --git a/cpp/fory/serialization/context.h b/cpp/fory/serialization/context.h
index be11dd2dc..18e5e68bd 100644
--- a/cpp/fory/serialization/context.h
+++ b/cpp/fory/serialization/context.h
@@ -26,6 +26,7 @@
#include "fory/type/type.h"
#include "fory/util/buffer.h"
#include "fory/util/error.h"
+#include "fory/util/flat_int_map.h"
#include "fory/util/result.h"
#include "absl/container/flat_hash_map.h"
@@ -331,7 +332,11 @@ private:
// Meta sharing state (for streaming inline TypeMeta)
// Maps TypeInfo* to index for reference tracking - uses map size as counter
- absl::flat_hash_map<const TypeInfo *, size_t> write_type_info_index_map_;
+ util::FlatIntMap<uint64_t, uint32_t> write_type_info_index_map_;
+ // Fast path for the common single-type stream: avoid hash map lookups.
+ const TypeInfo *first_type_info_ = nullptr;
+ bool has_first_type_info_ = false;
+ bool type_info_index_map_active_ = false;
};
/// Read context for deserialization operations.
@@ -607,12 +612,18 @@ private:
uint32_t current_dyn_depth_;
// Meta sharing state (for compatible mode)
- // Primary storage for TypeInfo objects created during deserialization
+ // Per-message storage for TypeInfo objects not cached across messages.
std::vector<std::unique_ptr<TypeInfo>> owned_reading_type_infos_;
+ // Persistent cache storage for TypeInfo objects keyed by meta header.
+ std::vector<std::unique_ptr<TypeInfo>> cached_type_infos_;
// Index-based access (pointers to owned_reading_type_infos_ or
type_resolver)
std::vector<const TypeInfo *> reading_type_infos_;
- // Cache by meta_header (pointers to owned_reading_type_infos_)
+ // Cache by meta_header (pointers to cached_type_infos_)
absl::flat_hash_map<int64_t, const TypeInfo *> parsed_type_infos_;
+ // Fast path for repeated type meta headers.
+ int64_t last_meta_header_ = 0;
+ const TypeInfo *last_meta_type_info_ = nullptr;
+ bool has_last_meta_header_ = false;
// Dynamic meta strings used for named type/class info.
meta::MetaStringTable meta_string_table_;
diff --git a/cpp/fory/serialization/struct_serializer.h
b/cpp/fory/serialization/struct_serializer.h
index b864539b1..bb4a24bb8 100644
--- a/cpp/fory/serialization/struct_serializer.h
+++ b/cpp/fory/serialization/struct_serializer.h
@@ -681,6 +681,47 @@ template <typename T> struct CompileTimeFieldHelpers {
}
}
+ /// Returns true if the field needs per-field type info in compatible mode.
+ /// This matches write_single_field/read_single_field logic:
+ /// - struct/ext fields always write type info in compatible mode
+ /// - polymorphic fields write type info when dynamic_value is AUTO/TRUE
+ template <size_t Index>
+ static constexpr bool field_needs_type_info_in_compatible() {
+ if constexpr (FieldCount == 0) {
+ return false;
+ } else {
+ using PtrT = std::tuple_element_t<Index, FieldPtrs>;
+ using RawFieldType = meta::RemoveMemberPointerCVRefT<PtrT>;
+ using FieldType = unwrap_field_t<RawFieldType>;
+
+ constexpr TypeId field_type_id = Serializer<FieldType>::type_id;
+ constexpr bool is_struct = is_struct_type(field_type_id);
+ constexpr bool is_ext = is_ext_type(field_type_id);
+ constexpr bool is_polymorphic = field_type_id == TypeId::UNKNOWN;
+ constexpr int dynamic_val = field_dynamic_value<Index>();
+
+ constexpr bool polymorphic_write_type =
+ (dynamic_val == 1) || (dynamic_val == -1 && is_polymorphic);
+ return polymorphic_write_type || is_struct || is_ext;
+ }
+ }
+
+ template <size_t... Indices>
+ static constexpr bool
+ any_field_needs_type_info_in_compatible(std::index_sequence<Indices...>) {
+ if constexpr (FieldCount == 0) {
+ return false;
+ } else {
+ return (field_needs_type_info_in_compatible<Indices>() || ...);
+ }
+ }
+
+ /// True if it's safe to use schema-consistent fast path in compatible mode
+ /// (no struct/ext fields and no polymorphic fields that require type info).
+ static constexpr bool strict_compatible_safe =
+ !any_field_needs_type_info_in_compatible(
+ std::make_index_sequence<FieldCount>{});
+
/// get the underlying field type (unwraps fory::field<> if present)
template <size_t Index> struct UnwrappedFieldTypeHelper {
using PtrT = std::tuple_element_t<Index, FieldPtrs>;
@@ -2750,6 +2791,53 @@ void read_struct_fields_impl(T &obj, ReadContext &ctx,
(read_field_at_sorted_position<T, Indices>(obj, ctx), ...);
}
+/// Read struct fields in sorted order using the fast primitive paths.
+/// Used when compatible mode is enabled but the remote schema matches locally.
+template <typename T, size_t... Indices>
+FORY_ALWAYS_INLINE void
+read_struct_fields_impl_fast(T &obj, ReadContext &ctx,
+ std::index_sequence<Indices...>) {
+ using Helpers = CompileTimeFieldHelpers<T>;
+ constexpr size_t fixed_count = Helpers::leading_fixed_count;
+ constexpr size_t fixed_bytes = Helpers::leading_fixed_size_bytes;
+ constexpr size_t varint_count = Helpers::varint_count;
+ constexpr size_t total_count = sizeof...(Indices);
+
+ Buffer &buffer = ctx.buffer();
+
+ // Phase 1: Read leading fixed-size primitives if any
+ if constexpr (fixed_count > 0 && fixed_bytes > 0) {
+ // Pre-check bounds for all fixed-size fields at once
+ if (FORY_PREDICT_FALSE(buffer.reader_index() + fixed_bytes >
+ buffer.size())) {
+ ctx.set_error(Error::buffer_out_of_bound(buffer.reader_index(),
+ fixed_bytes, buffer.size()));
+ return;
+ }
+ // Fast read fixed-size primitives
+ read_fixed_primitive_fields<T>(obj, buffer,
+ std::make_index_sequence<fixed_count>{});
+ }
+
+ // Phase 2: Read consecutive varint primitives (int32, int64) if any
+ if constexpr (varint_count > 0) {
+ // Track offset locally for batch varint reading
+ uint32_t offset = buffer.reader_index();
+ // Fast read varint primitives (bounds checking happens in
+ // get_var_uint32/64)
+ read_varint_primitive_fields<T, fixed_count>(
+ obj, buffer, offset, std::make_index_sequence<varint_count>{});
+ // Update reader_index once after all varints
+ buffer.reader_index(offset);
+ }
+
+ // Phase 3: Read remaining fields (if any) with normal path
+ constexpr size_t fast_count = fixed_count + varint_count;
+ if constexpr (fast_count < total_count) {
+ read_remaining_fields<T, fast_count, total_count>(obj, ctx);
+ }
+}
+
/// Read struct fields with schema evolution (compatible mode)
/// Reads fields in remote schema order, dispatching by field_id to local
fields
template <typename T, size_t... Indices>
@@ -3098,6 +3186,7 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
static T read_compatible(ReadContext &ctx, const TypeInfo *remote_type_info)
{
// Read and verify struct version if enabled (matches write_data behavior)
+ const TypeInfo *local_type_info = nullptr;
if (ctx.check_struct_version()) {
int32_t read_version = ctx.buffer().read_int32(ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
@@ -3109,7 +3198,7 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
ctx.set_error(std::move(local_type_info_res).error());
return T{};
}
- const TypeInfo *local_type_info = local_type_info_res.value();
+ local_type_info = local_type_info_res.value();
if (!local_type_info->type_meta) {
ctx.set_error(Error::type_error(
"Type metadata not initialized for requested struct"));
@@ -3123,6 +3212,19 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
ctx.set_error(std::move(version_res).error());
return T{};
}
+ } else {
+ auto local_type_info_res =
+ ctx.type_resolver().template get_type_info<T>();
+ if (!local_type_info_res.ok()) {
+ ctx.set_error(std::move(local_type_info_res).error());
+ return T{};
+ }
+ local_type_info = local_type_info_res.value();
+ if (!local_type_info->type_meta) {
+ ctx.set_error(Error::type_error(
+ "Type metadata not initialized for requested struct"));
+ return T{};
+ }
}
T obj{};
@@ -3136,6 +3238,29 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
return T{};
}
+ // Fast path: same schema hash, read fields in local sorted order.
+ if (local_type_info &&
+ remote_type_info->type_meta->hash == local_type_info->type_meta->hash)
{
+ if constexpr (detail::CompileTimeFieldHelpers<
+ T>::strict_compatible_safe) {
+ // Safe to use schema-consistent fast path (no per-field type info).
+ detail::read_struct_fields_impl_fast(
+ obj, ctx, std::make_index_sequence<field_count>{});
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return T{};
+ }
+ return obj;
+ }
+
+ // Compatible fast path: same order, but allow per-field type info.
+ detail::read_struct_fields_impl_fast(
+ obj, ctx, std::make_index_sequence<field_count>{});
+ if (FORY_PREDICT_FALSE(ctx.has_error())) {
+ return T{};
+ }
+ return obj;
+ }
+
// Use remote TypeMeta for schema evolution - field IDs already assigned
detail::read_struct_fields_compatible(
obj, ctx, remote_type_info->type_meta.get(),
diff --git a/cpp/fory/serialization/struct_test.cc
b/cpp/fory/serialization/struct_test.cc
index bd2713fe6..f02b66402 100644
--- a/cpp/fory/serialization/struct_test.cc
+++ b/cpp/fory/serialization/struct_test.cc
@@ -80,6 +80,32 @@ struct ManyFieldsStruct {
FORY_STRUCT(ManyFieldsStruct, b1, i8, i16, i32, i64, f32, f64, str);
};
+struct FieldConfigTaggedStruct {
+ int32_t a;
+ int64_t b;
+ std::string c;
+
+ bool operator==(const FieldConfigTaggedStruct &other) const {
+ return a == other.a && b == other.b && c == other.c;
+ }
+ FORY_STRUCT(FieldConfigTaggedStruct, a, b, c);
+};
+
+struct FieldTagsTaggedStruct {
+ int32_t a;
+ int64_t b;
+ std::string c;
+
+ bool operator==(const FieldTagsTaggedStruct &other) const {
+ return a == other.a && b == other.b && c == other.c;
+ }
+ FORY_STRUCT(FieldTagsTaggedStruct, a, b, c);
+};
+
+FORY_FIELD_CONFIG(FieldConfigTaggedStruct, (a, fory::F().id(1)),
+ (b, fory::F().id(2)), (c, fory::F().id(3)));
+FORY_FIELD_TAGS(FieldTagsTaggedStruct, (a, 1), (b, 2), (c, 3));
+
class PrivateFieldsStruct {
public:
PrivateFieldsStruct() = default;
@@ -474,6 +500,28 @@ TEST(StructComprehensiveTest, ManyFieldsStruct) {
-9223372036854775807LL - 1, -1.0f, -1.0,
""});
}
+TEST(StructComprehensiveTest, FieldTagsMatchFieldConfigSize) {
+ FieldConfigTaggedStruct config_obj{1, 2, "config"};
+ FieldTagsTaggedStruct tags_obj{1, 2, "config"};
+
+ auto fory_config =
+ Fory::builder().xlang(true).compatible(true).track_ref(false).build();
+ auto fory_tags =
+ Fory::builder().xlang(true).compatible(true).track_ref(false).build();
+
+ ASSERT_TRUE(fory_config.register_struct<FieldConfigTaggedStruct>(101).ok());
+ ASSERT_TRUE(fory_tags.register_struct<FieldTagsTaggedStruct>(101).ok());
+
+ auto config_bytes = fory_config.serialize(config_obj);
+ ASSERT_TRUE(config_bytes.ok())
+ << "Serialization failed: " << config_bytes.error().to_string();
+ auto tags_bytes = fory_tags.serialize(tags_obj);
+ ASSERT_TRUE(tags_bytes.ok())
+ << "Serialization failed: " << tags_bytes.error().to_string();
+
+ EXPECT_EQ(config_bytes->size(), tags_bytes->size());
+}
+
TEST(StructComprehensiveTest, PrivateFieldsStruct) {
test_roundtrip(PrivateFieldsStruct{42, "secret", {1, 2, 3}});
}
diff --git a/cpp/fory/serialization/type_resolver.cc
b/cpp/fory/serialization/type_resolver.cc
index 87d8003a8..a5c993017 100644
--- a/cpp/fory/serialization/type_resolver.cc
+++ b/cpp/fory/serialization/type_resolver.cc
@@ -160,10 +160,10 @@ Result<std::vector<uint8_t>, Error> FieldInfo::to_bytes()
const {
}
encoded_name = std::move(encoded.bytes);
}
- size_t name_size =
- use_tag_id ? static_cast<size_t>(field_id) + 1 : encoded_name.size();
+ const size_t size_field =
+ use_tag_id ? static_cast<size_t>(field_id) : encoded_name.size() - 1;
uint8_t header =
- (std::min(FIELD_NAME_SIZE_THRESHOLD, name_size - 1) << 2) & 0x3C;
+ (std::min(FIELD_NAME_SIZE_THRESHOLD, size_field) << 2) & 0x3C;
if (field_type.track_ref) {
header |= 1; // bit 0 for ref tracking
@@ -175,8 +175,8 @@ Result<std::vector<uint8_t>, Error> FieldInfo::to_bytes()
const {
buffer.write_uint8(header);
- if (name_size - 1 >= FIELD_NAME_SIZE_THRESHOLD) {
- buffer.write_var_uint32(name_size - 1 - FIELD_NAME_SIZE_THRESHOLD);
+ if (size_field >= FIELD_NAME_SIZE_THRESHOLD) {
+ buffer.write_var_uint32(size_field - FIELD_NAME_SIZE_THRESHOLD);
}
// write field type
@@ -209,15 +209,14 @@ Result<FieldInfo, Error> FieldInfo::from_bytes(Buffer
&buffer) {
bool use_tag_id = encoding_idx == 3;
bool track_ref = (header & 0b01u) != 0;
bool nullable = (header & 0b10u) != 0;
- size_t name_size = ((header >> 2) & FIELD_NAME_SIZE_THRESHOLD);
- if (name_size == FIELD_NAME_SIZE_THRESHOLD) {
+ size_t size_field = ((header >> 2) & FIELD_NAME_SIZE_THRESHOLD);
+ if (size_field == FIELD_NAME_SIZE_THRESHOLD) {
uint32_t extra = buffer.read_var_uint32(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
return Unexpected(std::move(error));
}
- name_size += extra;
+ size_field += extra;
}
- name_size += 1;
// Read field type with nullable and track_ref from header
FORY_TRY(field_type,
@@ -225,7 +224,7 @@ Result<FieldInfo, Error> FieldInfo::from_bytes(Buffer
&buffer) {
if (use_tag_id) {
FieldInfo info("", std::move(field_type));
- info.field_id = static_cast<int16_t>(name_size - 1);
+ info.field_id = static_cast<int16_t>(size_field);
return info;
}
@@ -236,6 +235,7 @@ Result<FieldInfo, Error> FieldInfo::from_bytes(Buffer
&buffer) {
// We mirror that here using MetaStringDecoder with '$' and '_' as
// special characters (same as Encoders.FIELD_NAME_DECODER).
+ const size_t name_size = size_field + 1;
std::vector<uint8_t> name_bytes(name_size);
buffer.read_bytes(name_bytes.data(), static_cast<uint32_t>(name_size),
error);
if (FORY_PREDICT_FALSE(!error.ok())) {
diff --git a/cpp/fory/serialization/type_resolver.h
b/cpp/fory/serialization/type_resolver.h
index 6bdf6521f..e3489b44a 100644
--- a/cpp/fory/serialization/type_resolver.h
+++ b/cpp/fory/serialization/type_resolver.h
@@ -713,6 +713,12 @@ constexpr int16_t compute_field_id() {
if constexpr (is_fory_field_v<ActualFieldType>) {
return field_tag_id_v<ActualFieldType>;
}
+ if constexpr (::fory::detail::has_field_tags_v<T>) {
+ constexpr int16_t tag_id = ::fory::detail::GetFieldTagEntry<T, Index>::id;
+ if constexpr (tag_id >= 0) {
+ return tag_id;
+ }
+ }
return -1;
}
diff --git a/cpp/fory/serialization/xlang_test_main.cc
b/cpp/fory/serialization/xlang_test_main.cc
index cf8e087f1..69ecaf49b 100644
--- a/cpp/fory/serialization/xlang_test_main.cc
+++ b/cpp/fory/serialization/xlang_test_main.cc
@@ -503,22 +503,6 @@ struct RefOuterSchemaConsistent {
}
FORY_STRUCT(RefOuterSchemaConsistent, inner1, inner2);
};
-FORY_FIELD_TAGS(RefOuterSchemaConsistent, (inner1, 0, nullable, ref),
- (inner2, 1, nullable, ref));
-// Verify field tags are correctly parsed
-static_assert(fory::detail::has_field_tags_v<RefOuterSchemaConsistent>,
- "RefOuterSchemaConsistent should have field tags");
-static_assert(fory::detail::GetFieldTagEntry<RefOuterSchemaConsistent, 0>::id
==
- 0,
- "inner1 should have id=0");
-static_assert(
- fory::detail::GetFieldTagEntry<RefOuterSchemaConsistent, 0>::is_nullable ==
- true,
- "inner1 should be nullable");
-static_assert(
- fory::detail::GetFieldTagEntry<RefOuterSchemaConsistent, 0>::track_ref ==
- true,
- "inner1 should have track_ref=true");
// Inner struct for reference tracking test (COMPATIBLE mode)
// Matches Java RefInnerCompatible with type ID 503
@@ -552,8 +536,6 @@ struct RefOuterCompatible {
}
FORY_STRUCT(RefOuterCompatible, inner1, inner2);
};
-FORY_FIELD_TAGS(RefOuterCompatible, (inner1, 0, nullable, ref),
- (inner2, 1, nullable, ref));
// Element struct for collection element ref override test
// Matches Java RefOverrideElement with type ID 701
@@ -603,7 +585,6 @@ struct CircularRefStruct {
}
FORY_STRUCT(CircularRefStruct, name, self_ref);
};
-FORY_FIELD_TAGS(CircularRefStruct, (name, 0), (self_ref, 1, nullable, ref));
// ============================================================================
// Unsigned Number Test Types
diff --git a/go/fory/field_info.go b/go/fory/field_info.go
index c10766a84..fb92d0b1e 100644
--- a/go/fory/field_info.go
+++ b/go/fory/field_info.go
@@ -60,8 +60,9 @@ type FieldMeta struct {
FixedSize int // 0 if not fixed-size, else 1/2/4/8
// Pre-computed flags for serialization (computed at init time)
- WriteType bool // whether to write type info (true for struct fields
in compatible mode)
- HasGenerics bool // whether element types are known from TypeDef (for
container fields)
+ WriteType bool // whether to write type info (true for struct
fields in compatible mode)
+ CachedTypeInfo *TypeInfo
+ HasGenerics bool // whether element types are known from TypeDef
(for container fields)
// Tag-based configuration (from fory struct tags)
TagID int // -1 = use field name, >=0 = use tag ID
diff --git a/go/fory/fory.go b/go/fory/fory.go
index 97b9dc4ae..09a0e3c6d 100644
--- a/go/fory/fory.go
+++ b/go/fory/fory.go
@@ -134,7 +134,7 @@ func New(opts ...Option) *Fory {
// Initialize meta context if compatible mode is enabled
if f.config.Compatible {
f.metaContext = &MetaContext{
- typeMap: make(map[reflect.Type]uint32),
+ typeMap: make(map[uintptr]uint32),
readTypeInfos: make([]*TypeInfo, 0),
scopedMetaShareEnable: true,
}
diff --git a/go/fory/pointer.go b/go/fory/pointer.go
index 50a9809b0..b30a15f53 100644
--- a/go/fory/pointer.go
+++ b/go/fory/pointer.go
@@ -94,6 +94,47 @@ func (s *ptrToValueSerializer) Write(ctx *WriteContext,
refMode RefMode, writeTy
s.WriteData(ctx, value)
}
+func (s *ptrToValueSerializer) writeWithTypeInfo(ctx *WriteContext, refMode
RefMode, value reflect.Value, typeInfo *TypeInfo) {
+ switch refMode {
+ case RefModeTracking:
+ if value.IsNil() {
+ ctx.Buffer().WriteInt8(NullFlag)
+ return
+ }
+ refWritten, err :=
ctx.RefResolver().WriteRefOrNull(ctx.Buffer(), value)
+ if err != nil {
+ ctx.SetError(FromError(err))
+ return
+ }
+ if refWritten {
+ return
+ }
+ case RefModeNullOnly:
+ if value.IsNil() {
+ ctx.Buffer().WriteInt8(NullFlag)
+ return
+ }
+ ctx.Buffer().WriteInt8(NotNullValueFlag)
+ case RefModeNone:
+ if value.IsNil() {
+ zeroValue := reflect.New(value.Type().Elem()).Elem()
+ if typeInfo == nil {
+ s.Write(ctx, refMode, true, false, value)
+ return
+ }
+ ctx.TypeResolver().WriteTypeInfo(ctx.Buffer(),
typeInfo, ctx.Err())
+ s.valueSerializer.WriteData(ctx, zeroValue)
+ return
+ }
+ }
+ if typeInfo == nil {
+ s.Write(ctx, refMode, true, false, value)
+ return
+ }
+ ctx.TypeResolver().WriteTypeInfo(ctx.Buffer(), typeInfo, ctx.Err())
+ s.WriteData(ctx, value)
+}
+
func (s *ptrToValueSerializer) ReadData(ctx *ReadContext, value reflect.Value)
{
// Check if value is already allocated (for circular reference handling)
var newVal reflect.Value
diff --git a/go/fory/struct.go b/go/fory/struct.go
index 123f53b7f..2cd21991b 100644
--- a/go/fory/struct.go
+++ b/go/fory/struct.go
@@ -114,6 +114,36 @@ func (s *structSerializer) Write(ctx *WriteContext,
refMode RefMode, writeType b
s.WriteData(ctx, value)
}
+func (s *structSerializer) writeWithTypeInfo(ctx *WriteContext, refMode
RefMode, value reflect.Value, typeInfo *TypeInfo) {
+ switch refMode {
+ case RefModeTracking:
+ if value.Kind() == reflect.Ptr && value.IsNil() {
+ ctx.buffer.WriteInt8(NullFlag)
+ return
+ }
+ refWritten, err := ctx.RefResolver().WriteRefOrNull(ctx.buffer,
value)
+ if err != nil {
+ ctx.SetError(FromError(err))
+ return
+ }
+ if refWritten {
+ return
+ }
+ case RefModeNullOnly:
+ if value.Kind() == reflect.Ptr && value.IsNil() {
+ ctx.buffer.WriteInt8(NullFlag)
+ return
+ }
+ ctx.buffer.WriteInt8(NotNullValueFlag)
+ }
+ if typeInfo == nil {
+ s.Write(ctx, refMode, true, false, value)
+ return
+ }
+ ctx.TypeResolver().WriteTypeInfo(ctx.buffer, typeInfo, ctx.Err())
+ s.WriteData(ctx, value)
+}
+
func (s *structSerializer) WriteData(ctx *WriteContext, value reflect.Value) {
// Early error check - skip all intermediate checks for normal path
performance
if ctx.HasError() {
@@ -398,6 +428,9 @@ func (s *structSerializer) writeRemainingField(ctx
*WriteContext, ptr unsafe.Poi
}
fieldValue := value.Field(field.Meta.FieldIndex)
if field.Serializer != nil {
+ if writeWithCachedTypeInfo(ctx, field, fieldValue) {
+ return
+ }
field.Serializer.Write(ctx, field.RefMode,
field.Meta.WriteType, field.Meta.HasGenerics, fieldValue)
} else {
ctx.WriteValue(fieldValue, RefModeTracking, true)
@@ -774,12 +807,31 @@ func (s *structSerializer) writeRemainingField(ctx
*WriteContext, ptr unsafe.Poi
// Fall back to serializer for other types
fieldValue := value.Field(field.Meta.FieldIndex)
if field.Serializer != nil {
+ if writeWithCachedTypeInfo(ctx, field, fieldValue) {
+ return
+ }
field.Serializer.Write(ctx, field.RefMode,
field.Meta.WriteType, field.Meta.HasGenerics, fieldValue)
} else {
ctx.WriteValue(fieldValue, RefModeTracking, true)
}
}
+func writeWithCachedTypeInfo(ctx *WriteContext, field *FieldInfo, fieldValue
reflect.Value) bool {
+ if field.Serializer == nil || !field.Meta.WriteType ||
field.Meta.CachedTypeInfo == nil {
+ return false
+ }
+ switch ser := field.Serializer.(type) {
+ case *structSerializer:
+ ser.writeWithTypeInfo(ctx, field.RefMode, fieldValue,
field.Meta.CachedTypeInfo)
+ return true
+ case *ptrToValueSerializer:
+ ser.writeWithTypeInfo(ctx, field.RefMode, fieldValue,
field.Meta.CachedTypeInfo)
+ return true
+ default:
+ return false
+ }
+}
+
func loadFieldValue[T any](kind FieldKind, fieldPtr unsafe.Pointer, opt
optionalInfo) (T, bool) {
var zero T
switch kind {
diff --git a/go/fory/struct_init.go b/go/fory/struct_init.go
index 8dc8eae5a..3b994b10f 100644
--- a/go/fory/struct_init.go
+++ b/go/fory/struct_init.go
@@ -373,6 +373,14 @@ func (s *structSerializer) initFields(typeResolver
*TypeResolver) error {
}
// Pre-compute WriteType: true for struct fields in compatible
mode
writeType := typeResolver.Compatible() &&
isStructField(baseType)
+ var cachedTypeInfo *TypeInfo
+ if writeType {
+ cachedType := baseType
+ if cachedType.Kind() == reflect.Ptr {
+ cachedType = cachedType.Elem()
+ }
+ cachedTypeInfo =
typeResolver.getTypeInfoByType(cachedType)
+ }
// Pre-compute DispatchId, with special handling for enum
fields and pointer-to-numeric
dispatchId := getDispatchIdFromTypeId(fieldTypeId, nullableFlag)
@@ -410,6 +418,7 @@ func (s *structSerializer) initFields(typeResolver
*TypeResolver) error {
Nullable: nullableFlag, // Use same logic
as TypeDef's nullable flag for consistent ref handling
FieldIndex: i,
WriteType: writeType,
+ CachedTypeInfo: cachedTypeInfo,
HasGenerics: isCollectionType(fieldTypeId),
// Container fields have declared element types
OptionalInfo: optionalInfo,
TagID: foryTag.ID,
@@ -847,6 +856,14 @@ func (s *structSerializer)
initFieldsFromTypeDef(typeResolver *TypeResolver) err
}
// Pre-compute WriteType: true for struct fields in compatible
mode
writeType := typeResolver.Compatible() &&
isStructField(baseType)
+ var cachedTypeInfo *TypeInfo
+ if writeType {
+ cachedType := baseType
+ if cachedType.Kind() == reflect.Ptr {
+ cachedType = cachedType.Elem()
+ }
+ cachedTypeInfo =
typeResolver.getTypeInfoByType(cachedType)
+ }
// Pre-compute DispatchId, with special handling for
pointer-to-numeric and enum fields
// IMPORTANT: For compatible mode reading, we must use the
REMOTE nullable flag
@@ -905,17 +922,18 @@ func (s *structSerializer)
initFieldsFromTypeDef(typeResolver *TypeResolver) err
Kind: fieldKind,
Serializer: fieldSerializer,
Meta: &FieldMeta{
- Name: fieldName,
- Type: fieldType,
- TypeId: fieldTypeId,
- Nullable: def.nullable, // Use remote
nullable flag
- FieldIndex: fieldIndex,
- FieldDef: def, // Save original FieldDef
for skipping
- WriteType: writeType,
- HasGenerics: isCollectionType(fieldTypeId), //
Container fields have declared element types
- OptionalInfo: optionalInfo,
- TagID: def.tagID,
- HasForyTag: def.tagID >= 0,
+ Name: fieldName,
+ Type: fieldType,
+ TypeId: fieldTypeId,
+ Nullable: def.nullable, // Use remote
nullable flag
+ FieldIndex: fieldIndex,
+ FieldDef: def, // Save original FieldDef
for skipping
+ WriteType: writeType,
+ CachedTypeInfo: cachedTypeInfo,
+ HasGenerics: isCollectionType(fieldTypeId),
// Container fields have declared element types
+ OptionalInfo: optionalInfo,
+ TagID: def.tagID,
+ HasForyTag: def.tagID >= 0,
},
}
fields = append(fields, fieldInfo)
diff --git a/go/fory/type_def.go b/go/fory/type_def.go
index 9a0d41621..f2bca01a6 100644
--- a/go/fory/type_def.go
+++ b/go/fory/type_def.go
@@ -54,6 +54,7 @@ type TypeDef struct {
fieldDefs []FieldDef
encoded []byte
type_ reflect.Type
+ cachedTypeInfo *TypeInfo
}
func NewTypeDef(typeId uint32, userTypeId uint32, nsName, typeName
*MetaStringBytes, registerByName, compressed bool, fieldDefs []FieldDef)
*TypeDef {
@@ -264,6 +265,18 @@ func (td *TypeDef) buildTypeInfoWithResolver(resolver
*TypeResolver) (TypeInfo,
return info, nil
}
+func (td *TypeDef) getOrBuildTypeInfo(resolver *TypeResolver) (*TypeInfo,
error) {
+ if td.cachedTypeInfo != nil {
+ return td.cachedTypeInfo, nil
+ }
+ info, err := td.buildTypeInfoWithResolver(resolver)
+ if err != nil {
+ return nil, err
+ }
+ td.cachedTypeInfo = &info
+ return td.cachedTypeInfo, nil
+}
+
func readTypeDef(fory *Fory, buffer *ByteBuffer, header int64, err *Error)
*TypeDef {
td, decodeErr := decodeTypeDef(fory, buffer, header)
if decodeErr != nil {
diff --git a/go/fory/type_resolver.go b/go/fory/type_resolver.go
index ebb4c416f..bd49a03e1 100644
--- a/go/fory/type_resolver.go
+++ b/go/fory/type_resolver.go
@@ -1460,36 +1460,96 @@ func (r *TypeResolver) WriteTypeInfo(buffer
*ByteBuffer, typeInfo *TypeInfo, err
func (r *TypeResolver) writeSharedTypeMeta(buffer *ByteBuffer, typeInfo
*TypeInfo, err *Error) {
context := r.fory.MetaContext()
- typ := typeInfo.Type
-
- if index, exists := context.typeMap[typ]; exists {
- // Reference to previously written type: (index << 1) | 1, LSB=1
- buffer.WriteVarUint32((index << 1) | 1)
- return
- }
-
- // New type: index << 1, LSB=0, followed by TypeDef bytes inline
- newIndex := uint32(len(context.typeMap))
- buffer.WriteVarUint32(newIndex << 1)
- context.typeMap[typ] = newIndex
-
- // Only build TypeDef for struct types - enums don't have field
definitions
- actualType := typ
- if actualType.Kind() == reflect.Ptr {
- actualType = actualType.Elem()
+ key := typePointer(typeInfo.Type)
+ writeTypeDefInline := func() {
+ // Only build TypeDef for struct types - enums don't have field
definitions
+ actualType := typeInfo.Type
+ if actualType.Kind() == reflect.Ptr {
+ actualType = actualType.Elem()
+ }
+ if actualType.Kind() == reflect.Struct {
+ typeDef, typeDefErr := r.getTypeDef(typeInfo.Type, true)
+ if typeDefErr != nil {
+ err.SetError(typeDefErr)
+ return
+ }
+ // Write TypeDef bytes inline
+ typeDef.writeTypeDef(buffer, err)
+ }
}
- if actualType.Kind() == reflect.Struct {
+ writeTypeDefWithZeroMarker := func() {
+ actualType := typeInfo.Type
+ if actualType.Kind() == reflect.Ptr {
+ actualType = actualType.Elem()
+ }
+ if actualType.Kind() != reflect.Struct {
+ buffer.WriteUint8(0)
+ return
+ }
typeDef, typeDefErr := r.getTypeDef(typeInfo.Type, true)
if typeDefErr != nil {
err.SetError(typeDefErr)
return
}
- // Write TypeDef bytes inline
+ buffer.WriteUint8(0)
typeDef.writeTypeDef(buffer, err)
}
+ if !context.typeMapActive {
+ if !context.hasFirstType {
+ context.hasFirstType = true
+ context.firstTypePtr = key
+ // New type: index << 1, LSB=0, followed by TypeDef
bytes inline
+ writeTypeDefWithZeroMarker()
+ return
+ }
+ if key == context.firstTypePtr {
+ // Reference to first type: (0 << 1) | 1
+ buffer.WriteUint8(1)
+ return
+ }
+ context.typeMapActive = true
+ if context.typeMap == nil {
+ context.typeMap = make(map[uintptr]uint32, 8)
+ } else if len(context.typeMap) != 0 {
+ for k := range context.typeMap {
+ delete(context.typeMap, k)
+ }
+ }
+ context.typeMap[context.firstTypePtr] = 0
+ } else if key == context.firstTypePtr {
+ buffer.WriteUint8(1)
+ return
+ }
+
+ if index, exists := context.typeMap[key]; exists {
+ // Reference to previously written type: (index << 1) | 1, LSB=1
+ marker := (index << 1) | 1
+ if marker < 0x80 {
+ buffer.WriteUint8(uint8(marker))
+ } else {
+ buffer.WriteVarUint32(marker)
+ }
+ return
+ }
+
+ // New type: index << 1, LSB=0, followed by TypeDef bytes inline
+ newIndex := uint32(len(context.typeMap))
+ marker := newIndex << 1
+ if marker < 0x80 {
+ buffer.WriteUint8(uint8(marker))
+ } else {
+ buffer.WriteVarUint32(marker)
+ }
+ context.typeMap[key] = newIndex
+ writeTypeDefInline()
}
func (r *TypeResolver) getTypeDef(typ reflect.Type, create bool) (*TypeDef,
error) {
+ // Normalize pointer types to their element type for consistent caching.
+ if typ.Kind() == reflect.Ptr {
+ typ = typ.Elem()
+ }
+
if existingTypeDef, exists := r.typeToTypeDef[typ]; exists {
return existingTypeDef, nil
}
@@ -1498,10 +1558,6 @@ func (r *TypeResolver) getTypeDef(typ reflect.Type,
create bool) (*TypeDef, erro
return nil, fmt.Errorf("TypeDef not found for type %s", typ)
}
- // don't create TypeDef for pointer types, we create TypeDef for its
element type instead.
- if typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- }
zero := reflect.Zero(typ)
typeDef, err := buildTypeDef(r.fory, zero)
if err != nil {
@@ -1563,14 +1619,14 @@ func (r *TypeResolver) readSharedTypeMeta(buffer
*ByteBuffer, err *Error) *TypeI
td = newTd
}
- typeInfo, typeInfoErr := td.buildTypeInfoWithResolver(r)
+ typeInfo, typeInfoErr := td.getOrBuildTypeInfo(r)
if typeInfoErr != nil {
err.SetError(typeInfoErr)
return nil
}
- context.readTypeInfos = append(context.readTypeInfos, &typeInfo)
- return &typeInfo
+ context.readTypeInfos = append(context.readTypeInfos, typeInfo)
+ return typeInfo
}
func (r *TypeResolver) createSerializer(type_ reflect.Type, mapInStruct bool)
(s Serializer, err error) {
@@ -2341,9 +2397,12 @@ var ErrTypeMismatch = errors.New("fory: type ID
mismatch")
// MetaContext holds metadata for schema evolution and type sharing
type MetaContext struct {
- typeMap map[reflect.Type]uint32 // For writing: tracks
written types
- readTypeInfos []*TypeInfo // For reading: types
read inline
+ typeMap map[uintptr]uint32 // For writing: tracks written
types
+ readTypeInfos []*TypeInfo // For reading: types read
inline
scopedMetaShareEnable bool
+ firstTypePtr uintptr
+ hasFirstType bool
+ typeMapActive bool
}
// IsScopedMetaShareEnabled returns whether scoped meta share is enabled
@@ -2353,6 +2412,10 @@ func (m *MetaContext) IsScopedMetaShareEnabled() bool {
// Reset clears the meta context for reuse
func (m *MetaContext) Reset() {
- m.typeMap = make(map[reflect.Type]uint32)
- m.readTypeInfos = nil
+ m.hasFirstType = false
+ m.typeMapActive = false
+ m.firstTypePtr = 0
+ if m.readTypeInfos != nil {
+ m.readTypeInfos = m.readTypeInfos[:0]
+ }
}
diff --git a/rust/fory-core/src/error.rs b/rust/fory-core/src/error.rs
index d4c5ec488..10455ee79 100644
--- a/rust/fory-core/src/error.rs
+++ b/rust/fory-core/src/error.rs
@@ -33,8 +33,21 @@ use crate::types::format_type_id;
use thiserror::Error;
/// Global flag to check if FORY_PANIC_ON_ERROR environment variable is set at
compile time.
-/// Set FORY_PANIC_ON_ERROR=1 at compile time to enable panic on error.
-pub const PANIC_ON_ERROR: bool = option_env!("FORY_PANIC_ON_ERROR").is_some();
+/// Set FORY_PANIC_ON_ERROR=1 (or true) at compile time to enable panic on
error.
+const fn is_truthy_env(value: &str) -> bool {
+ let bytes = value.as_bytes();
+ (bytes.len() == 1 && bytes[0] == b'1')
+ || (bytes.len() == 4
+ && (bytes[0] | 0x20) == b't'
+ && (bytes[1] | 0x20) == b'r'
+ && (bytes[2] | 0x20) == b'u'
+ && (bytes[3] | 0x20) == b'e')
+}
+
+pub const PANIC_ON_ERROR: bool = match option_env!("FORY_PANIC_ON_ERROR") {
+ Some(value) => is_truthy_env(value),
+ None => false,
+};
/// Check if FORY_PANIC_ON_ERROR environment variable is set.
#[inline(always)]
diff --git a/rust/fory-core/src/meta/type_meta.rs
b/rust/fory-core/src/meta/type_meta.rs
index e67d76cb2..771fd2064 100644
--- a/rust/fory-core/src/meta/type_meta.rs
+++ b/rust/fory-core/src/meta/type_meta.rs
@@ -373,6 +373,52 @@ impl FieldInfo {
}
}
+const FNV_OFFSET_BASIS: u64 = 14695981039346656037;
+const FNV_PRIME: u64 = 1099511628211;
+
+#[inline(always)]
+fn fnv1a_hash_bytes(mut hash: u64, bytes: &[u8]) -> u64 {
+ for &b in bytes {
+ hash ^= b as u64;
+ hash = hash.wrapping_mul(FNV_PRIME);
+ }
+ hash
+}
+
+#[inline(always)]
+fn fnv1a_hash_u8(hash: u64, value: u8) -> u64 {
+ fnv1a_hash_bytes(hash, &[value])
+}
+
+#[inline(always)]
+fn fnv1a_hash_u32(hash: u64, value: u32) -> u64 {
+ fnv1a_hash_bytes(hash, &value.to_le_bytes())
+}
+
+#[inline(always)]
+fn hash_field_type(mut hash: u64, field_type: &FieldType) -> u64 {
+ let type_id = normalize_type_id_for_eq(field_type.type_id);
+ hash = fnv1a_hash_u32(hash, type_id);
+ hash = fnv1a_hash_u32(hash, field_type.user_type_id);
+ hash = fnv1a_hash_u8(hash, field_type.nullable as u8);
+ hash = fnv1a_hash_u8(hash, field_type.track_ref as u8);
+ hash = fnv1a_hash_u32(hash, field_type.generics.len() as u32);
+ for generic in &field_type.generics {
+ hash = hash_field_type(hash, generic);
+ }
+ hash
+}
+
+#[inline(always)]
+fn compute_schema_hash(field_infos: &[FieldInfo]) -> i64 {
+ let mut hash = fnv1a_hash_u32(FNV_OFFSET_BASIS, field_infos.len() as u32);
+ for field in field_infos {
+ hash = fnv1a_hash_bytes(hash, field.field_name.as_bytes());
+ hash = hash_field_type(hash, &field.field_type);
+ }
+ hash as i64
+}
+
/// Sorts field infos according to the provided sorted field names and assigns
field IDs.
///
/// This function takes a vector of field infos and a slice of sorted field
names,
@@ -436,6 +482,7 @@ impl PartialEq for FieldType {
pub struct TypeMeta {
// assigned valid value and used, only during deserializing
hash: i64,
+ schema_hash: i64,
type_id: u32,
user_type_id: u32,
namespace: Rc<MetaString>,
@@ -454,8 +501,10 @@ impl TypeMeta {
register_by_name: bool,
field_infos: Vec<FieldInfo>,
) -> Result<TypeMeta, Error> {
+ let schema_hash = compute_schema_hash(&field_infos);
let mut meta = TypeMeta {
hash: 0,
+ schema_hash,
type_id,
user_type_id,
namespace: Rc::from(namespace),
@@ -490,6 +539,11 @@ impl TypeMeta {
self.hash
}
+ #[inline(always)]
+ pub fn get_schema_hash(&self) -> i64 {
+ self.schema_hash
+ }
+
#[inline(always)]
pub fn get_type_name(&self) -> Rc<MetaString> {
self.type_name.clone()
@@ -509,6 +563,7 @@ impl TypeMeta {
pub fn empty() -> Result<TypeMeta, Error> {
Ok(TypeMeta {
hash: 0,
+ schema_hash: 0,
type_id: 0,
user_type_id: NO_USER_TYPE_ID,
namespace: Rc::from(MetaString::get_empty().clone()),
@@ -524,6 +579,7 @@ impl TypeMeta {
pub fn deep_clone(&self) -> TypeMeta {
TypeMeta {
hash: self.hash,
+ schema_hash: self.schema_hash,
type_id: self.type_id,
user_type_id: self.user_type_id,
namespace: Rc::new((*self.namespace).clone()),
diff --git a/rust/fory-core/src/resolver/meta_resolver.rs
b/rust/fory-core/src/resolver/meta_resolver.rs
index 2022233b9..1f1af26c8 100644
--- a/rust/fory-core/src/resolver/meta_resolver.rs
+++ b/rust/fory-core/src/resolver/meta_resolver.rs
@@ -77,6 +77,8 @@ impl MetaWriterResolver {
pub struct MetaReaderResolver {
pub reading_type_infos: Vec<Rc<TypeInfo>>,
parsed_type_infos: HashMap<i64, Rc<TypeInfo>>,
+ last_meta_header: i64,
+ last_type_info: Option<Rc<TypeInfo>>,
}
impl MetaReaderResolver {
@@ -105,7 +107,18 @@ impl MetaReaderResolver {
} else {
// New type - read TypeMeta inline
let meta_header = reader.read_i64()?;
+ if let Some(type_info) = self
+ .last_type_info
+ .as_ref()
+ .filter(|_| self.last_meta_header == meta_header)
+ {
+ self.reading_type_infos.push(type_info.clone());
+ TypeMeta::skip_bytes(reader, meta_header)?;
+ return Ok(type_info.clone());
+ }
if let Some(type_info) = self.parsed_type_infos.get(&meta_header) {
+ self.last_meta_header = meta_header;
+ self.last_type_info = Some(type_info.clone());
self.reading_type_infos.push(type_info.clone());
TypeMeta::skip_bytes(reader, meta_header)?;
Ok(type_info.clone())
@@ -190,6 +203,8 @@ impl MetaReaderResolver {
self.parsed_type_infos
.insert(meta_header, type_info.clone());
}
+ self.last_meta_header = meta_header;
+ self.last_type_info = Some(type_info.clone());
self.reading_type_infos.push(type_info.clone());
Ok(type_info)
}
diff --git a/rust/fory-core/src/resolver/type_resolver.rs
b/rust/fory-core/src/resolver/type_resolver.rs
index df7c8f2fa..c88f2fa61 100644
--- a/rust/fory-core/src/resolver/type_resolver.rs
+++ b/rust/fory-core/src/resolver/type_resolver.rs
@@ -234,6 +234,11 @@ impl TypeInfo {
self.type_meta.clone()
}
+ #[inline(always)]
+ pub fn get_type_meta_ref(&self) -> &TypeMeta {
+ self.type_meta.as_ref()
+ }
+
#[inline(always)]
pub fn is_registered_by_name(&self) -> bool {
self.register_by_name
@@ -495,6 +500,12 @@ pub struct TypeResolver {
partial_type_infos: HashMap<std::any::TypeId, TypeInfo>,
// Fast lookup by numeric ID for common types
type_id_index: Vec<TypeId>,
+ // Fast lookup by type index for user type IDs
+ user_type_id_index: Vec<u32>,
+ // Mapping from type index to Rust TypeId for fast meta lookup
+ rust_type_id_by_index: Vec<Option<std::any::TypeId>>,
+ // Fast lookup by type index for TypeMeta
+ type_meta_by_index: Vec<Option<Rc<crate::meta::TypeMeta>>>,
compatible: bool,
xlang: bool,
}
@@ -516,6 +527,9 @@ impl Default for TypeResolver {
type_info_map_by_name: HashMap::new(),
type_info_map_by_meta_string_name: HashMap::new(),
type_id_index: Vec::new(),
+ user_type_id_index: Vec::new(),
+ rust_type_id_by_index: Vec::new(),
+ type_meta_by_index: Vec::new(),
partial_type_infos: HashMap::new(),
compatible: false,
xlang: false,
@@ -588,6 +602,80 @@ impl TypeResolver {
)))
}
+ /// Fast path for getting type info by type index (avoids HashMap lookup
and TypeId::of)
+ #[inline(always)]
+ pub fn get_type_id_by_index(&self, index: u32) -> Result<TypeId, Error> {
+ let id_usize = index as usize;
+ if id_usize < self.type_id_index.len() {
+ let type_id_value = self.type_id_index[id_usize];
+ if type_id_value != NO_TYPE_ID {
+ return Ok(type_id_value);
+ }
+ }
+ Err(Error::type_error(format!(
+ "Type index {:?} not found in type_id_index, maybe you forgot to
register some types",
+ index
+ )))
+ }
+
+ /// Fast path for getting user type ID by type index (avoids HashMap
lookup by TypeId)
+ #[inline(always)]
+ pub fn get_user_type_id_by_index(
+ &self,
+ type_id: &std::any::TypeId,
+ id: u32,
+ ) -> Result<u32, Error> {
+ let id_usize = id as usize;
+ if id_usize < self.user_type_id_index.len() {
+ let user_type_id = self.user_type_id_index[id_usize];
+ if user_type_id != NO_USER_TYPE_ID {
+ return Ok(user_type_id);
+ }
+ }
+ Err(Error::type_error(format!(
+ "TypeId {:?} not found in user_type_id_index, maybe you forgot to
register some types",
+ type_id
+ )))
+ }
+
+ /// Fast path for getting TypeMeta by type index (avoids HashMap lookup by
TypeId)
+ #[inline(always)]
+ pub fn get_type_meta_by_index(
+ &self,
+ type_id: &std::any::TypeId,
+ index: u32,
+ ) -> Result<Rc<crate::meta::TypeMeta>, Error> {
+ let id_usize = index as usize;
+ if id_usize < self.type_meta_by_index.len() {
+ if let Some(meta) = &self.type_meta_by_index[id_usize] {
+ return Ok(meta.clone());
+ }
+ }
+ Err(Error::type_error(format!(
+ "TypeId {:?} not found in type_meta_by_index, maybe you forgot to
register some types",
+ type_id
+ )))
+ }
+
+ /// Fast path for getting TypeMeta by type index without cloning Rc.
+ #[inline(always)]
+ pub fn get_type_meta_by_index_ref(
+ &self,
+ type_id: &std::any::TypeId,
+ index: u32,
+ ) -> Result<&crate::meta::TypeMeta, Error> {
+ let id_usize = index as usize;
+ if id_usize < self.type_meta_by_index.len() {
+ if let Some(meta) = &self.type_meta_by_index[id_usize] {
+ return Ok(meta.as_ref());
+ }
+ }
+ Err(Error::type_error(format!(
+ "TypeId {:?} not found in type_meta_by_index, maybe you forgot to
register some types",
+ type_id
+ )))
+ }
+
#[inline(always)]
pub fn get_harness(&self, id: u32) -> Option<Rc<Harness>> {
self.get_type_info_by_id(id)
@@ -856,10 +944,12 @@ impl TypeResolver {
)));
}
- // Update type_id_index for fast lookup
+ // Update type_id_index/user_type_id_index for fast lookup
let index = T::fory_type_index() as usize;
if index >= self.type_id_index.len() {
self.type_id_index.resize(index + 1, NO_TYPE_ID);
+ self.user_type_id_index.resize(index + 1, NO_USER_TYPE_ID);
+ self.rust_type_id_by_index.resize(index + 1, None);
} else if self.type_id_index[index] != NO_TYPE_ID {
return Err(Error::type_error(format!(
"Type index {:?} already registered",
@@ -867,6 +957,8 @@ impl TypeResolver {
)));
}
self.type_id_index[index] = type_info.type_id;
+ self.user_type_id_index[index] = type_info.user_type_id;
+ self.rust_type_id_by_index[index] = Some(rs_type_id);
// Insert partial type info into id maps
if crate::types::is_internal_type(actual_type_id) {
@@ -1147,6 +1239,8 @@ impl TypeResolver {
let mut type_info_map_by_name = self.type_info_map_by_name.clone();
let mut type_info_map_by_meta_string_name =
self.type_info_map_by_meta_string_name.clone();
let type_id_index = self.type_id_index.clone();
+ let rust_type_id_by_index = self.rust_type_id_by_index.clone();
+ let user_type_id_index = self.user_type_id_index.clone();
// Iterate over partial_type_infos and complete them
for (_rust_type_id, partial_type_info) in
self.partial_type_infos.iter() {
@@ -1181,6 +1275,13 @@ impl TypeResolver {
}
}
+ let type_meta_by_index: Vec<Option<Rc<crate::meta::TypeMeta>>> =
rust_type_id_by_index
+ .iter()
+ .map(|id| {
+ id.and_then(|rust_id| type_info_map.get(&rust_id).map(|info|
info.get_type_meta()))
+ })
+ .collect();
+
Ok(TypeResolver {
internal_type_info_by_id,
user_type_info_by_id,
@@ -1189,6 +1290,9 @@ impl TypeResolver {
type_info_map_by_meta_string_name,
partial_type_infos: HashMap::new(),
type_id_index,
+ user_type_id_index,
+ rust_type_id_by_index,
+ type_meta_by_index,
compatible: self.compatible,
xlang: self.xlang,
})
@@ -1278,6 +1382,9 @@ impl TypeResolver {
type_info_map_by_meta_string_name,
partial_type_infos: HashMap::new(),
type_id_index: self.type_id_index.clone(),
+ user_type_id_index: self.user_type_id_index.clone(),
+ rust_type_id_by_index: self.rust_type_id_by_index.clone(),
+ type_meta_by_index: self.type_meta_by_index.clone(),
compatible: self.compatible,
xlang: self.xlang,
}
diff --git a/rust/fory-core/src/serializer/collection.rs
b/rust/fory-core/src/serializer/collection.rs
index 71985a977..68a6dc6a4 100644
--- a/rust/fory-core/src/serializer/collection.rs
+++ b/rust/fory-core/src/serializer/collection.rs
@@ -262,6 +262,97 @@ where
}
}
+#[inline(always)]
+pub fn read_vec_data<T>(context: &mut ReadContext) -> Result<Vec<T>, Error>
+where
+ T: Serializer + ForyDefault,
+{
+ let len = context.reader.read_varuint32()?;
+ if len == 0 {
+ return Ok(Vec::new());
+ }
+ if T::fory_is_polymorphic() || T::fory_is_shared_ref() {
+ return read_vec_data_dyn_ref(context, len);
+ }
+ let header = context.reader.read_u8()?;
+ let declared = (header & DECL_ELEMENT_TYPE) != 0;
+ if !declared {
+ T::fory_read_type_info(context)?;
+ }
+ let has_null = (header & HAS_NULL) != 0;
+ ensure!(
+ (header & IS_SAME_TYPE) != 0,
+ Error::type_error("Type inconsistent, target type is not polymorphic")
+ );
+ let mut vec = Vec::with_capacity(len as usize);
+ if !has_null {
+ for _ in 0..len {
+ vec.push(T::fory_read_data(context)?);
+ }
+ } else {
+ for _ in 0..len {
+ let flag = context.reader.read_i8()?;
+ if flag == RefFlag::Null as i8 {
+ vec.push(T::fory_default());
+ } else {
+ vec.push(T::fory_read_data(context)?);
+ }
+ }
+ }
+ Ok(vec)
+}
+
+#[inline(always)]
+fn read_vec_data_dyn_ref<T>(context: &mut ReadContext, len: u32) ->
Result<Vec<T>, Error>
+where
+ T: Serializer + ForyDefault,
+{
+ let header = context.reader.read_u8()?;
+ let is_track_ref = (header & TRACKING_REF) != 0;
+ let is_same_type = (header & IS_SAME_TYPE) != 0;
+ let has_null = (header & HAS_NULL) != 0;
+ let is_declared = (header & DECL_ELEMENT_TYPE) != 0;
+
+ let elem_ref_mode = if is_track_ref {
+ RefMode::Tracking
+ } else if has_null {
+ RefMode::NullOnly
+ } else {
+ RefMode::None
+ };
+
+ let mut vec = Vec::with_capacity(len as usize);
+ if is_same_type {
+ let type_info = if !is_declared {
+ context.read_any_type_info()?
+ } else {
+ T::fory_get_type_info(context.get_type_resolver())?
+ };
+ if elem_ref_mode == RefMode::None {
+ for _ in 0..len {
+ vec.push(T::fory_read_with_type_info(
+ context,
+ RefMode::None,
+ type_info.clone(),
+ )?);
+ }
+ } else {
+ for _ in 0..len {
+ vec.push(T::fory_read_with_type_info(
+ context,
+ elem_ref_mode,
+ type_info.clone(),
+ )?);
+ }
+ }
+ } else {
+ for _ in 0..len {
+ vec.push(T::fory_read(context, elem_ref_mode, true)?);
+ }
+ }
+ Ok(vec)
+}
+
/// Slow but versatile collection deserialization for dynamic trait object and
shared/circular reference.
pub fn read_collection_data_dyn_ref<C, T>(context: &mut ReadContext, len: u32)
-> Result<C, Error>
where
diff --git a/rust/fory-core/src/serializer/list.rs
b/rust/fory-core/src/serializer/list.rs
index 725471157..54fad4f61 100644
--- a/rust/fory-core/src/serializer/list.rs
+++ b/rust/fory-core/src/serializer/list.rs
@@ -26,7 +26,7 @@ use std::collections::{LinkedList, VecDeque};
use std::mem;
use super::collection::{
- read_collection_data, read_collection_type_info, write_collection_data,
+ read_collection_data, read_collection_type_info, read_vec_data,
write_collection_data,
write_collection_type_info,
};
@@ -126,7 +126,7 @@ impl<T: Serializer + ForyDefault> Serializer for Vec<T> {
if is_primitive_type::<T>() {
primitive_list::fory_read_data(context)
} else {
- read_collection_data(context)
+ read_vec_data(context)
}
}
diff --git a/rust/fory-core/src/serializer/struct_.rs
b/rust/fory-core/src/serializer/struct_.rs
index 51dd6a0f0..aecc8711b 100644
--- a/rust/fory-core/src/serializer/struct_.rs
+++ b/rust/fory-core/src/serializer/struct_.rs
@@ -15,9 +15,10 @@
// specific language governing permissions and limitations
// under the License.
+use crate::ensure;
use crate::error::Error;
use crate::resolver::context::{ReadContext, WriteContext};
-use crate::serializer::Serializer;
+use crate::serializer::{Serializer, StructSerializer};
use crate::types::{RefFlag, RefMode, TypeId};
use crate::util::ENABLE_FORY_DEBUG_OUTPUT;
use std::any::Any;
@@ -51,6 +52,36 @@ pub fn read_type_info<T: Serializer>(context: &mut
ReadContext) -> Result<(), Er
Ok(())
}
+#[inline(always)]
+pub fn read_type_info_fast<T: StructSerializer>(context: &mut ReadContext) ->
Result<(), Error> {
+ if context.is_compatible() || context.is_xlang() {
+ return read_type_info::<T>(context);
+ }
+ let local_type_id = context
+ .get_type_resolver()
+ .get_type_id_by_index(T::fory_type_index())?;
+ let local_type_id_u32 = local_type_id as u32;
+ if !crate::types::needs_user_type_id(local_type_id_u32) {
+ return read_type_info::<T>(context);
+ }
+ let remote_type_id = context.reader.read_u8()? as u32;
+ ensure!(
+ local_type_id_u32 == remote_type_id,
+ Error::type_mismatch(local_type_id_u32, remote_type_id)
+ );
+ let remote_user_type_id = context.reader.read_varuint32()?;
+ let local_user_type_id = context
+ .get_type_resolver()
+ .get_user_type_id_by_index(&std::any::TypeId::of::<T>(),
T::fory_type_index())?;
+ if remote_user_type_id != local_user_type_id {
+ return Err(Error::type_error(format!(
+ "User type id mismatch: local {} vs remote {}",
+ local_user_type_id, remote_user_type_id
+ )));
+ }
+ Ok(())
+}
+
#[inline(always)]
pub fn write<T: Serializer>(
this: &T,
diff --git a/rust/fory-derive/src/object/read.rs
b/rust/fory-derive/src/object/read.rs
index ba1e7aa44..4f931bd49 100644
--- a/rust/fory-derive/src/object/read.rs
+++ b/rust/fory-derive/src/object/read.rs
@@ -441,7 +441,7 @@ pub fn gen_read_field(field: &Field, private_ident: &Ident,
field_name: &str) ->
pub fn gen_read_type_info() -> TokenStream {
quote! {
- fory_core::serializer::struct_::read_type_info::<Self>(context)
+ fory_core::serializer::struct_::read_type_info_fast::<Self>(context)
}
}
@@ -1068,23 +1068,36 @@ pub(crate) fn gen_read_compatible_with_construction(
quote! {}
};
+ let fields_binding = if variant_ident.is_some() {
+ quote! {
+ let mut fields = remote_meta.get_field_infos().clone();
+ #variant_field_remap
+ }
+ } else {
+ quote! {
+ let fields = remote_meta.get_field_infos();
+ }
+ };
+
quote! {
- let mut fields = type_info.get_type_meta().get_field_infos().clone();
- #variant_field_remap
- #(#declare_ts)*
- let meta =
context.get_type_info(&std::any::TypeId::of::<Self>())?.get_type_meta();
+ let meta = context.get_type_resolver().get_type_meta_by_index_ref(
+ &std::any::TypeId::of::<Self>(),
+ <Self as fory_core::StructSerializer>::fory_type_index(),
+ )?;
let local_type_hash = meta.get_hash();
- let remote_type_hash = type_info.get_type_meta().get_hash();
+ let remote_meta = type_info.get_type_meta_ref();
+ let remote_type_hash = remote_meta.get_hash();
if remote_type_hash == local_type_hash {
- <Self as fory_core::Serializer>::fory_read_data(context)
- } else {
- for _field in fields.iter() {
- match _field.field_id {
- #(#match_arms)*
- #skip_arm
- }
+ return <Self as fory_core::Serializer>::fory_read_data(context);
+ }
+ #fields_binding
+ #(#declare_ts)*
+ for _field in fields.iter() {
+ match _field.field_id {
+ #(#match_arms)*
+ #skip_arm
}
- #construction
}
+ #construction
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]