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 ef874a0ee perf(rust): optimize rust deserialize perf (#2584)
ef874a0ee is described below
commit ef874a0ee303fc5c26610711a25a5bc7008c48b5
Author: Shawn Yang <[email protected]>
AuthorDate: Mon Sep 8 11:27:37 2025 +0800
perf(rust): optimize rust deserialize perf (#2584)
<!--
**Thanks for contributing to Fory.**
**If this is your first time opening a PR on fory, you can refer to
[CONTRIBUTING.md](https://github.com/apache/fory/blob/main/CONTRIBUTING.md).**
Contribution Checklist
- The **Apache Fory** community has requirements on the naming of pr
titles. You can also find instructions in
[CONTRIBUTING.md](https://github.com/apache/fory/blob/main/CONTRIBUTING.md).
- Fory has a strong focus on performance. If the PR you submit will have
an impact on performance, please benchmark it first and provide the
benchmark result here.
-->
## Why?
## What does this PR do?
Reduce type id hash cost for deserialization.
## Related issues
<!--
Is there any related issue? If this PR closes them you say say
fix/closes:
- #xxxx0
- #xxxx1
- Fixes #xxxx2
-->
## Does this PR introduce any user-facing change?
<!--
If any user-facing interface changes, please [open an
issue](https://github.com/apache/fory/issues/new/choose) describing the
need to do so and update the document if necessary.
Delete section if not applicable.
-->
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
Before optimization:
<img width="3464" height="706" alt="image"
src="https://github.com/user-attachments/assets/402e7fa2-202a-46e7-bfe3-9c2d8b1be2c8"
/>
After optimization:
<img width="3458" height="670" alt="image"
src="https://github.com/user-attachments/assets/2e348d29-2763-4483-9c35-205a75bd74ca"
/>
---
rust/benches/Cargo.toml | 4 ++
rust/benches/README.md | 5 +++
rust/benches/src/main.rs | 65 ++++++++++++++++++++++++++++
rust/fory-core/src/resolver/type_resolver.rs | 24 ++++++++++
rust/fory-core/src/serializer/mod.rs | 2 +
rust/fory-derive/src/object/misc.rs | 21 ++++++++-
rust/fory-derive/src/object/serializer.rs | 6 ++-
7 files changed, 124 insertions(+), 3 deletions(-)
diff --git a/rust/benches/Cargo.toml b/rust/benches/Cargo.toml
index 413415071..a4762ad74 100644
--- a/rust/benches/Cargo.toml
+++ b/rust/benches/Cargo.toml
@@ -20,6 +20,10 @@ name = "fory-benchmarks"
version = "0.1.0"
edition = "2021"
+[[bin]]
+name = "fury_profiler"
+path = "src/main.rs"
+
[[bench]]
name = "serialization_bench"
path = "benches/serialization_bench.rs"
diff --git a/rust/benches/README.md b/rust/benches/README.md
new file mode 100644
index 000000000..909cc5064
--- /dev/null
+++ b/rust/benches/README.md
@@ -0,0 +1,5 @@
+## How to generate flamegraph
+
+```bash
+cargo flamegraph --bin fury_profiler
+```
diff --git a/rust/benches/src/main.rs b/rust/benches/src/main.rs
new file mode 100644
index 000000000..2c1a104c6
--- /dev/null
+++ b/rust/benches/src/main.rs
@@ -0,0 +1,65 @@
+// 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.
+
+use fory_benchmarks::models::simple::SimpleStruct;
+use fory_benchmarks::models::TestDataGenerator;
+use fory_benchmarks::serializers::fury::FurySerializer;
+use fory_benchmarks::serializers::Serializer;
+use std::hint::black_box;
+
+fn main() {
+ println!("Starting Fury deserialization profiling...");
+
+ // Initialize the Fury serializer
+ let fury_serializer = FurySerializer::new();
+
+ // Generate test data
+ let simple_struct = SimpleStruct::generate_small();
+ println!("Generated test struct: {:?}", simple_struct);
+
+ // Pre-serialize the data once
+ let serialized_data = fury_serializer.serialize(&simple_struct).unwrap();
+ println!("Serialized data size: {} bytes", serialized_data.len());
+
+ // Warm up - run a few iterations to get the code in cache
+ for _ in 0..1000 {
+ let _: SimpleStruct = black_box(
+ fury_serializer
+ .deserialize(black_box(&serialized_data))
+ .unwrap(),
+ );
+ }
+
+ println!("Warmup complete. Starting main profiling loop...");
+
+ // Main profiling loop - run many iterations to get good profiling data
+ const ITERATIONS: usize = 10_000_000;
+ for i in 0..ITERATIONS {
+ let _: SimpleStruct = black_box(
+ fury_serializer
+ .deserialize(black_box(&serialized_data))
+ .unwrap(),
+ );
+
+ // Print progress every million iterations
+ if i % 1_000_000 == 0 && i > 0 {
+ println!("Completed {} iterations", i);
+ }
+ }
+
+ println!("Profiling complete! Ran {} iterations", ITERATIONS);
+}
diff --git a/rust/fory-core/src/resolver/type_resolver.rs
b/rust/fory-core/src/resolver/type_resolver.rs
index a4cc0bb49..14cce6734 100644
--- a/rust/fory-core/src/resolver/type_resolver.rs
+++ b/rust/fory-core/src/resolver/type_resolver.rs
@@ -73,8 +73,12 @@ pub struct TypeResolver {
serialize_map: HashMap<u32, Harness>,
type_id_map: HashMap<std::any::TypeId, u32>,
type_info_map: HashMap<std::any::TypeId, TypeInfo>,
+ // Fast lookup by numeric ID for common types
+ type_id_index: Vec<u32>,
}
+const NO_TYPE_ID: u32 = 1000000000;
+
impl TypeResolver {
pub fn get_type_info(&self, type_id: std::any::TypeId) -> &TypeInfo {
self.type_info_map.get(&type_id).unwrap_or_else(|| {
@@ -85,6 +89,21 @@ impl TypeResolver {
})
}
+ /// Fast path for getting type info by numeric ID (avoids HashMap lookup
by TypeId)
+ pub fn get_type_id(&self, type_id: &std::any::TypeId, id: u32) -> u32 {
+ let id_usize = id as usize;
+ if id_usize < self.type_id_index.len() {
+ let type_id = self.type_id_index[id_usize];
+ if type_id != NO_TYPE_ID {
+ return type_id;
+ }
+ }
+ panic!(
+ "TypeId {:?} not found in type_id_index, maybe you forgot to
register some types",
+ type_id
+ )
+ }
+
pub fn register<T: StructSerializer>(&mut self, type_info: TypeInfo, id:
u32) {
fn serializer<T2: 'static + StructSerializer>(this: &dyn Any, context:
&mut WriteContext) {
let this = this.downcast_ref::<T2>();
@@ -104,6 +123,11 @@ impl TypeResolver {
Err(e) => Err(e),
}
}
+ let index = T::type_index() as usize;
+ if index >= self.type_id_index.len() {
+ self.type_id_index.resize(index + 1, NO_TYPE_ID);
+ }
+ self.type_id_index[index] = id;
self.type_id_map.insert(std::any::TypeId::of::<T>(), id);
self.serialize_map
.insert(id, Harness::new(serializer::<T>, deserializer::<T>));
diff --git a/rust/fory-core/src/serializer/mod.rs
b/rust/fory-core/src/serializer/mod.rs
index bd07735db..dc7570460 100644
--- a/rust/fory-core/src/serializer/mod.rs
+++ b/rust/fory-core/src/serializer/mod.rs
@@ -101,4 +101,6 @@ pub trait StructSerializer: Serializer + 'static {
fn actual_type_id(type_id: u32) -> u32 {
(type_id << 8) + TypeId::STRUCT as u32
}
+
+ fn type_index() -> u32;
}
diff --git a/rust/fory-derive/src/object/misc.rs
b/rust/fory-derive/src/object/misc.rs
index 7840a801a..4df03d5bb 100644
--- a/rust/fory-derive/src/object/misc.rs
+++ b/rust/fory-derive/src/object/misc.rs
@@ -17,10 +17,19 @@
use proc_macro2::TokenStream;
use quote::quote;
+use std::sync::atomic::{AtomicU32, Ordering};
use syn::Field;
use super::util::{generic_tree_to_tokens, parse_generic_tree};
+// Global type ID counter that auto-grows from 0 at macro processing time
+static TYPE_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
+
+/// Allocates a new unique type ID at macro processing time
+pub fn allocate_type_id() -> u32 {
+ TYPE_ID_COUNTER.fetch_add(1, Ordering::SeqCst)
+}
+
fn hash(fields: &[&Field]) -> TokenStream {
let props = fields.iter().map(|field| {
let ty = &field.ty;
@@ -74,10 +83,18 @@ pub fn gen_in_struct_impl(fields: &[&Field]) -> TokenStream
{
}
}
-pub fn gen() -> TokenStream {
+pub fn gen(type_id: u32) -> TokenStream {
quote! {
fn get_type_id(fory: &fory_core::fory::Fory) -> u32 {
-
fory.get_type_resolver().get_type_info(std::any::TypeId::of::<Self>()).get_type_id()
+
fory.get_type_resolver().get_type_id(&std::any::TypeId::of::<Self>(), #type_id)
+ }
+ }
+}
+
+pub fn gen_type_index(type_id: u32) -> TokenStream {
+ quote! {
+ fn type_index() -> u32 {
+ #type_id
}
}
}
diff --git a/rust/fory-derive/src/object/serializer.rs
b/rust/fory-derive/src/object/serializer.rs
index cf5195dbe..21ad2e054 100644
--- a/rust/fory-derive/src/object/serializer.rs
+++ b/rust/fory-derive/src/object/serializer.rs
@@ -51,11 +51,15 @@ pub fn derive_serializer(ast: &syn::DeriveInput) ->
TokenStream {
}
};
- let misc_token_stream = misc::gen();
+ // Allocate a unique type ID once and share it between both functions
+ let type_id = misc::allocate_type_id();
+ let misc_token_stream = misc::gen(type_id);
+ let type_index_token_stream = misc::gen_type_index(type_id);
let gen = quote! {
impl fory_core::serializer::StructSerializer for #name {
#type_def_token_stream
+ #type_index_token_stream
}
impl fory_core::types::ForyGeneralList for #name {}
impl fory_core::serializer::Serializer for #name {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]