我能否在Rust中高效地通过值返回一个对象?

16

我想用一个函数来初始化一个大对象。目前我有:

fn initialize(mydata: &mut Vec<Vec<MyStruct>>) { /* ... */ }

我更喜欢有:

fn initialize() -> Vec<Vec<MyStruct>> { /* ... */ }

我听说C++经常实现返回值优化(RVO),如果你运气好并拥有一个好的编译器。我们能不能在这里禁用复制,并通过传递到函数中的隐藏指针返回它?RVO是语言的一部分还是可选优化?


这篇讨论表明,RVO在大于指针的情况下会被触发。但是也有一个关于NRVO的未解决的问题。因此,我猜这取决于你如何实现initialize函数。 - Michael
1个回答

23

没错,无论如何,你都应该写下来。

fn initialize() -> Vec<Vec<MyStruct>> { ... }

顺便说一下,Vec 不是很大——只有 3 个指针大小的整数。

Rust 具有 RVO,并且在 指南中进行了宣传。你可以使用以下代码自己查看:

#[inline(never)]
fn initialize() -> Vec<i32> {
    Vec::new()
}

fn main() {
    let v = initialize();
}

如果您在Playground上以发布模式编译此程序并输出汇编码,除其他所有内容外,您将看到以下信息:

playground::initialize:
    movq    $4, (%rdi)
    xorps   %xmm0, %xmm0
    movups  %xmm0, 8(%rdi)
    retq

Vec::new()被内联了,但是你可以看出其思想 - 新的Vec实例的地址通过%rdi传递到函数中,函数直接将Vec字段存储到该内存中,避免了不必要的堆栈复制。这是调用方式:

playground::main:
    subq    $24, %rsp
    movq    %rsp, %rdi
    callq   playground::initialize

您可以看到最终Vec实例将直接放入堆栈内存中。


可能吧,但是我发现对于某些原因,LLVM IR 的阅读比汇编要困难得多 :( - Vladimir Matveev
2
为此,我们只需要签名:define internal fastcc void @initialize(%"struct.collections::vec::Vec<[i32]>[#3]"* noalias nocapture sret dereferenceable(24)) unnamed_addr #0。LLVM IR对于声明类似于C语言,因此该函数返回void并且取一个指针(*)到一个 struct.collections::vec::Vec<[i32]>。(我使用#[no_mangle]使其更清晰。) - huon
1
这可能显示传递指针,但如果您查看LLVM IR的末尾,您可能会看到将数据块复制到该内存块或类似操作。我编写了一段代码,它分配了一个256个整数数组以返回结果,如果您查看IR底部,您会看到从一个对象复制到另一个对象的过程。查看生成的ASM(在play.r-l.org上的beta和nightly版本),您会看到在函数调用的底部调用mempcy@PLT。http://is.gd/u9GVx6 - JasonN

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接