Может ли современный C++ получить вам производительность бесплатно?


иногда утверждается, что C++11/14 может повысить производительность даже при простой компиляции кода C++98. Обоснование обычно выполняется в соответствии с семантикой перемещения, так как в некоторых случаях конструкторы rvalue автоматически генерируются или теперь являются частью STL. Теперь мне интересно, были ли эти случаи ранее уже обработаны RVO или аналогичными оптимизациями компилятора.

мой вопрос тогда, если бы вы могли дать мне фактический пример части C++98 код, который без изменений работает быстрее с помощью компилятора, поддерживающего новые функции языка. Я понимаю, что стандартный соответствующий компилятор не требуется для выполнения копирования elision и только по этой причине семантика перемещения может привести к скорости, но я хотел бы увидеть менее патологический случай, если хотите.

EDIT: просто чтобы быть ясным, я не спрашиваю, являются ли новые компиляторы быстрее старых компиляторов, а скорее, если есть код, в котором добавление-std=c++14 к моим флагам компилятора он будет работать быстрее (избегайте копий, но если вы можете придумать что-нибудь еще, кроме семантики перемещения, мне тоже будет интересно)

2 196

2 ответа:

я знаю 5 общих категорий, где перекомпиляция компилятора C++03 Как C++11 может привести к неограниченному увеличению производительности, которые практически не связаны с качеством реализации. Это все вариации семантики перемещения.

std::vector перераспределять

struct bar{
  std::vector<int> data;
};
std::vector<bar> foo(1);
foo.back().data.push_back(3);
foo.reserve(10); // two allocations and a delete occur in C++03

каждый раз fooбуфер перераспределяется в C++03 он копируется каждый vector на bar.

в C++11 он вместо этого перемещает bar::datas, который в основном бесплатный.

в этом случае это зависит от оптимизации внутри std контейнер vector. В каждом случае ниже, использование std контейнеры просто потому, что они являются объектами C++, которые имеют эффективный move семантика в C++11 "автоматически" при обновлении компилятора. Объекты, которые не блокируют его, которые содержат std контейнер также наследует автоматическое улучшение move конструкторы.

NRVO отказ

когда NRVO (по имени return оптимизация значения) не удается, в C++03 он возвращается на копию, на C++11 он возвращается на ход. Неудачи НРВО легки:

std::vector<int> foo(int count){
  std::vector<int> v; // oops
  if (count<=0) return std::vector<int>();
  v.reserve(count);
  for(int i=0;i<count;++i)
    v.push_back(i);
  return v;
}

или еще:

std::vector<int> foo(bool which) {
  std::vector<int> a, b;
  // do work, filling a and b, using the other for calculations
  if (which)
    return a;
  else
    return b;
}

у нас есть три значения -- возвращаемое значение и два разных значения внутри функции. Elision позволяет объединить значения внутри функции с возвращаемым значением, но не друг с другом. Они оба не могут быть объединены с возвращаемым значением без слияния друг с другом.

базовый проблема в том, что nrvo elision является хрупким, а код с изменениями не рядом с return место может внезапно иметь массивные уменьшения представления на том пятне без испущенной диагностики. В большинстве случаев отказа NRVO C++11 заканчивается на move, в то время как C++03 заканчивается копией.

возврат аргумента функции

Элизия здесь тоже невозможна:

std::set<int> func(std::set<int> in){
  return in;
}

в C++11 это дешево: в C++03 нет способа избежать копирования. Аргументы для функций невозможно удалить возвращаемое значение, поскольку время жизни и расположение параметра и возвращаемого значения управляются вызывающим кодом.

однако, C++11 может переходить от одного к другому. (В менее игрушечном примере что-то может быть сделано с set).

push_back или insert

наконец elision в контейнеры не происходит: но C++11 перегружает операторы вставки перемещения rvalue, что сохраняет копии.

struct whatever {
  std::string data;
  int count;
  whatever( std::string d, int c ):data(d), count(c) {}
};
std::vector<whatever> v;
v.push_back( whatever("some long string goes here", 3) );

в C++03 a временно whatever создается, затем копируется в вектор v. 2 std::string буферы выделяются, каждый с идентичными данными, и один отбрасывается.

в C++11 временный это. Элемент whatever&&push_back тонкости move s, что временно в вектор v. Один std::string буфер выделяется и перемещается в вектор. Пустой std::string отбрасывается.

задание

украдено из ответа @Jarod42 под.

Elision не может произойти с назначением, но move-from может.

std::set<int> some_function();

std::set<int> some_value;

// code

some_value = some_function();

здесь some_function возвращает кандидата в elide from, но поскольку он не используется для непосредственного построения объекта, он не может быть elide. В C++03 вышеприведенные результаты приводят к тому, что содержимое временного копируется в some_value. В C++11 он перемещается в some_value, который в основном является бесплатным.


для полного эффекта выше, вам нужен компилятор, который синтезирует перемещение конструкторов и назначение для вас.

MSVC 2013 реализует перемещение конструкторов в std контейнеры, но не синтезирует конструкторы перемещения для ваших типов.

так, типа, содержащие std::vectors и подобные не получают таких улучшений в MSVC2013, но начнут получать их в MSVC2015.

clang и gcc уже давно реализовали неявные конструкторы перемещения. Компилятор Intel 2013 будет поддерживать неявную генерацию конструкторов перемещения, если вы пас -Qoption,cpp,--gen_move_operations (они не делают это по умолчанию, чтобы быть кросс-совместимыми с MSVC2013).

если у вас есть что-то вроде:

std::vector<int> foo(); // function declaration.
std::vector<int> v;

// some code

v = foo();

вы получили копию в C++03, В то время как вы получили назначение перемещения в C++11. таким образом, у вас есть бесплатная оптимизация в этом случае.