Может ли использование C + 11 "авто" повысить производительность?

Я вижу, почему тип auto в С++ 11 улучшает правильность и ремонтопригодность. Я читал, что он также может улучшить производительность (Почти всегда Auto от Herb Sutter), но я пропустил хорошее объяснение.

  • Как auto повысить производительность?
  • Может ли кто-нибудь привести пример?

Ответ 1

auto может помочь производительности избежать молчащих неявных преобразований. Примером, который я считаю убедительным, является следующее.

std::map<Key, Val> m;
// ...

for (std::pair<Key, Val> const& item : m) {
    // do stuff
}

Посмотрите на ошибку? Здесь мы находимся, думая, что мы элегантно берем каждый элемент на карте по ссылке const и используем новое выражение для выражения, чтобы сделать наше намерение понятным, но на самом деле мы копируем каждый элемент. Это связано с тем, что std::map<Key, Val>::value_type - std::pair<const Key, Val>, а не std::pair<Key, Val>. Таким образом, когда мы (неявно) имеем:

std::pair<Key, Val> const& item = *iter;

Вместо того, чтобы ссылаться на существующий объект и оставлять его на этом, мы должны сделать преобразование типа. Вы можете использовать константную ссылку на объект (или временный) другого типа, если существует неявное преобразование, например:

int const& i = 2.0; // perfectly OK

Преобразование типа является разрешенным неявным преобразованием по той же причине, что вы можете преобразовать const Key в Key, но мы должны создать временный тип нового типа, чтобы это разрешить. Таким образом, эффективно наш цикл:

std::pair<Key, Val> __tmp = *iter;       // construct a temporary of the correct type
std::pair<Key, Val> const& item = __tmp; // then, take a reference to it

(Конечно, на самом деле нет объекта __tmp, он просто для иллюстрации, на самом деле неназванное временное ограничение связано только с item за время его существования).

Просто измените на:

for (auto const& item : m) {
    // do stuff
}

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

Ответ 2

Потому что auto выводит тип инициализирующего выражения, нет преобразования типа. В сочетании с шаблонами алгоритмов это означает, что вы можете получить более прямое вычисление, чем если бы вы сами составили тип – особенно когда вы имеете дело с выражениями, тип которых вы не можете назвать!

Типичный пример исходит из (ab) с помощью std::function:

std::function<bool(T, T)> cmp1 = std::bind(f, _2, 10, _1);  // bad
auto cmp2 = std::bind(f, _2, 10, _1);                       // good
auto cmp3 = [](T a, T b){ return f(b, 10, a); };            // also good

std::stable_partition(begin(x), end(x), cmp?);

С cmp2 и cmp3 весь алгоритм может встроить вызов сравнения, тогда как если вы построите объект std::function, не только вызов не будет вложен, но вам также придется пройти через полиморфный поиск в стираемой по типу внутренней оболочке функции.

Другой вариант этой темы - вы можете сказать:

auto && f = MakeAThing();

Это всегда ссылка, связанная со значением выражения вызова функции и никогда не создающая никаких дополнительных объектов. Если вы не знаете тип возвращаемого значения, вам может потребоваться построить новый объект (возможно, временно) через нечто вроде T && f = MakeAThing(). (Более того, auto && работает даже тогда, когда тип возврата не движется, а возвращаемое значение является значением prvalue.)

Ответ 3

Существуют две категории.

auto может избежать стирания стилей. Существуют неизмеримые типы (например, lambdas) и почти неуязвимые типы (например, результат std::bind или другие выражения-шаблоны, подобные вещам).

Без auto вам придется вводить данные для стирания, например, std::function. У стирания типа есть затраты.

std::function<void()> task1 = []{std::cout << "hello";};
auto task2 = []{std::cout << " world\n";};

task1 имеет накладные расходы стираемого типа - возможное распределение кучи, сложность его вложения и служебные вызовы таблицы виртуальных функций. task2 нет. Lambdas необходимо автоматическое или другие формы вывода типа для хранения без стирания типа; другие типы могут быть настолько сложными, что им это нужно только на практике.

Во-вторых, вы можете ошибаться. В некоторых случаях неправильный тип будет работать, казалось бы, отлично, но приведет к копированию.

Foo const& f = expression();

будет компилироваться, если expression() возвращает Bar const& или Bar или даже Bar&, где Foo можно построить из Bar. Будет создан временный Foo, затем привязан к f, и его время жизни будет расширено до тех пор, пока f не исчезнет.

Программист, возможно, имел в виду Bar const& f и не предназначен для копирования там, но копия выполняется независимо.

Наиболее распространенным примером является тип *std::map<A,B>::const_iterator, который является std::pair<A const, B> const& not std::pair<A,B> const&, но ошибка является категорией ошибок, которые бесшумно оценивают стоимость. Вы можете построить std::pair<A, B> из std::pair<const A, B>. (Ключ на карте const, потому что редактирование это плохая идея)

Оба @Barry и @KerrekSB впервые проиллюстрировали эти два принципа в своих ответах. Это просто попытка выделить два вопроса в одном ответе, с формулировкой, которая направлена ​​на проблему, а не на пример-ориентированную.

Ответ 4

В существующих трех ответах приводятся примеры, в которых использование auto помогает "делает менее вероятным непреднамеренное пессимизацию" , что делает его "улучшающим производительность".

С монеткой стоит оборотная сторона. Использование auto с объектами, которые имеют операторы, которые не возвращают базовый объект, может привести к некорректному (все еще компилируемому и исполняемому) коду. Например, этот вопрос спрашивает, как использование auto дало разные (неверные) результаты с использованием библиотеки Eigen, то есть следующие строки

const auto    resAuto    = Ha + Vector3(0.,0.,j * 2.567);
const Vector3 resVector3 = Ha + Vector3(0.,0.,j * 2.567);

std::cout << "resAuto = " << resAuto <<std::endl;
std::cout << "resVector3 = " << resVector3 <<std::endl;

привел к разным результатам. По общему признанию, это в основном связано с ленивой оценкой Eigens, но этот код должен/должен быть прозрачным для пользователя (библиотеки).

В то время как производительность здесь не сильно затронута, использование auto во избежание непреднамеренной пессимизации может быть классифицировано как преждевременная оптимизация или, по крайней мере, неправильная;).