Skip to content

Reduce code bloat from conversion traits in function parameters #28256

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

Merged
merged 1 commit into from
Sep 15, 2015

Conversation

petrochenkov
Copy link
Contributor

This patch transforms functions of the form

fn f<Generic: AsRef<Concrete>>(arg: Generic) {
    let arg: &Concrete = arg.as_ref();
    // Code using arg
}

to the next form:

#[inline]
fn f<Generic: AsRef<Concrete>>(arg: Generic) {
    fn f_inner(arg: &Concrete) {
        // Code using arg
    }

    f_inner(arg.as_ref());
}

Therefore, most of the code is concrete and not duplicated during monomorphisation (unless inlined)
and only the tiny bit of conversion code is duplicated. This method was mentioned by @aturon in the
Conversion Traits RFC (https://github.com/rust-lang/rfcs/blame/master/text/0529-conversion-traits.md#L249) and similar techniques are not uncommon in C++ template libraries.

This patch goes to the extremes and applies the transformation even to smaller functions1
for purity of the experiment. Some of them can be rolled back if considered too ridiculous.

1 However who knows how small are these functions are after inlining and everything.

The functions in question are mostly fs/os functions and not used especially often with variety
of argument types, so the code size reduction is rather small (but consistent). Here are the sizes
of stage2 artifacts before and after the patch:
https://gist.github.com/petrochenkov/e76a6b280f382da13c5d
https://gist.github.com/petrochenkov/6cc28727d5256dbdfed0

Note:
All the inner functions are concrete and unavailable for cross-crate inlining, some of them may
need #[inline] annotations in the future.

r? @aturon

@eefriedman
Copy link
Contributor

It would be nice if the compiler could do this automatically... with MIR, it should be possible to detect generic functions where only some small prefix depends on the type parameter.

@alexcrichton
Copy link
Member

I would personally prefer to avoid this sort of manual conversion for now unless we've got some numbers saying otherwise. It looks like the binary sizes didn't change much, but could you also measure compile time as well? I expect that's the largest win to gain from conversions like this.

I would also prefer a style that looked something like:

fn f<Generic: AsRef<Concrete>>(arg: Generic) {
    _f(arg.as_ref())
}

fn _f(arg: &Concrete) {
    // ...
}

I don't think the #[inline] on the generic function is necessary, it avoids an extra level of indentation, and it meshes well with &self by allowing you to still use methods.

@alexcrichton
Copy link
Member

I also agree with @eefriedman that it'd be nice to have this automatically done in some cases, but I also wouldn't want to hold my breath for that to happen in the near future. I'd primarily like to see some measurements in terms of compile time to see what we can gain from a pattern like this.

@arielb1
Copy link
Contributor

arielb1 commented Sep 8, 2015

There is no real reason to add #[inline] to generic functions - these are inlined automatically.

@petrochenkov
Copy link
Contributor Author

The difference in compile times is minimal:

before: 18m34.628s
after: 18m29.909s
before: 18m30.321s
after: 18m28.178s

@petrochenkov
Copy link
Contributor Author

@arielb1

There is no real reason to add #[inline] to generic functions - these are inlined automatically.

Doesn't #[inline] serve as a hint to LLVM inlining heuristics? Like "yes, inline it harder". In my imagination the function calls to the outer functions shouldn't happen at all, i.e. argument conversions should exist "outside" of functions, like in C++, and #[inline] is supposed to reflect this in code.

@alexcrichton

I would personally prefer to avoid this sort of manual conversion

Sure, doing this manually is not optimal. Instead of compiler "magic" @aturon suggested explicit, but very consice syntax https://github.com/rust-lang/rfcs/blame/master/text/0529-conversion-traits.md#L271 which I like better. This patch is mostly trying to answer the question if this is worth doing at all.

I would also prefer a style that looked something like:

fn f<Generic: AsRef>(arg: Generic) {
_f(arg.as_ref())
}

fn _f(arg: &Concrete) {
// ...
}

Ok, I have no preferences here.

@arielb1
Copy link
Contributor

arielb1 commented Sep 8, 2015

@petrochenkov

LLVM should be smart enough to make inlining decisions.

@petrochenkov
Copy link
Contributor Author

Without #[inline] (just in case):
Time: 18m31.700s
Size: https://gist.github.com/petrochenkov/055e9df6c5260ac02a7d

@petrochenkov
Copy link
Contributor Author

Without #[inline] + changes to "small" functions reverted
https://gist.github.com/petrochenkov/d7047e5803cd5b92fc4e

I'd probably switch to this version. The size is a bit worse than in https://gist.github.com/petrochenkov/055e9df6c5260ac02a7d, but librustc_back, libterm, libstd and executables still benefit.

@petrochenkov
Copy link
Contributor Author

Updated.

@aturon
Copy link
Member

aturon commented Sep 11, 2015

@alexcrichton I'm happy to move forward with the current version of this code, but leave the final decision to you.

@alexcrichton
Copy link
Member

@bors: r+ 1049021

@bors
Copy link
Collaborator

bors commented Sep 14, 2015

⌛ Testing commit 1049021 with merge e629dba...

bors added a commit that referenced this pull request Sep 14, 2015
This patch transforms functions of the form
```
fn f<Generic: AsRef<Concrete>>(arg: Generic) {
	let arg: &Concrete = arg.as_ref();
	// Code using arg
}
```
to the next form:
```
#[inline]
fn f<Generic: AsRef<Concrete>>(arg: Generic) {
	fn f_inner(arg: &Concrete) {
		// Code using arg
	}
	
	f_inner(arg.as_ref());
}
```

Therefore, most of the code is concrete and not duplicated during monomorphisation (unless inlined)
and only the tiny bit of conversion code is duplicated. This method was mentioned by @aturon in the
Conversion Traits RFC (https://github.com/rust-lang/rfcs/blame/master/text/0529-conversion-traits.md#L249) and similar techniques are not uncommon in C++ template libraries.

This patch goes to the extremes and applies the transformation even to smaller functions<sup>1</sup>
for purity of the experiment. *Some of them can be rolled back* if considered too ridiculous.

<sup>1</sup> However who knows how small are these functions are after inlining and everything.

The functions in question are mostly `fs`/`os` functions and not used especially often with variety
of argument types, so the code size reduction is rather small (but consistent). Here are the sizes
of stage2 artifacts before and after the patch:
https://gist.github.com/petrochenkov/e76a6b280f382da13c5d
https://gist.github.com/petrochenkov/6cc28727d5256dbdfed0

Note:
All the `inner` functions are concrete and unavailable for cross-crate inlining, some of them may
need `#[inline]` annotations in the future.

r? @aturon
@bors bors merged commit 1049021 into rust-lang:master Sep 15, 2015
@petrochenkov petrochenkov deleted the conv branch September 21, 2015 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants