Rust 是否去虚拟化特征对象函数调用?
Does Rust devirtualize trait object function calls?
devirtualize: to change a virtual/polymorphic/indirect function call into a static function call due to some guarantee that the change is correct -- source: myself
给定一个简单的特征对象,&dyn ToString
,使用静态已知类型创建,String
:
fn main() {
let name: &dyn ToString = &String::from("Steve");
println!("{}", name.to_string());
}
对.to_string()
的调用是否直接使用<String as ToString>::to_string()
?或者只是通过特征的 vtable 间接地?如果是间接的,是否可以将此调用去虚拟化?还是有什么基本因素阻碍了这种优化?
这道题的激发代码要复杂得多;它使用异步特征函数,我想知道在某些情况下是否可以优化返回 Box<dyn Future>
。
Does Rust devirtualize trait object function calls?
没有
Rust 是一种语言,它不做任何事情;它只规定了语义。
在这种特定情况下,Rust 语言没有规定去虚拟化,因此允许实现去虚拟化。
目前,唯一稳定的实现是带有 LLVM 后端的 rustc——尽管如果您喜欢冒险,也可以使用 cranelift 后端。
您可以在 the playground 和 select“显示 LLVM IR”而不是“运行”以及“发布”而不是“调试”上测试此实现的代码",你应该可以检查没有虚拟调用。
代码的修订版本将强制转换隔离为 trait + 动态调用以使其更容易:
#[inline(never)]
fn to_string(s: &String) -> String {
let name: &dyn ToString = s;
name.to_string()
}
fn main() {
let name = String::from("Steve");
let name = to_string(&name);
println!("{}", name);
}
当 the playground 上的 运行 产生其他东西时:
; playground::to_string
; Function Attrs: noinline nonlazybind uwtable
define internal fastcc void @_ZN10playground9to_string17h4a25abbd46fc29d4E(%"std::string::String"* noalias nocapture dereferenceable(24) %0, %"std::string::String"* noalias readonly align 8 dereferenceable(24) %s) unnamed_addr #0 {
start:
; call <alloc::string::String as core::clone::Clone>::clone
tail call void @"_ZN60_$LT$alloc..string..String$u20$as$u20$core..clone..Clone$GTclone17h1e3037d7443348baE"(%"std::string::String"* noalias nocapture nonnull sret dereferenceable(24) %0, %"std::string::String"* noalias nonnull readonly align 8 dereferenceable(24) %s)
ret void
}
你可以清楚地看到对 ToString::to_string
的调用已被对 <String as Clone>::clone
的简单调用所取代;去虚拟化调用。
The motivating code for this question is much more complicated; it uses async trait functions and I'm wondering if returning a Box<dyn Future>
can be optimized in some cases.
很遗憾,您不能从上面的例子中得出任何结论。
优化很挑剔。本质上,大多数优化类似于模式匹配+使用正则表达式替换:人类看起来良性的差异可能会完全抛弃模式匹配并阻止优化应用。
如果重要的话,唯一可以确定在您的情况下应用了优化的方法是检查发出的程序集。
但是,实际上,在这种情况下,与虚拟调用相比,我更担心内存分配。虚拟调用大约有 5ns 的开销——尽管它确实抑制了一些优化——而内存分配(和最终的释放)通常花费 20ns - 30ns。
Does the call to .to_string()
use <String as ToString>::to_string()
directly? Or only indirectly via the trait's vtable?
我们可以通过编写两个函数来测试这种情况,一个使用 dyn ToString
,一个直接使用具体类型 String
:
pub fn dyn_to_string() {
let name: &dyn ToString = &String::from("Steve");
println!("{}", name.to_string());
}
pub fn concrete_to_string() {
let name: &String = &String::from("Steve");
println!("{}", name.to_string());
}
现在我们可以查看生成的程序集:
playground::dyn_to_string:
...
callq *<alloc::string::String as core::clone::Clone>::clone@GOTPCREL(%rip)
movq %rbx, 24(%rsp)
leaq <alloc::string::String as core::fmt::Display>::fmt(%rip), %rax
如您所见,dyn_to_string
已优化为直接使用 <String as Clone>::clone
而不是通过 vtable 间接使用 - 它已被去虚拟化。其实具体实现和trait对象调用完全一样:
set playground::concrete_to_string, playground::dyn_to_string
但是,要回答更广泛的问题:
Does Rust devirtualize trait object function calls?
视情况而定。编译器不能总是执行去虚拟化。它在上面的代码中做到了,但在其他情况下,它可能不会。您不应该期望特征对象调用会被去虚拟化。泛型是有保证的零成本抽象。特征对象不是。
devirtualize: to change a virtual/polymorphic/indirect function call into a static function call due to some guarantee that the change is correct -- source: myself
给定一个简单的特征对象,&dyn ToString
,使用静态已知类型创建,String
:
fn main() {
let name: &dyn ToString = &String::from("Steve");
println!("{}", name.to_string());
}
对.to_string()
的调用是否直接使用<String as ToString>::to_string()
?或者只是通过特征的 vtable 间接地?如果是间接的,是否可以将此调用去虚拟化?还是有什么基本因素阻碍了这种优化?
这道题的激发代码要复杂得多;它使用异步特征函数,我想知道在某些情况下是否可以优化返回 Box<dyn Future>
。
Does Rust devirtualize trait object function calls?
没有
Rust 是一种语言,它不做任何事情;它只规定了语义。
在这种特定情况下,Rust 语言没有规定去虚拟化,因此允许实现去虚拟化。
目前,唯一稳定的实现是带有 LLVM 后端的 rustc——尽管如果您喜欢冒险,也可以使用 cranelift 后端。
您可以在 the playground 和 select“显示 LLVM IR”而不是“运行”以及“发布”而不是“调试”上测试此实现的代码",你应该可以检查没有虚拟调用。
代码的修订版本将强制转换隔离为 trait + 动态调用以使其更容易:
#[inline(never)]
fn to_string(s: &String) -> String {
let name: &dyn ToString = s;
name.to_string()
}
fn main() {
let name = String::from("Steve");
let name = to_string(&name);
println!("{}", name);
}
当 the playground 上的 运行 产生其他东西时:
; playground::to_string
; Function Attrs: noinline nonlazybind uwtable
define internal fastcc void @_ZN10playground9to_string17h4a25abbd46fc29d4E(%"std::string::String"* noalias nocapture dereferenceable(24) %0, %"std::string::String"* noalias readonly align 8 dereferenceable(24) %s) unnamed_addr #0 {
start:
; call <alloc::string::String as core::clone::Clone>::clone
tail call void @"_ZN60_$LT$alloc..string..String$u20$as$u20$core..clone..Clone$GTclone17h1e3037d7443348baE"(%"std::string::String"* noalias nocapture nonnull sret dereferenceable(24) %0, %"std::string::String"* noalias nonnull readonly align 8 dereferenceable(24) %s)
ret void
}
你可以清楚地看到对 ToString::to_string
的调用已被对 <String as Clone>::clone
的简单调用所取代;去虚拟化调用。
The motivating code for this question is much more complicated; it uses async trait functions and I'm wondering if returning a
Box<dyn Future>
can be optimized in some cases.
很遗憾,您不能从上面的例子中得出任何结论。
优化很挑剔。本质上,大多数优化类似于模式匹配+使用正则表达式替换:人类看起来良性的差异可能会完全抛弃模式匹配并阻止优化应用。
如果重要的话,唯一可以确定在您的情况下应用了优化的方法是检查发出的程序集。
但是,实际上,在这种情况下,与虚拟调用相比,我更担心内存分配。虚拟调用大约有 5ns 的开销——尽管它确实抑制了一些优化——而内存分配(和最终的释放)通常花费 20ns - 30ns。
Does the call to
.to_string()
use<String as ToString>::to_string()
directly? Or only indirectly via the trait's vtable?
我们可以通过编写两个函数来测试这种情况,一个使用 dyn ToString
,一个直接使用具体类型 String
:
pub fn dyn_to_string() {
let name: &dyn ToString = &String::from("Steve");
println!("{}", name.to_string());
}
pub fn concrete_to_string() {
let name: &String = &String::from("Steve");
println!("{}", name.to_string());
}
现在我们可以查看生成的程序集:
playground::dyn_to_string:
...
callq *<alloc::string::String as core::clone::Clone>::clone@GOTPCREL(%rip)
movq %rbx, 24(%rsp)
leaq <alloc::string::String as core::fmt::Display>::fmt(%rip), %rax
如您所见,dyn_to_string
已优化为直接使用 <String as Clone>::clone
而不是通过 vtable 间接使用 - 它已被去虚拟化。其实具体实现和trait对象调用完全一样:
set playground::concrete_to_string, playground::dyn_to_string
但是,要回答更广泛的问题:
Does Rust devirtualize trait object function calls?
视情况而定。编译器不能总是执行去虚拟化。它在上面的代码中做到了,但在其他情况下,它可能不会。您不应该期望特征对象调用会被去虚拟化。泛型是有保证的零成本抽象。特征对象不是。