Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(ast): shrink large struct sizes #7019

Closed
Boshen opened this issue Mar 6, 2023 · 8 comments · Fixed by #7041
Closed

perf(ast): shrink large struct sizes #7019

Boshen opened this issue Mar 6, 2023 · 8 comments · Fixed by #7041
Milestone

Comments

@Boshen
Copy link
Contributor

Boshen commented Mar 6, 2023

Background

https://nnethercote.github.io/perf-book/type-sizes.html

Shrinking oft-instantiated types can help performance.

Problem

I have gathered a list of large structs (> 64 bytes) inside swc_ecma_ast, by using RUSTFLAGS=-Zprint-type-sizes with the help of top-type-sizes:

Top Type Sizes
216 jsx::JSXElement align=8
  104 opening
   72 closing
   24 children
   12 span

208 stmt::TryStmt align=8
  112 handler
   40 block
   40 finalizer
   12 span

160 alloc::sync::ArcInner<std::thread::Inner> align=8
  144 data
    8 strong
    8 weak

144 std::thread::Inner align=8
  120 parker
   16 name
    8 id

136 jsx::JSXAttrOrSpread align=8
  136 variant JSXAttr
   24 variant SpreadElement

136 jsx::JSXAttr align=8
   72 value
   48 name
   12 span

120 std::sys::unix::thread_parker::pthread::Parker align=8
   64 lock
   48 cvar
    8 state
    0 _pinned

120 function::Function align=8
   40 body
   24 params
   24 decorators
   12 span
    8 type_params
    8 return_type
    1 is_generator
    1 is_async

120 class::Constructor align=8
   40 key
   40 body
   24 params
   12 span
    1 accessibility
    1 is_optional

120 class::ClassMember align=8
  120 variant Constructor
  112 variant ClassProp, PrivateProp
   80 variant Method, PrivateMethod
   64 variant StaticBlock
   56 variant TsIndexSignature
   20 variant Empty

112 typescript::TsType align=8
    4 <discriminant>
  108 variant TsTypeQuery
   92 variant TsMappedType, TsLitType
   84 variant TsImportType
   76 variant TsInferType
   68 variant TsFnOrConstructorType
   52 variant TsTypeRef, TsUnionOrIntersectionType, TsConditionalType, TsTypePredicate
   44 variant TsTypeLit, TsTupleType
   36 variant TsIndexedAccessType
   28 variant TsArrayType, TsOptionalType, TsRestType, TsParenthesizedType, TsTypeOperator
   16 variant TsKeywordType
   12 variant TsThisType

112 typescript::TsInterfaceDecl align=8
   40 body
   24 id
   24 extends
   12 span
    8 type_params
    1 declare

112 stmt::CatchClause align=8
   56 param
   40 body
   12 span

112 prop::Prop align=8
    8 <discriminant>
  104 variant Getter, Setter
   48 variant KeyValue, Method
   32 variant Assign
   24 variant Shorthand

112 class::Class align=8
   24 decorators
   24 body
   24 implements
   12 span
    8 super_class
    8 type_params
    8 super_type_params
    1 is_abstract

104 typescript::TsTypeQuery align=8
   80 expr_name
   12 span
    8 type_args

104 typescript::TsModuleDecl align=8
   56 body
   32 id
   12 span
    1 declare
    1 global

104 prop::SetterProp align=8
   40 key
   40 body
   12 span
    8 param

104 prop::GetterProp align=8
   40 key
   40 body
   12 span
    8 type_ann

104 jsx::JSXOpeningElement align=8
   56 name
   24 attrs
   12 span
    8 type_args
    1 self_closing

104 function::ParamOrTsParamProp align=8
    8 <discriminant>
   96 variant Param
   80 variant TsParamProp

104 expr::Expr align=8
    4 <discriminant>
  100 variant TaggedTpl, Arrow, OptChain
   76 variant Member
   68 variant Call, Tpl, JSXFragment
   60 variant SuperProp, New
   52 variant JSXMember, JSXNamespacedName
   44 variant Array, Object, Assign, Cond, Seq, Lit, PrivateName
   36 variant Fn, Bin, Class, TsTypeAssertion, TsAs, TsInstantiation, TsSatisfies
   28 variant Unary, Update, Ident, Yield, Await, Paren, TsConstAssertion, TsNonNull
   16 variant MetaProp
   12 variant This, JSXEmpty, Invalid
   12 variant JSXElement

104 class::PrivateProp align=8
   40 key
   24 decorators
   12 span
    8 value
    8 type_ann
    1 is_static
    1 accessibility
    1 is_optional
    1 is_override
    1 readonly
    1 definite

104 class::ClassProp align=8
   40 key
   24 decorators
   12 span
    8 value
    8 type_ann
    1 is_static
    1 accessibility
    1 is_abstract
    1 is_optional
    1 is_override
    1 readonly
    1 declare
    1 definite

96 std::cell::RefCell<swc_common::source_map::SourceMapFiles> align=8
   88 value
    8 borrow

96 function::Param align=8
   56 pat
   24 decorators
   12 span

96 expr::TaggedTpl align=8
   64 tpl
   12 span
    8 tag
    8 type_params

96 expr::OptChainExpr align=8
   72 base
   12 span
   12 question_dot_token

96 expr::ArrowExpr align=8
   40 body
   24 params
   12 span
    8 type_params
    8 return_type
    1 is_async
    1 is_generator

88 url::Url align=8
   24 serialization
   17 host
    8 query_start
    8 fragment_start
    4 scheme_end
    4 username_end
    4 host_start
    4 host_end
    4 path_start
    4 port

88 typescript::TsTypeElement align=8
    8 <discriminant>
   80 variant TsSetterSignature
   72 variant TsPropertySignature
   64 variant TsMethodSignature
   56 variant TsCallSignatureDecl, TsConstructSignatureDecl
   48 variant TsIndexSignature
   32 variant TsGetterSignature

88 typescript::TsMappedType align=8
   56 type_param
   12 span
    8 name_type
    8 type_ann
    1 readonly
    1 optional

88 typescript::TsLitType align=8
   72 lit
   12 span

88 typescript::TsImportEqualsDecl align=8
   48 module_ref
   24 id
   12 span
    1 declare
    1 is_export
    1 is_type_only

88 swc_common::source_map::SourceMapFiles align=8
   64 stable_id_to_source_file
   24 source_files

88 swc_common::FileName align=8
   88 variant Url
   24 variant Real, Macros, Internal, Custom
    0 variant QuoteExpansion, Anon, MacroExpansion, ProcMacroSourceCode

88 module_decl::ExportSpecifier align=8
   88 variant Namespace
   88 variant Named
   24 variant Default

88 module_decl::ExportNamedSpecifier align=8
   40 exported
   32 orig
   12 span
    1 is_type_only

80 typescript::TsTypeQueryExpr align=8
   80 variant Import
   24 variant TsEntityName

80 typescript::TsTupleElement align=8
   56 label
   12 span
    8 ty

80 typescript::TsSetterSignature align=8
   56 param
   12 span
    8 key
    1 readonly
    1 computed
    1 optional

80 typescript::TsParamProp align=8
   40 param
   24 decorators
   12 span
    1 accessibility
    1 is_override
    1 readonly

80 typescript::TsImportType align=8
   32 arg
   24 qualifier
   12 span
    8 type_args

80 module_decl::ImportSpecifier align=8
   80 variant Named
   72 variant Default, Namespace

80 module_decl::ImportNamedSpecifier align=8
   40 imported
   24 local
   12 span
    1 is_type_only

80 decl::VarDeclarator align=8
   56 name
   12 span
    8 init
    1 definite

72 typescript::TsPropertySignature align=8
   24 params
   12 span
    8 key
    8 init
    8 type_ann
    8 type_params
    1 readonly
    1 computed
    1 optional

72 typescript::TsLit align=8
    4 <discriminant>
   68 variant Tpl
   36 variant Number, Str, BigInt
   16 variant Bool

72 typescript::TsInferType align=8
   56 type_param
   12 span

72 module::ModuleItem align=8
    8 <discriminant>
   64 variant ModuleDecl, Stmt

72 jsx::JSXElementChild align=8
    8 <discriminant>
   64 variant JSXFragment
   32 variant JSXText, JSXExprContainer
   24 variant JSXSpreadChild
    8 variant JSXElement

72 jsx::JSXClosingElement align=8
   56 name
   12 span

72 jsx::JSXAttrValue align=8
    8 <discriminant>
   64 variant JSXFragment
   40 variant Lit
   32 variant JSXExprContainer
    8 variant JSXElement

72 expr::OptChainBase align=8
   72 variant Member
   72 variant Call

72 expr::MemberExpr align=8
   48 prop
   12 span
    8 obj

72 class::PrivateMethod align=8
   40 key
   12 span
    8 function
    1 kind
    1 is_static
    1 accessibility
    1 is_abstract
    1 is_optional
    1 is_override

72 class::ClassMethod align=8
   40 key
   12 span
    8 function
    1 kind
    1 is_static
    1 accessibility
    1 is_abstract
    1 is_optional
    1 is_override

And from my own memory profiling, at least Expr (104 bytes) should be reduced as much as possible, it has the most impact on the memory allocator.

Stmt needs some shrinking too because there are places where the whole array is cloned, which made the allocator to allocate for MBs of memory:

let mut new_stmts = Vec::with_capacity(stmts.len());

Solution

The best approach is to box all enum variants for all enum types, and the second best approach is to box at least some of the larger struct fields. for example Expr -> TaggedTpl.tpl, Arrow.body.

Changing the AST will break all downstream dependencies, so a strategy need to be selected.


Note: Changing the AST is a definite breaking change for WASM plugins, and needs to be documented in https://swc.rs/docs/plugin/selecting-swc-core


Related discussion: #6991

@kdy1 kdy1 added this to the Planned milestone Mar 6, 2023
@kdy1
Copy link
Member

kdy1 commented Mar 6, 2023

Btw, I'm not sure if additional boxing is a breaking change for Wasm plugins. I need to look at rkyv source code.

@kdy1
Copy link
Member

kdy1 commented Mar 6, 2023

@djkoloski Sorry for the ping, but I can't check it using the source code.

Does boxing a field change the memory layout?

@djkoloski
Copy link

Yes, that will change rkyv's serialized format. You can fix this with a wrapper type that restores the original layout:

use rkyv::{
    with::{ArchiveWith, DeserializeWith, SerializeWith},
    Archive, Deserialize, Fallible, Serialize,
};

pub struct InlineBox;

impl<F: Archive> ArchiveWith<Box<F>> for InlineBox {
    type Archived = F::Archived;
    type Resolver = F::Resolver;

    #[inline]
    unsafe fn resolve_with(
        field: &Box<F>,
        pos: usize,
        resolver: Self::Resolver,
        out: *mut Self::Archived,
    ) {
        (**field).resolve(pos, resolver, out);
    }
}

impl<F, S> SerializeWith<Box<F>, S> for InlineBox
where
    F: Serialize<S>,
    S: Fallible + ?Sized,
{
    #[inline]
    fn serialize_with(
        field: &Box<F>,
        serializer: &mut S,
    ) -> Result<Self::Resolver, S::Error> {
        (**field).serialize(serializer)
    }
}

impl<F, D> DeserializeWith<F::Archived, Box<F>, D> for InlineBox
where
    F: Archive,
    D: Fallible + ?Sized,
    F::Archived: Deserialize<F, D>,
{
    #[inline]
    fn deserialize_with(
        field: &F::Archived,
        deserializer: &mut D,
    ) -> Result<Box<F>, D::Error> {
        Ok(Box::new(field.deserialize(deserializer)?))
    }
}

You can use wrapper types to customize how fields are archived without affecting the original type:

// Before
#[derive(Archive, Deserialize, Serialize)]
struct OldFoo {
    big_array: [u32; 100],
}

// After
#[derive(Archive, Deserialize, Serialize)]
struct NewFoo {
    #[with(InlineBox)]
    big_array: Box<[u32; 100]>,
}

fn main() {
    use core::mem::size_of;
    use rkyv::Archived;

    println!("OldFoo: {}", size_of::<OldFoo>());
    println!("archived OldFoo: {}", size_of::<Archived<OldFoo>>());
    println!("NewFoo: {}", size_of::<NewFoo>());
    println!("archived NewFoo: {}", size_of::<Archived<NewFoo>>());
}
OldFoo: 400
archived OldFoo: 400
NewFoo: 8
archived NewFoo: 400

@kdy1
Copy link
Member

kdy1 commented Mar 7, 2023

So we can box fields without breaking Wasm plugins. The only problem is that it's quite time-consuming...

@Boshen
Copy link
Contributor Author

Boshen commented Mar 7, 2023

@kdy1 If you can box Expr and Stmt in the AST with the serialization stuff from above, I'll finish the rest (slowly).

@Boshen
Copy link
Contributor Author

Boshen commented Mar 9, 2023

By looking at the great effort you put into the two PRs, I suggest to only box some of the larger fields and make Stmt and Expr slightly smaller, as I wrote in the solution section. It's just way too much work for this if you box everything.

@kdy1
Copy link
Member

kdy1 commented Mar 9, 2023

I optimized Expr. For Stmt, I checked the size but it was 64 bytes (typical cache line size), so i decided to leave it as-is.

@kdy1 kdy1 closed this as completed in #7041 Mar 9, 2023
kdy1 added a commit that referenced this issue Mar 9, 2023
**Related issue:**

 - Closes #7019.
@kdy1 kdy1 modified the milestones: Planned, v1.3.39 Mar 10, 2023
@swc-bot
Copy link
Collaborator

swc-bot commented Apr 9, 2023

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@swc-project swc-project locked as resolved and limited conversation to collaborators Apr 9, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
4 participants