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 a38029d3b feat(Rust): Unroll fields loop & Add a feature for this
(#2724)
a38029d3b is described below
commit a38029d3bff2cf3b7c14324534816f9120ef13d6
Author: urlyy <[email protected]>
AuthorDate: Thu Oct 9 17:50:07 2025 +0800
feat(Rust): Unroll fields loop & Add a feature for this (#2724)
## Why?
loop unroll can speed up code cache and give better branch predict.
## What does this PR do?
1. Add loop unroll logic
2. Add a feature for switch, default is enable, you can comment `default
= ["fields-loop-unroll"]` in `fory-derive/Cargo.toml` to disable this.
Disabling this feature helps analyze the expanded code during the Debug
phase, because as the number of struct fields increases, loop unrolling
causes the amount of code generated in
`fory_read_compatible()`/`fory_read_data()`/`fory_write_data` to grow
exponentially.
## Related issues
Fixes #2721
---
rust/fory-core/src/meta/type_meta.rs | 14 ++++-
rust/fory-derive/Cargo.toml | 4 ++
rust/fory-derive/src/object/read.rs | 118 ++++++++++++++++++++++-------------
rust/fory-derive/src/object/write.rs | 29 ++++++++-
4 files changed, 117 insertions(+), 48 deletions(-)
diff --git a/rust/fory-core/src/meta/type_meta.rs
b/rust/fory-core/src/meta/type_meta.rs
index 88581e729..2d0408333 100644
--- a/rust/fory-core/src/meta/type_meta.rs
+++ b/rust/fory-core/src/meta/type_meta.rs
@@ -548,7 +548,8 @@ impl TypeMetaLayer {
#[derive(Debug)]
pub struct TypeMeta {
- // hash: u64,
+ // assigned valid value and used, only during deserializing
+ hash: i64,
layers: Vec<TypeMetaLayer>,
}
@@ -561,6 +562,10 @@ impl TypeMeta {
self.layers.first().unwrap().get_type_id()
}
+ pub fn get_hash(&self) -> i64 {
+ self.hash
+ }
+
pub fn get_type_name(&self) -> MetaString {
self.layers.first().unwrap().get_type_name().clone()
}
@@ -577,7 +582,7 @@ impl TypeMeta {
field_infos: Vec<FieldInfo>,
) -> TypeMeta {
TypeMeta {
- // hash: 0,
+ hash: 0,
layers: vec![TypeMetaLayer::new(
type_id,
namespace,
@@ -604,7 +609,10 @@ impl TypeMeta {
// while current_meta_size < meta_size {}
let layer = TypeMetaLayer::from_bytes(reader);
layers.push(layer);
- TypeMeta { layers }
+ TypeMeta {
+ layers,
+ hash: header,
+ }
}
pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
diff --git a/rust/fory-derive/Cargo.toml b/rust/fory-derive/Cargo.toml
index 361f76f10..79f69d663 100644
--- a/rust/fory-derive/Cargo.toml
+++ b/rust/fory-derive/Cargo.toml
@@ -35,3 +35,7 @@ syn = { default-features = false, version = "2.0", features =
[
] }
quote = { default-features = false, version = "1.0" }
thiserror = { default-features = false, version = "1.0" }
+
+[features]
+default = ["fields-loop-unroll"]
+fields-loop-unroll = []
\ No newline at end of file
diff --git a/rust/fory-derive/src/object/read.rs
b/rust/fory-derive/src/object/read.rs
index cff396ac9..b41e05de0 100644
--- a/rust/fory-derive/src/object/read.rs
+++ b/rust/fory-derive/src/object/read.rs
@@ -211,19 +211,51 @@ pub fn gen_read_type_info() -> TokenStream {
}
}
-pub fn gen_read_data(fields: &[&Field]) -> TokenStream {
- let private_idents: Vec<Ident> = fields
+fn get_fields_loop_ts(fields: &[&Field]) -> TokenStream {
+ let match_ts: Vec<_> = fields
.iter()
- .map(|f| create_private_field_name(f))
+ .map(|field| {
+ let private_ident = create_private_field_name(field);
+ gen_read_match_arm(field, &private_ident)
+ })
.collect();
+ #[cfg(not(feature = "fields-loop-unroll"))]
+ let loop_ts = quote! {
+ for field_name in field_names {
+ match field_name.as_str() {
+ #(#match_ts),*
+ , _ => unreachable!()
+ }
+ }
+ };
+ #[cfg(feature = "fields-loop-unroll")]
+ let loop_ts = {
+ let loop_item_ts = fields.iter().enumerate().map(|(i, _field)| {
+ let idx = syn::Index::from(i);
+ quote! {
+ let field_name = field_names.get(#idx).unwrap();
+ match field_name.as_str() {
+ #(#match_ts),*
+ , _ => { unreachable!() }
+ }
+ }
+ });
+ quote! {
+ #(#loop_item_ts)*
+ }
+ };
+ loop_ts
+}
+
+pub fn gen_read_data(fields: &[&Field]) -> TokenStream {
let sorted_read = if fields.is_empty() {
quote! {}
} else {
let declare_var_ts =
fields
.iter()
- .zip(private_idents.iter())
- .map(|(field, private_ident)| {
+ .map(|field| {
+ let private_ident = create_private_field_name(field);
let ty = &field.ty;
match classify_trait_object_field(ty) {
StructField::BoxDyn(_)
@@ -240,45 +272,35 @@ pub fn gen_read_data(fields: &[&Field]) -> TokenStream {
}
}
});
- let match_ts = fields
- .iter()
- .zip(private_idents.iter())
- .map(|(field, private_ident)| gen_read_match_arm(field,
private_ident));
+ let loop_ts = get_fields_loop_ts(fields);
quote! {
#(#declare_var_ts)*
- let sorted_field_names = <Self as
fory_core::serializer::StructSerializer>::fory_get_sorted_field_names(context.get_fory());
- for field_name in sorted_field_names {
- match field_name.as_str() {
- #(#match_ts),*
- , _ => unreachable!()
- }
- }
+ let field_names = <Self as
fory_core::serializer::StructSerializer>::fory_get_sorted_field_names(context.get_fory());
+ #loop_ts
}
};
- let field_idents = fields
- .iter()
- .zip(private_idents.iter())
- .map(|(field, private_ident)| {
- let original_ident = &field.ident;
- let ty = &field.ty;
- match classify_trait_object_field(ty) {
- StructField::BoxDyn(_) | StructField::RcDyn(_) |
StructField::ArcDyn(_) => {
- quote! {
- #original_ident: #private_ident
- }
+ let field_idents = fields.iter().map(|field| {
+ let private_ident = create_private_field_name(field);
+ let original_ident = &field.ident;
+ let ty = &field.ty;
+ match classify_trait_object_field(ty) {
+ StructField::BoxDyn(_) | StructField::RcDyn(_) |
StructField::ArcDyn(_) => {
+ quote! {
+ #original_ident: #private_ident
}
- StructField::ContainsTraitObject => {
- quote! {
- #original_ident: #private_ident.unwrap()
- }
+ }
+ StructField::ContainsTraitObject => {
+ quote! {
+ #original_ident: #private_ident.unwrap()
}
- _ => {
- quote! {
- #original_ident: #private_ident.unwrap_or_default()
- }
+ }
+ _ => {
+ quote! {
+ #original_ident: #private_ident.unwrap_or_default()
}
}
- });
+ }
+ });
quote! {
#sorted_read
Ok(Self {
@@ -478,6 +500,8 @@ pub fn gen_read_compatible(fields: &[&Field]) ->
TokenStream {
});
let declare_ts: Vec<TokenStream> = declare_var(fields);
let assign_ts: Vec<TokenStream> = assign_value(fields);
+
+ let consistent_fields_loop_ts = get_fields_loop_ts(fields);
quote! {
let remote_type_id = context.reader.read_varuint32();
let meta_index = context.reader.read_varuint32();
@@ -487,12 +511,22 @@ pub fn gen_read_compatible(fields: &[&Field]) ->
TokenStream {
meta.get_field_infos().clone()
};
#(#declare_ts)*
- for _field in fields.iter() {
- #(#pattern_items else)* {
- println!("skip {:?}:{:?}", _field.field_name.as_str(),
_field.field_type);
- let nullable_field_type =
fory_core::meta::NullableFieldType::from(_field.field_type.clone());
- let read_ref_flag =
fory_core::serializer::skip::get_read_ref_flag(&nullable_field_type);
- fory_core::serializer::skip::skip_field_value(context,
&nullable_field_type, read_ref_flag).unwrap();
+
+ let local_type_def =
context.get_fory().get_type_resolver().get_type_info(std::any::TypeId::of::<Self>()).get_type_def();
+ let high_bytes = &local_type_def[..8];
+ let local_type_hash =
i64::from_le_bytes(high_bytes.try_into().unwrap());
+ if meta.get_hash() == local_type_hash {
+ // fast path
+ let field_names = <Self as
fory_core::serializer::StructSerializer>::fory_get_sorted_field_names(context.get_fory());
+ #consistent_fields_loop_ts
+ } else {
+ for _field in fields.iter() {
+ #(#pattern_items else)* {
+ println!("skip {:?}:{:?}", _field.field_name.as_str(),
_field.field_type);
+ let nullable_field_type =
fory_core::meta::NullableFieldType::from(_field.field_type.clone());
+ let read_ref_flag =
fory_core::serializer::skip::get_read_ref_flag(&nullable_field_type);
+ fory_core::serializer::skip::skip_field_value(context,
&nullable_field_type, read_ref_flag).unwrap();
+ }
}
}
Ok(Self {
diff --git a/rust/fory-derive/src/object/write.rs
b/rust/fory-derive/src/object/write.rs
index 9f8f51068..5aa862084 100644
--- a/rust/fory-derive/src/object/write.rs
+++ b/rust/fory-derive/src/object/write.rs
@@ -223,15 +223,38 @@ pub fn gen_write_data(fields: &[&Field]) -> TokenStream {
let sorted_serialize = if fields.is_empty() {
quote! {}
} else {
- let match_ts = fields.iter().map(|field| gen_write_match_arm(field));
- quote! {
- let sorted_field_names = <Self as
fory_core::serializer::StructSerializer>::fory_get_sorted_field_names(context.get_fory());
+ let match_ts: Vec<_> = fields
+ .iter()
+ .map(|field| gen_write_match_arm(field))
+ .collect();
+ #[cfg(not(feature = "fields-loop-unroll"))]
+ let loop_ts = quote! {
for field_name in sorted_field_names {
match field_name.as_str() {
#(#match_ts),*
, _ => {unreachable!()}
}
}
+ };
+ #[cfg(feature = "fields-loop-unroll")]
+ let loop_ts = {
+ let loop_item_ts = fields.iter().enumerate().map(|(i, _field)| {
+ let idx = syn::Index::from(i);
+ quote! {
+ let field_name = sorted_field_names.get(#idx).unwrap();
+ match field_name.as_str() {
+ #(#match_ts),*
+ , _ => { unreachable!() }
+ }
+ }
+ });
+ quote! {
+ #(#loop_item_ts)*
+ }
+ };
+ quote! {
+ let sorted_field_names = <Self as
fory_core::serializer::StructSerializer>::fory_get_sorted_field_names(context.get_fory());
+ #loop_ts
}
};
quote! {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]