消费自我并将其归还有什么性能影响?

时间:2018-01-12 12:07:22

标签: rust ownership

我一直在阅读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" checkerHow to capture self consuming variable in a struct?等问题,现在我对消费自我的性能特征感到好奇,但可能会将其返回给来电者。

为了做一个更简单的例子,想象一下我想创建一个保证非空的集合类型。为实现此目的,“删除”操作需要使用集合并可选地返回自身。

struct NonEmptyCollection { ... }

impl NonEmptyCollection {
    fn pop(mut self) -> Option<Self> {
        if self.len() == 1 {
            None
        } else {
            // really remove the element here
            Some(self)
        }
    }
}

(我想它应该返回它从列表中删除的值,但它只是一个例子。)现在让我说我调用这个函数:

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();

函数的签名不能保证返回的对象实际上是同一个,那么可以进行哪些优化?像C ++ 返回值优化这样的东西是否适用,允许返回的对象在之前的同一个内存中“构建”?如果我可以在上面的接口和调用者必须处理生命周期的接口之间做出选择:

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。这个脏接口会不会更快?

1 个答案:

答案 0 :(得分:3)

<强> YMMV

根据优化程序的突发奇想,您最终可能会:

  • 接近无操作,
  • 一些注册移动,
  • 许多位副本。

这将取决于:

  • 内联或不内联,
  • 调用者重新分配给原始变量或创建一个新变量(以及LLVM处理重用死空的程度),
  • size_of::<Self>()

唯一可以保证的是,不会发生深层复制,因为没有.clone()电话。

除此之外,您还需要检查LLVM IR或汇编。