rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.8k stars 12.77k forks source link

Mark TypeId::of as inline to avoid monomorphizing unnecessary code #74362

Closed alecmocatta closed 2 years ago

alecmocatta commented 4 years ago

TypeId can be used to specialize code by comparing types explicitly (TypeId::of::<u8>() == TypeId::of::<T>(), examples in the wild) or implicitly (<dyn Any>::downcast_mut).

In release mode this works well; in debug mode though the unused impl is unnecessarily monomorphized and codegened, slowing compile time as well as run time.

It isn't a major issue as it only affects debug mode, but I wondered if it was worth marking TypeId::of, <TypeId as PartialEq>::eq, <dyn Any>::is and the various downcast methods as inline(always) to potentially reduce compile times and provide a nice debug mode runtime boost?

use std::any::{Any, TypeId};

struct Container<T>(T);

impl<T: 'static> Container<T> {
    fn foo(&mut self) {
        if let Some(_) = <dyn Any>::downcast_mut::<Container<u8>>(self) {
            // specialized impl
            std::process::abort();
        }
        // general impl
    }
    fn bar(&mut self) {
        if TypeId::of::<u8>() == TypeId::of::<T>() {
            // specialized impl
            std::process::abort();
        }
        // general impl
    }
}

fn main() {
    let mut c = Container(String::new());
    c.foo();
    c.bar();
}

See e.g. abort is present in IR/assembly: Playground

AngelicosPhosphoros commented 3 years ago

It would be cool if this calls get inlined in -C opt-level=1. This would improve performance of debug builds of engines like bevy, compiled with weak optimizations.

AngelicosPhosphoros commented 3 years ago

I found that this function always promoted to const on -C opt-level=1 [godbolt](https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(fontScale:14,fontUsePx:'0',j:1,lang:rust,selection:(endColumn:2,endLineNumber:22,positionColumn:2,positionLineNumber:22,selectionStartColumn:6,selectionStartLineNumber:21,startColumn:6,startLineNumber:21),source:'%23%5Binline(never)%5D%0Afn+print_a()%7B%0A++++println!!(%22A%22)%0A%7D%0A%0A%23%5Binline(never)%5D%0Afn+print_b()%7B%0A++++println!!(%22B%22)%0A%7D%0A%0A%23%5Bno_mangle%5D%0Apub+fn+test_calc()%7B%0A++++use+std::any::TypeId%3B%0A%0A++++if+TypeId::of::%3Cu8%3E()+!!%3D+TypeId::of::%3Cu32%3E()%7B%0A++++++++print_a()%3B%0A++++%7D%0A%0A++++if+TypeId::of::%3Cu8%3E()+%3D%3D+TypeId::of::%3Cu8%3E()%7B%0A++++++++print_b()%3B%0A++++%7D%0A%7D%0A'),l:'5',n:'0',o:'Rust+source+%231',t:'0')),k:45.838779956427025,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:nightly,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'1',libraryCode:'1',trim:'1'),fontScale:14,fontUsePx:'0',j:1,lang:rust,libs:!(),options:'-Copt-level%3D1',selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'rustc+nightly+(Editor+%231,+Compiler+%231)+Rust',t:'0')),k:31.619429924200652,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compiler:1,editor:1,fontScale:14,fontUsePx:'0',wrap:'1'),l:'5',n:'0',o:'%231+with+rustc+nightly',t:'0')),k:22.54179011937233,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4)

Another problem is that ParialEq::eq isn't inlined even if I use #[inline(always)]. Also, manual implementation of PartialEq would remove ability to use it in pattern matching like: [godbolt](https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(fontScale:14,fontUsePx:'0',j:1,lang:rust,selection:(endColumn:6,endLineNumber:11,positionColumn:6,positionLineNumber:8,selectionStartColumn:6,selectionStartLineNumber:11,startColumn:6,startLineNumber:8),source:'%23!!%5Bfeature(const_type_id)%5D%0A%0A%23%5Binline(always)%5D%0Apub+const+fn+is_bool%3CT:+!'static%3E()-%3Ebool%7B%0A++++use+std::any::TypeId%3B%0A%0A++++const+BOOL_ID:+TypeId+%3D+TypeId::of::%3Cbool%3E()%3B%0A++++match+TypeId::of::%3CT%3E()%7B%0A++++++++BOOL_ID+%3D%3E+true,%0A++++++++_+%3D%3E+false,%0A++++%7D%0A%7D%0A%0Apub+fn+good()-%3Ebool%7B%0A++++is_bool::%3Cbool%3E()%0A%7D%0A%0Apub+fn+bad()-%3Ebool%7B%0A++++is_bool::%3Cu8%3E()%0A%7D'),l:'5',n:'0',o:'Rust+source+%231',t:'0')),k:45.59471365638767,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:nightly,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'1',libraryCode:'1',trim:'1'),fontScale:14,fontUsePx:'0',j:1,lang:rust,libs:!(),options:'-Copt-level%3D1',selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'rustc+nightly+(Editor+%231,+Compiler+%231)+Rust',t:'0')),k:54.40528634361234,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4)

#![feature(const_type_id)]

#[inline(always)]
pub const fn is_bool<T: 'static>()->bool{
    use std::any::TypeId;

    const BOOL_ID: TypeId = TypeId::of::<bool>();
    match TypeId::of::<T>(){
        BOOL_ID => true,
        _ => false,
    }
}

pub fn good()->bool{
    is_bool::<bool>()
}

pub fn bad()->bool{
    is_bool::<u8>()
}
AngelicosPhosphoros commented 3 years ago

I think, we should close this issue in favor of https://github.com/rust-lang/rust/issues/77125

Or in favor of https://github.com/rust-lang/rust/issues/31844 which allows write such code:

#![feature(specialization)]

use std::marker::PhantomData;

struct IsSameType<T0: 'static, T1: 'static>(PhantomData<T0>, PhantomData<T1>);

trait IsSameTypeTrait{
    const IS_SAME: bool;
}

impl<T0: 'static, T1: 'static> IsSameTypeTrait for IsSameType<T0, T1>{
    default const IS_SAME: bool = false;
}

impl<T> IsSameTypeTrait for IsSameType<T, T>{
    const IS_SAME: bool = true;
}
AngelicosPhosphoros commented 2 years ago

I decided to add #[inline] because we unlikely to get #77125 resolved soon, and #31844 still has soundness problems.

AngelicosPhosphoros commented 2 years ago

Adding #[inline] wouldn't have any effect without optimizations unfortunately because there must run InlinerPass which doesn't run on debug.

With New Pass Manager (which I hope would stabilized soon), users may set opt-level=1 for fast debug builds and get profits from this change.