问题是发出的程序集做什么
无需猜测;你可以看看。让我们使用这段代码:
use std::ops::Add;
#[derive(Copy, Clone, Debug)]
struct Num(i32);
impl Add for Num {
type Output = Num;
fn add(self, rhs: Num) -> Num {
Num(self.0 + rhs.0)
}
}
#[inline(never)]
fn example() -> Num {
let a = Num(0);
let b = Num(1);
let c = a + b;
let d = a + b;
c + d
}
fn main() {
println!("{:?}", example());
}
将其粘贴到铁锈游乐场 https://play.rust-lang.org/,然后选择Release模式并查看LLVM IR:
搜索结果以查看该定义example
功能:
; playground::example
; Function Attrs: noinline norecurse nounwind nonlazybind readnone uwtable
define internal fastcc i32 @_ZN10playground7example17h60e923840d8c0cd0E() unnamed_addr #2 {
start:
ret i32 2
}
是的,这在编译时被完全评估,并一直简化为一个简单的常量。现在的编译器已经相当不错了。
也许您想尝试一些不那么硬编码的东西?
#[inline(never)]
fn example(a: Num, b: Num) -> Num {
let c = a + b;
let d = a + b;
c + d
}
fn main() {
let something = std::env::args().count();
println!("{:?}", example(Num(something as i32), Num(1)));
}
Produces
; playground::example
; Function Attrs: noinline norecurse nounwind nonlazybind readnone uwtable
define internal fastcc i32 @_ZN10playground7example17h73d4138fe5e9856fE(i32 %a) unnamed_addr #3 {
start:
%0 = shl i32 %a, 1
%1 = add i32 %0, 2
ret i32 %1
}
哎呀,编译器看到我们基本上在做 (x + 1) * 2,所以它在这里做了一些棘手的优化以达到 2x + 2。让我们尝试更难的事情......
#[inline(never)]
fn example(a: Num, b: Num) -> Num {
a + b
}
fn main() {
let something = std::env::args().count() as i32;
let another = std::env::vars().count() as i32;
println!("{:?}", example(Num(something), Num(another)));
}
Produces
; playground::example
; Function Attrs: noinline norecurse nounwind nonlazybind readnone uwtable
define internal fastcc i32 @_ZN10playground7example17h73d4138fe5e9856fE(i32 %a, i32 %b) unnamed_addr #3 {
start:
%0 = add i32 %b, %a
ret i32 %0
}
一个简单的add
操作说明。
真正的收获是:
- 查看为您的案例生成的程序集。即使看起来相似的代码也可能以不同的方式进行优化。
- 执行微观和宏观基准测试。你永远不知道代码在大局中将如何发挥作用。也许你所有的缓存都会被耗尽,但你的微基准测试将会非常出色。
Rust 编译器是否足够聪明,知道在这种情况下,没有必要进行复制?
正如您刚才看到的,Rust 编译器plusLLVM 非常聪明。一般来说,是possible当它知道不需要操作数时删除副本。它是否适用于您的情况很难回答。
即使确实如此,您可能也不希望通过堆栈传递大项目,因为它总是如此possible需要复制它。
请注意,您不必为该值实现复制,您可以选择仅通过引用允许它:
impl<'a, 'b> Add<&'b Num> for &'a Num {
type Output = Num;
fn add(self, rhs: &'b Num) -> Num {
Num(self.0 + rhs.0)
}
}
事实上,您可能想要实现两种添加它们的方法,也许还有所有 4 种值/引用的排列!