Может ли использование 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
во избежание непреднамеренной пессимизации может быть классифицировано как преждевременная оптимизация или, по крайней мере, неправильная;).