Force inlining for deep call chains (with frunk)

⚓ rust    📅 2025-05-18    👤 surdeus    👁️ 6      

surdeus

Warning

This post was published 42 days ago. The information described in this article may have changed.

I have a trait that I want to use with frunk to generate trait impls automatically for a bunch of generated structs (see below, not linking to a playground since this requires quite a bit of unrelated infrastructure).

Functionally, this works fine, but is unfortunately significantly slower (as in 2x) compared to an implementation where I build an array of unsafe fn pointers -- one per field -- that take a *const mut() and decode a T there (as unsafe as can be, but surprisingly faster than unrolled code from a dedicated proc macro).

Flamegraphs show a pyramid of <HCons<T,U> as ParseToGeneric>::parse_to_generic calls (with literal T and U, though there are tons of functions like this in the binary, with different hashes appended) that I hoped #[inline(always)] would flatten into a single function.

The structs have various numbers of fields, from 0 to 30-odd. I'm running benchmarks on one of the larger ones.

This is just about the hottest path I have, so I do care about performance. Is there a way to force the compiler to build one fully inlined function per <T,U>? The function call overhead really hurts here.

trait ParseToGeneric<'a>: Sized {
    fn parse_to_generic(
        params: impl Iterator<Item = Result<&'a [u8], FromBytesError>>,
    ) -> Result<Self, PayloadFromBytesError>;
}

impl<'a, T, U> ParseToGeneric<'a> for HCons<T, U>
where
    T: FromBytes<'a>,
    U: ParseToGeneric<'a>,
{
    #[inline(always)]
    fn parse_to_generic(
        mut params: impl Iterator<Item = Result<&'a [u8], FromBytesError>>,
    ) -> Result<Self, PayloadFromBytesError> {
        let val = T::from_bytes(todo!())?;

        Ok(HCons {
            head: val,
            tail: U::parse_to_generic(params)?,
        })
    }
}

impl<'a> ParseToGeneric<'a> for HNil {
    #[inline(always)]
    fn parse_to_generic(
        _params: impl Iterator<Item = Result<&'a [u8], FromBytesError>>,
    ) -> Result<Self, PayloadFromBytesError> {
        Ok(Self)
    }
}

impl<'a, T> FromRawEvent<'a> for T
where
    T: Generic,
    T::Repr: ParseToGeneric<'a>,
{
    fn parse(raw_event: &RawEvent<'a>) -> Result<Self, PayloadFromBytesError> {
        let mut params = todo!();

        let hlist = T::Repr::parse_to_generic(&mut params)?;
        Ok(frunk::from_generic(hlist))
    }
}

1 post - 1 participant

Read full topic

🏷️ rust_feed