Skip to content

Optimize generics to a dynamic dispatch for size #114598

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

Open
orowith2os opened this issue Aug 7, 2023 · 8 comments
Open

Optimize generics to a dynamic dispatch for size #114598

orowith2os opened this issue Aug 7, 2023 · 8 comments
Labels
C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such I-heavy Issue: Problems and improvements with respect to binary size of generated code. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@orowith2os
Copy link

orowith2os commented Aug 7, 2023

Generic currently generate two similar functions to do something - for some cases, this isn't desired, like in "all I care about is size" cases. For this, it seems reasonable for the compiler to instead check the input types at runtime and figure out what to do based off of that. I'm not quite sure how much it would help, but it would help nonetheless.

So, for a theoretical example:

fn convert_to_string<T: ToString>(input: T) -> String {
    input.to_string()
}

Given two inputs, an i32 and a f64 in this example, it would get turned into:

// The compiler will probably mangle these so they don't conflict, but that's a different topic
fn convert_to_string(input: i32) -> String {
    input.to_string()
}
fn convert_to_string(input: f64) -> String {
    input.to_string()
}

But, if the compiler were to be told to optimize it as much as possible for size, this would instead get turned into something like:

fn convert_to_string(input: i32 or f64) -> String {
    match typeof(input) {
        i32 => input.to_string(),
        f64 => input.to_string(),
    }
}

Obviously, the example code is a bit weird and isn't valid, but the idea is there. Instead of creating two different functions, make one that matches on the types at runtime. The point here is to work without traits, which is why I did not suggest converting generics that rely on traits to a dynamic dispatch instead.

This would also be useful with variadic generics, I think.

@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Aug 7, 2023
@saethlin saethlin added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Aug 7, 2023
@marcospb19
Copy link
Contributor

marcospb19 commented Aug 8, 2023

Maybe a duplicate of Related to #77960.

@saethlin saethlin added I-heavy Issue: Problems and improvements with respect to binary size of generated code. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Aug 12, 2023
@orowith2os
Copy link
Author

@marcospb19 maybe? Assuming #77960 works without traits too, it would probably be.

You can easily optimize generics that rely on traits to something computed at runtime, but not ones that don't use traits. That's why I made this request.

@DasLixou
Copy link

Without the background in expose-fn-type RFC, the code that the compiler would generate could probably also look like this:

// Callee side
enum Input {
    A(i32),
    B(f64),
}
fn convert_to_string(input: Input) -> String {
    match input {
        Input::A(a) => a.to_string(),
        Input::B(b) => b.to_string(),
    }
}
// Caller side
convert_to_string(Input::A(0i32));

This would possibly be already possible with a proc macro attribute 🤔.

But with the background of the RFC, we may have other reasons for a type_of! macro, but then there's the question how this would work with for example closures...

@marcospb19
Copy link
Contributor

@orowith2os

but not ones that don't use traits

Sorry, I got confused because your example uses the trait ToString, could you give a more concrete example without traits involved?

@orowith2os
Copy link
Author

You can just get rid of the traits in the example; iirc I put them in there after some quick discussions and it was noted that this would be feasible at first only for those that use traits.

@LunarLambda
Copy link

LunarLambda commented Dec 16, 2023

@orowith2os

but not ones that don't use traits

Sorry, I got confused because your example uses the trait ToString, could you give a more concrete example without traits involved?

Vec<T>::push. No trait bounds, so the only thing known about T is it's size and alignment, so it could be type erased into memcpy + pointer add. Maybe this is already done for Vec (it has some specializations under the hood), but any function that just moves unbounded Ts around (containers, iterators) could benefit.

Edit: I think this only works for types that don't need Drop or ones that take Drop types but never call drop (do those exist?)

@marcospb19
Copy link
Contributor

IDK if rustc supports variable length arrays.

fn example(t: T) {
    // 1. VLA
    let array: [T; 8] = ...; // Variable size in runtime
    
    // 2. Variable memory layout
    let tuple = (0, t, 100);
    &tuple.1; // What's the offset to access this? Variable in runtime
}

@DasLixou
Copy link

IDK if rustc supports variable length arrays.

fn example(t: T) {

    // 1. VLA

    let array: [T; 8] = ...; // Variable size in runtime

    

    // 2. Variable memory layout

    let tuple = (0, t, 100);

    &tuple.1; // What's the offset to access this? Variable in runtime

}

When looking at my enum representation, this can be fairly easy answered. The Offset is the biggest of all used types for T

@workingjubilee workingjubilee added the C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such label Feb 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such I-heavy Issue: Problems and improvements with respect to binary size of generated code. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

7 participants