为什么 Rust 编译器不能优化掉 Box::downcast 的 Err 臂?
Why can the Rust compiler not optimize away the Err arm of Box::downcast?
我有一个 Box<dyn Any>
并且我知道底层类型所以我想优化掉 Box::downcast()
(source 中的测试。
首先我尝试使用 std::hint::unreachable_unchecked()
:
pub unsafe fn downcast() -> Box<i32> {
let value = any();
if let Ok(value) = value.downcast() {
value
} else {
std::hint::unreachable_unchecked()
}
}
和
pub unsafe fn downcast() -> Box<i32> {
any().downcast().map_err(|_| std::hint::unreachable_unchecked()).unwrap()
}
和 rustc -C opt-level=3
都会导致这个(省略 40 行):
example::downcast:
push rbx
sub rsp, 16
call any@PLT
mov rbx, rax
mov qword ptr [rsp], rax
mov qword ptr [rsp + 8], rdx
mov rdi, rax
call qword ptr [rdx + 24]
mov rax, rbx
add rsp, 16
pop rbx
ret
mov rbx, rax
mov rdi, rsp
call core::ptr::drop_in_place
mov rdi, rbx
call _Unwind_Resume@PLT
ud2
因为这不是我想要的优化,所以我尝试了
pub unsafe fn downcast() -> Box<i32> {
let value = any();
std::intrinsics::assume(value.is::<i32>());
value.downcast().unwrap()
}
但这变得更糟(省略了 118 行):
example::downcast:
push r15
push r14
push rbx
sub rsp, 32
call any@PLT
mov rbx, rax
mov r14, rdx
mov qword ptr [rsp], rax
mov qword ptr [rsp + 8], rdx
mov r15, qword ptr [rdx + 24]
mov rdi, rax
call r15
mov qword ptr [rsp + 16], rbx
mov qword ptr [rsp + 24], r14
mov rdi, rbx
call r15
movabs rcx, -5015437470765251660 ;TypeId::of::<i32>()
cmp rax, rcx
jne .LBB5_7
mov rax, rbx
add rsp, 32
pop rbx
pop r14
pop r15
ret
.LBB5_7:
mov rdi, rbx
mov rsi, r14
call core::result::unwrap_failed
ud2
mov rbx, rax
lea rdi, [rsp + 16]
call core::ptr::drop_in_place
mov rdi, rbx
call _Unwind_Resume@PLT
ud2
mov rbx, rax
mov rdi, rsp
call core::ptr::drop_in_place
mov rdi, rbx
call _Unwind_Resume@PLT
ud2
我希望生成这样的代码,这是来自 Box::downcast
的 Ok
手臂:
pub unsafe fn downcast() -> Box<i32> {
let value = any();
let raw: *mut dyn Any = Box::into_raw(value);
Box::from_raw(raw as *mut i32)
}
结果如下(0 行省略):
example::downcast:
push rax
call any@PLT
pop rcx
ret
为什么编译器不能这样优化代码?
所有程序集generated by godbolt。
让我们尽量手动优化您的代码。如果我们手动内联 downcast()
我们会得到以下内容:
pub unsafe fn downcast() -> Box<i32> {
let value = any();
if value.is::<i32>() {
let raw: *mut Any = Box::into_raw(value);
Box::from_raw(raw as *mut i32)
} else {
std::hint::unreachable_unchecked()
}
}
我们可以这样改造:
pub unsafe fn downcast() -> Box<i32> {
let value = any();
value.is::<i32>();
let raw: *mut Any = Box::into_raw(value);
Box::from_raw(raw as *mut i32)
}
value.is::<i32>()
未使用!我们可以删除它吗?问题就出在这里。
is
方法在 dyn Any
对象上调用 get_type_id
。该方法只能在运行时确定。而且它可能有副作用。因此它不能被删除。
您可以从以下函数中看到与示例中相同的冗长汇编代码:
#![feature(get_type_id)]
pub fn nop(any: Box<dyn Any>) {
any.get_type_id();
}
现在你可能会争辩说 Any::get_type_id
是由编译器普遍定义的,不能被覆盖,但编译器不够聪明,无法意识到这一点。
我有一个 Box<dyn Any>
并且我知道底层类型所以我想优化掉 Box::downcast()
(source 中的测试。
首先我尝试使用 std::hint::unreachable_unchecked()
:
pub unsafe fn downcast() -> Box<i32> {
let value = any();
if let Ok(value) = value.downcast() {
value
} else {
std::hint::unreachable_unchecked()
}
}
和
pub unsafe fn downcast() -> Box<i32> {
any().downcast().map_err(|_| std::hint::unreachable_unchecked()).unwrap()
}
和 rustc -C opt-level=3
都会导致这个(省略 40 行):
example::downcast:
push rbx
sub rsp, 16
call any@PLT
mov rbx, rax
mov qword ptr [rsp], rax
mov qword ptr [rsp + 8], rdx
mov rdi, rax
call qword ptr [rdx + 24]
mov rax, rbx
add rsp, 16
pop rbx
ret
mov rbx, rax
mov rdi, rsp
call core::ptr::drop_in_place
mov rdi, rbx
call _Unwind_Resume@PLT
ud2
因为这不是我想要的优化,所以我尝试了
pub unsafe fn downcast() -> Box<i32> {
let value = any();
std::intrinsics::assume(value.is::<i32>());
value.downcast().unwrap()
}
但这变得更糟(省略了 118 行):
example::downcast:
push r15
push r14
push rbx
sub rsp, 32
call any@PLT
mov rbx, rax
mov r14, rdx
mov qword ptr [rsp], rax
mov qword ptr [rsp + 8], rdx
mov r15, qword ptr [rdx + 24]
mov rdi, rax
call r15
mov qword ptr [rsp + 16], rbx
mov qword ptr [rsp + 24], r14
mov rdi, rbx
call r15
movabs rcx, -5015437470765251660 ;TypeId::of::<i32>()
cmp rax, rcx
jne .LBB5_7
mov rax, rbx
add rsp, 32
pop rbx
pop r14
pop r15
ret
.LBB5_7:
mov rdi, rbx
mov rsi, r14
call core::result::unwrap_failed
ud2
mov rbx, rax
lea rdi, [rsp + 16]
call core::ptr::drop_in_place
mov rdi, rbx
call _Unwind_Resume@PLT
ud2
mov rbx, rax
mov rdi, rsp
call core::ptr::drop_in_place
mov rdi, rbx
call _Unwind_Resume@PLT
ud2
我希望生成这样的代码,这是来自 Box::downcast
的 Ok
手臂:
pub unsafe fn downcast() -> Box<i32> {
let value = any();
let raw: *mut dyn Any = Box::into_raw(value);
Box::from_raw(raw as *mut i32)
}
结果如下(0 行省略):
example::downcast:
push rax
call any@PLT
pop rcx
ret
为什么编译器不能这样优化代码?
所有程序集generated by godbolt。
让我们尽量手动优化您的代码。如果我们手动内联 downcast()
我们会得到以下内容:
pub unsafe fn downcast() -> Box<i32> {
let value = any();
if value.is::<i32>() {
let raw: *mut Any = Box::into_raw(value);
Box::from_raw(raw as *mut i32)
} else {
std::hint::unreachable_unchecked()
}
}
我们可以这样改造:
pub unsafe fn downcast() -> Box<i32> {
let value = any();
value.is::<i32>();
let raw: *mut Any = Box::into_raw(value);
Box::from_raw(raw as *mut i32)
}
value.is::<i32>()
未使用!我们可以删除它吗?问题就出在这里。
is
方法在 dyn Any
对象上调用 get_type_id
。该方法只能在运行时确定。而且它可能有副作用。因此它不能被删除。
您可以从以下函数中看到与示例中相同的冗长汇编代码:
#![feature(get_type_id)]
pub fn nop(any: Box<dyn Any>) {
any.get_type_id();
}
现在你可能会争辩说 Any::get_type_id
是由编译器普遍定义的,不能被覆盖,但编译器不够聪明,无法意识到这一点。