Могу ли я эффективно вернуть объект по значению в Rust?

Я хочу инициализировать большой объект функцией. На данный момент у меня есть:

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

Я бы предпочел:

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

Я слышал, что C ++ часто реализует оптимизацию возвращаемого значения (RVO), если вам повезет и у вас есть хороший компилятор. Можем ли мы отключить здесь копирование и вернуть его с помощью скрытого указателя, который передается в функцию? RVO - это часть языка или дополнительная оптимизация?


person stevenkucera    schedule 08.01.2015    source источник
comment
В этом обсуждении предполагается, что RVO срабатывает для чего-либо большего, чем указатель. . Но есть также этот открытый вопрос, касающийся NRVO. Думаю, это зависит от того, как именно вы реализуете initialize.   -  person Michael    schedule 08.01.2015


Ответы (1)


Да, непременно нужно написать

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

(Кстати, Vec не такой большой - это всего 3 целых числа размером с указатель)

В Rust есть RVO, и это рекламируется в руководствах. Вы можете убедиться в этом сами с помощью этого кода:

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

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

Если вы скомпилируете эту программу в режиме выпуска на игровой площадке, вывод сборки, среди всего прочего вы увидите это:

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 будет помещен прямо в память стека.

person Vladimir Matveev    schedule 08.01.2015
comment
Наверное, но мне почему-то LLVM IR читается намного сложнее сборки :( - person Vladimir Matveev; 08.01.2015
comment
Для этого нам понадобится подпись: 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], чтобы было понятнее.) - person huon; 08.01.2015
comment
Это может означать передачу указателя, но если вы посмотрите на конец LLVM IR, вы можете увидеть memcpy в этот блок памяти или что-то подобное. У меня есть фрагмент кода, который я написал, который выделяет массив размером 256 int для возврата, и если вы посмотрите на нижнюю часть IR, вы увидите копию от одного объекта к другому. Глядя на сгенерированный ASM (как бета-версию, так и каждую ночь на play.r-l.org), вы видите вызов mempcy @ PLT внизу вызова функции. is.gd/u9GVx6 - person JasonN; 08.07.2015