消费 self 并返回它对性能有何影响?
What are the performance implications of consuming self and returning it?
我一直在阅读诸如 Why does a function that accepts a Box<MyType> complain of a value being moved when a function that accepts self works?, Preferable pattern for getting around the "moving out of borrowed self" checker, and 之类的问题,现在我对使用 self 的性能特征感到好奇,但可能 return 将其传递给调用者。
举一个更简单的例子,假设我想创建一个保证非空的集合类型。为实现这一点,"remove" 操作需要使用集合并可选地使用 return 本身。
struct NonEmptyCollection { ... }
impl NonEmptyCollection {
fn pop(mut self) -> Option<Self> {
if self.len() == 1 {
None
} else {
// really remove the element here
Some(self)
}
}
}
(我想它也应该 return 它从列表中删除的值,但这只是一个例子。)现在假设我调用这个函数:
let mut c = NonEmptyCollection::new(...);
if let Some(new_c) = c.pop() {
c = new_c
} else {
// never use c again
}
对象的内存实际上发生了什么?如果我有这样的代码怎么办:
let mut opt: Option<NonEmptyCollection> = Some(NonEmptyCollection::new(...));
opt = opt.take().pop();
函数的签名不能保证returned的对象是同一个对象,有什么优化的可能吗? C++ return 值优化 之类的东西是否适用,允许 returned 对象 "constructed" 在与之前相同的内存中?如果我可以在上面的接口和调用者必须处理生命周期的接口之间进行选择:
enum PopResult {
StillValid,
Dead
};
impl NonEmptyCollection {
fn pop(&mut self) -> PopResult {
// really remove the element
if self.len() == 0 { PopResult::Dead } else { PopResult::StillValid }
}
}
出于性能原因,有没有理由选择这个更脏的接口?在 answer to the second example I linked 中,trentcl 建议将 Option
存储在数据结构中,以允许调用者就地进行更改,而不是每次都执行 remove
后跟 insert
。这个肮脏的界面会是一个更快的选择吗?
YMMV
根据优化器的突发奇想,您最终可能会得到:
- 接近空操作,
- 一些寄存器移动,
- 位拷贝数。
这将取决于是否:
- 调用是否内联,
- 调用者重新分配给原始变量或创建一个新变量(以及 LLVM 如何处理重用死space),
size_of::<Self>()
.
您得到的唯一保证是不会发生深层复制,因为没有 .clone()
调用。
对于其他任何事情,您需要检查 LLVM IR 或程序集。
我一直在阅读诸如 Why does a function that accepts a Box<MyType> complain of a value being moved when a function that accepts self works?, Preferable pattern for getting around the "moving out of borrowed self" checker, and
举一个更简单的例子,假设我想创建一个保证非空的集合类型。为实现这一点,"remove" 操作需要使用集合并可选地使用 return 本身。
struct NonEmptyCollection { ... }
impl NonEmptyCollection {
fn pop(mut self) -> Option<Self> {
if self.len() == 1 {
None
} else {
// really remove the element here
Some(self)
}
}
}
(我想它也应该 return 它从列表中删除的值,但这只是一个例子。)现在假设我调用这个函数:
let mut c = NonEmptyCollection::new(...);
if let Some(new_c) = c.pop() {
c = new_c
} else {
// never use c again
}
对象的内存实际上发生了什么?如果我有这样的代码怎么办:
let mut opt: Option<NonEmptyCollection> = Some(NonEmptyCollection::new(...));
opt = opt.take().pop();
函数的签名不能保证returned的对象是同一个对象,有什么优化的可能吗? C++ return 值优化 之类的东西是否适用,允许 returned 对象 "constructed" 在与之前相同的内存中?如果我可以在上面的接口和调用者必须处理生命周期的接口之间进行选择:
enum PopResult {
StillValid,
Dead
};
impl NonEmptyCollection {
fn pop(&mut self) -> PopResult {
// really remove the element
if self.len() == 0 { PopResult::Dead } else { PopResult::StillValid }
}
}
出于性能原因,有没有理由选择这个更脏的接口?在 answer to the second example I linked 中,trentcl 建议将 Option
存储在数据结构中,以允许调用者就地进行更改,而不是每次都执行 remove
后跟 insert
。这个肮脏的界面会是一个更快的选择吗?
YMMV
根据优化器的突发奇想,您最终可能会得到:
- 接近空操作,
- 一些寄存器移动,
- 位拷贝数。
这将取决于是否:
- 调用是否内联,
- 调用者重新分配给原始变量或创建一个新变量(以及 LLVM 如何处理重用死space),
size_of::<Self>()
.
您得到的唯一保证是不会发生深层复制,因为没有 .clone()
调用。
对于其他任何事情,您需要检查 LLVM IR 或程序集。