Inlined code for operating on scala -> operating on vec

Suppose we have plus_0 and mul_0 which are defined on i32's and we want to generalize it to operate on
Vec<i32> with the following constraints:

  • we want to inline mul_0, plus_0 rather than a function call per + *

Here is what I have so far. I am stuck on figuring how to invoke the plus_0 / mul_0, and how to force it to be inlined.

EDIT: simplified code a bit, removing useless parameters

pub struct Messy<Fniii>
where
    Fniii: Fn(i32, i32) -> i32,
{
    data_iii: std::marker::PhantomData<Fniii>,
}

impl<Fniii> Messy<Fniii>
where
    Fniii: Fn(i32, i32) -> i32,
{
    fn do_vec(lhs: Vec<i32>, rhs: Vec<i32>) -> Vec<i32> {
        let n = lhs.len();
        assert!(n == rhs.len());
        let mut ans = Vec::with_capacity(n);

        for i in 0..n {
            ans.push(
                // Fniii(lhs[i], rhs[i])
                // how do I call this function on these two args?
            )
        }

        ans
    }
}

#[inline(always)]
fn plus_0(a: i32, b: i32) -> i32 {
    a + b
}

#[inline(always)]
fn mul_0(a: i32, b: i32) -> i32 {
    a * b
}

The Fn traits require you to have an actual object of the type to call it. You have to make your own trait if you want to be able to call it without an object.

2 Likes

If I want the functions to be inlined then I have to go with the trait strategy, because "require object -> compiler can't inline function at compile time" ?

It certainly might inline it even if you have a specific object. In fact, every closure has a unique type exactly because it enables this kind of inlining.

1 Like
pub trait MessyT {
    fn do_scalar(lhs: i32, rhs: i32) -> i32;

    fn do_vec(lhs: Vec<i32>, rhs: Vec<i32>) -> Vec<i32> {
        let n = lhs.len();
        assert!(n == rhs.len());
        let mut ans = Vec::with_capacity(n);

        for i in 0..n {
            ans.push(
                Self::do_scalar(lhs[i], rhs[i]), // Fniii(lhs[i], rhs[i])
                                                 // how do I call this function on these two args?
            )
        }

        ans
    }
}

pub struct ScalarPlus {}
pub struct ScalarMul {}

impl MessyT for ScalarPlus {
    #[inline(always)]
    fn do_scalar(a: i32, b: i32) -> i32 {
        a + b
    }
}

impl MessyT for ScalarMul {
    #[inline(always)]
    fn do_scalar(a: i32, b: i32) -> i32 {
        a * b
    }
}

Is this the 'trait solution' you were referring to ? Is this guaranteed to inline?

I don't know if its guaranteed, but I would say that it is very likely.

1 Like

Are there other flags I can throw at this to guarantee it's inlined ?

I don't know the rules regarding traits and inlining.

I think a cleaner trait solution might have the trait only for do_scalar, and something similar to your first code for the rest of it. Maybe something like this?

pub struct Messy<Fniii>
{
    data_iii: std::marker::PhantomData<Fniii>,
}

impl<Fniii> Messy<Fniii>
where
    Fniii: ScalarOperation,
{
    pub fn do_vec(lhs: Vec<i32>, rhs: Vec<i32>) -> Vec<i32> {
        let n = lhs.len();
        assert!(n == rhs.len());
        let mut ans = Vec::with_capacity(n);

        for i in 0..n {
            ans.push(
                Fniii::do_scalar(lhs[i], rhs[i])
            )
        }

        ans
    }
}

pub trait ScalarOperation {
    fn do_scalar(lhs: i32, rhs: i32) -> i32;
}

pub struct ScalarPlus;
impl ScalarOperation for ScalarPlus {
    #[inline(always)]
    fn do_scalar(lhs: i32, rhs: i32) -> i32 {
        lhs + rhs
    }
}

pub struct ScalarMul;
impl ScalarOperation for ScalarMul {
    #[inline(always)]
    fn do_scalar(lhs: i32, rhs: i32) -> i32 {
        lhs * rhs
    }
}

With that said, both your trait-based solution and this one are reasonable, and should be pretty equivalent.

Is this guaranteed to inline?

#[inline(always)] is the strongest hint you an give to the compiler. According to the reference, it "suggests that an inline expansion should always be performed".

The compiler is free to not inline it, but there must be a very good reason not to. Since these are such tiny functions, and they're marked with #[inline(always)], I would be confident assuming they are always inlined.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.