2015-09-10 2 views
210

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

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

См. Http://herbsutter.com/2013/06/13/gotw-93-solution-auto-variables-part-2/, в котором говорится об избежании случайных неявных преобразований, например. от гаджета до виджета. Это не обычная проблема. –

+41

Вы принимаете «делает его менее вероятным для непреднамеренного пессимизма» как улучшение производительности? – 5gon12eder

+1

Перенос кода очистки только в будущем, возможно – Croll

ответ

281

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 
} 

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

+16

@Barry Можете ли вы объяснить, почему компилятор с радостью сделает копии вместо того, чтобы жаловаться на попытку обработать 'std :: pair const &' as 'std :: пара const &'? Новый для C++ 11, не уверенный, как в нем задействованы диапазоны и 'auto'. – Agop

+0

@Barry Спасибо за объяснение. Это то, что мне не хватало - по какой-то причине я думал, что ты не мог бы постоянно ссылаться на временный. Но, конечно, вы можете - он просто перестанет существовать в конце своей сферы. – Agop

+0

@barry Я вас понимаю, но проблема в том, что тогда нет ответа, который охватывает все причины использования 'auto', которые увеличивают производительность. Поэтому я напишу это в своих словах ниже. – Yakk

67

Потому что 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.)

+0

Так что это «избежать стирания стирания», чтобы использовать 'auto'. Ваш другой вариант - «избегать случайных копий», но нуждается в приукрашивании; почему 'auto' дает вам скорость, просто набирая там тип? (Я думаю, что ответ: «вы ошибаетесь, и он бесшумно преобразует»), что делает его менее обоснованным примером ответа Барри, нет? То есть, есть два основных случая: авто, чтобы избежать стирания стилей, и авто, чтобы избежать бесшумных ошибок типа, которые случайно конвертируются, причем оба из них имеют затраты времени выполнения. – Yakk

+2

«не только призыв не встраивается» - почему это так? Вы имеете в виду, что в принципе что-то предотвращает девиртуализацию звонка после анализа потока данных, если все специализации 'std :: bind',' std :: function' и 'std :: stable_partition' были полностью встроены? Или просто на практике компилятор C++ не будет достаточно агрессивным, чтобы разобраться в беспорядке? –

+0

@SteveJessop: В основном последнее - после того, как вы пройдете через конструктор 'std :: function', будет очень сложно увидеть фактический вызов, особенно при оптимизации небольших функций (так что вы фактически не хотите девиртуализации) , Конечно * в принципе * все как-будто ... –

35

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

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& не std::pair<A,B> const&, но ошибка является категорией ошибок, которые бесшумно оценивают стоимость. Вы можете построить std::pair<A, B> от std::pair<const A, B>. (Ключ на карте const, потому что редактирование - плохая идея)

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

7

В существующих трех ответах приведены примеры, в которых использование auto помогает “makes it less likely to unintentionally pessimize” эффективно, что делает его «улучшающим производительность».

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

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

+0

Добавлен противоположный вопрос: http://stackoverflow.com/questions/38415831/can-the-use-of-c11s-auto-deteriorate-performance-or-even-break-the-code – Leon

Смежные вопросы