【问题标题】:What are the performance implications of consuming self and returning it?消费 self 并返回它对性能有什么影响?
【发布时间】:2018-01-12 12:07:22
【问题描述】:

我一直在阅读诸如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? 之类的问题,现在我很好奇消费 self 但可能会将其返回给调用者的性能特征。

举一个更简单的例子,假设我想创建一个保证非空的集合类型。为此,“删除”操作需要消耗集合并可选择返回自身。

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 建议将Options 存储在数据结构中,以允许调用者就地进行更改,而不是每次都在remove 后面跟着insert。这个肮脏的界面会是一个更快的选择吗?

【问题讨论】:

  • 任何类型的任何其他变量相比,self 没有什么特别之处。

标签: rust ownership


【解决方案1】:

YMMV

根据优化器的突发奇想,您最终可能会得到:

  • 接近无操作,
  • 一些寄存器移动,
  • 多个位副本。

这取决于是否:

  • 调用是否内联,
  • 调用者重新分配给原始变量或创建一个新变量(以及 LLVM 处理重用死区的能力),
  • size_of::&lt;Self&gt;()

您得到的唯一保证是不会发生深拷贝,因为没有.clone() 调用。

对于其他任何事情,您需要检查 LLVM IR 或程序集。

【讨论】:

  • 显然我正在寻找指导,而不是保证。在特定情况下,我会介绍;我只是想建立一种直觉,知道什么时候值得担心。
  • @DanHulme:在那种情况下,我会说这取决于size_of&lt;Self&gt;。对于小尺寸(几句话),即使没有优化,副本的成本也很小,不用担心。对于更大的尺寸,例如嵌入数组时,如果预期性能,我会比较保守并使用参考。
  • @DanHulme 相反,我主张 moving 总是可以接受的。 Rust 的语义取决于移动的概念——它甚至出现在 rust-lang.org 的首页!因此,任何“额外”的动作都很重要,应该从根本上解决。
  • @Shepmaster:如果你的 API 强制移动,而函数没有内联,你将在最终的二进制文件中移动,这不是 Rust 的错。
  • @DanHulme:如果性能很重要,我更愿意从编译器无法忽略这一举动的假设开始,因为没有任何保证。
猜你喜欢
  • 2012-05-31
  • 1970-01-01
  • 2020-03-22
  • 1970-01-01
  • 1970-01-01
  • 2017-02-27
  • 1970-01-01
  • 1970-01-01
  • 2018-11-15
相关资源
最近更新 更多