Назначение без копирования в стандартный ход - PullRequest
0 голосов
/ 09 января 2019

У меня есть функция util, которая возвращает карту

std::map getFooMap() {
  std::map foo;
  // ... populate the map
  return foo;
}

Со стороны вызывающей стороны я хочу назначить карту полю данных какого-либо объекта. Я могу сделать:

dest.data = getFooMap()

Будет ли это быстрее, чем следующее?

auto temp = getFooMap();
dest.data = std::move(temp);

Я думаю, что это должно быть, поскольку я избегаю одной дополнительной копии?

Ответы [ 2 ]

0 голосов
/ 22 января 2019

С эстетической точки зрения, имеет в качестве первого варианта. Вы должны иметь некоторую веру в языковой стандарт и авторов компилятора, чтобы не требовать глупого артефакта, такого как временный, для облегчения оптимизации здесь.

Давайте проверим, так ли это на самом деле. Во-первых, позвольте мне немного абстрагировать ваш пример и заменить map на класс foo, не определяя его различные методы; и предполагать, что ничего не выбрасывает исключения:

#include <utility>

// Originally this was an std::map, replaced with
// an "opaque" class to shorten the output and
//  prevent inlining and conflation of 
// std-map-related code with the rest of the code
struct foo {
    foo() noexcept;
    foo(const foo&) noexcept;
    foo(foo&&) noexcept;
    foo& operator=(foo&&) noexcept;
    foo& operator=(const foo&) noexcept;
    ~foo() noexcept;
};

struct dest_t { foo data; };

foo get_foo() noexcept;
void do_stuff_with(const dest_t& dest) noexcept;

void move_from_intermediate() noexcept {
    dest_t dest;
    auto temp = get_foo();
    dest.data = std::move(temp);
    do_stuff_with(dest);
}

void straight_assignment() noexcept {
    dest_t dest;
    dest.data = get_foo();
    do_stuff_with(dest);
}

Теперь, если мы скомпилируем это на GodBolt , мы увидим, что GCC (trunk) создает одинаковый код сборки для обеих функций. Это включает в себя:

  • 1 конструкция
  • 2 разрушения
  • 1 задание на перемещение
  • 1 звонок на get_foo()
  • 1 звонок на do_stuff_with()

в обоих случаях. clang (trunk) также выдаст тот же код, вплоть до небольшого переупорядочения, но не совсем того же кода, что и GCC.

0 голосов
/ 09 января 2019

Я думаю, что это должно быть, поскольку я избегаю одной дополнительной копии?

Пока "std::map" является подвижным, вы потенциально можете избежать только одного дополнительного движения - которого оптимизатор может избежать также.

Разница в производительности, вероятно, незначительна или отсутствует, но dest.data = getFooMap() проще и, скорее всего, не будет медленнее.

Как указано в комментарии, было бы еще быстрее напрямую инициализировать dest.data вместо назначения его после построения. Это может быть достигнуто путем вызова getFooMap в инициализаторе члена.

...