2014-08-27 4 views
62

Предположим, у меня есть следующий код:Является ли преждевременной оптимизацией использование std :: move()?

int main() 
{ 
    std::vector<std::string> strs; 

    std::string var("Hello World"); 
    // Make some modifications to 'var' 
    strs.push_back(std::move(var)); 
} 

Часть образца, я хочу отметить, является использование std::move(). В основном меня беспокоит копия на звонок push_back(). Предположим, что строка, которую я добавляю, действительно большая. Я все еще изучаю C++ 11 r-value-ссылки, поэтому я не уверен, как компилятор будет оптимизировать копию (если вообще) без std::move().

Может ли кто-нибудь объяснить, является ли это преждевременной оптимизацией (форсирование ходов во всех случаях, когда вы хотите избежать копирования вообще)? Если да, то какие шаблоны я должен ожидать, что компилятор будет следовать (или, скорее всего, следовать), что приведет к оптимизации и автоматическому перемещению здесь?

EDIT

Я хочу добавить, что я понимаю, как автоматические движения происходят на возвращении значения функции, поскольку применяется NRVO/РВО. Конкретный пример, который я привел здесь, не будет применяться RVO, поэтому я не уверен.

+0

'move' действительно имеет смысл только в том случае, если вызываемая функция называется * move aware * (и тип поддерживает перемещение семантики.) В этом случае функция, которую вы вызываете, отсутствует, и поэтому вы ничего не получите от этого , Если, однако, вы вызываете функцию * move know * (скажем, 'emplace_back()'), тогда имеет смысл использовать 'std :: move()'. Тот же принцип применяется к любым вашим пользовательским функциям, типам и т. Д. – Nim

+0

@Nim - это обязательно верно? Я рассматривал сценарий, в котором временное построено по ходу движения (из принудительного значения r, возвращаемого 'std :: move'), а затем это будет передано в push_back() как r-значение (const ref). Является ли этот сценарий неправильным? –

+7

@Nim С тех пор, как 'push_back' не перемещается? –

ответ

22

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

Вкратце: Нет, использование «трюка», такого как std :: move в способе, описанном в вопросе, не является преждевременной оптимизацией. Не использовать std::move, когда он может быть использован, тоже хорошо, если только код уже не известен как критически важный.

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

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

Связанный пример: Люди, работающие с критическим кодом производительности, часто передают аргументы как ссылки на const (const std::string&). Поскольку это то, что они используют, они будут использовать один и тот же шаблон в коде, который не является критичным для производительности, хотя они могут просто использовать pass-by-copy (const std::string или даже std::string). Это тоже не преждевременная оптимизация.

+9

+1: * С этой точки зрения я бы классифицировал любое длинное размышление о самом вопросе как о преждевременной оптимизации. * Если безоговорочно использовать быстрый подход проще чем определить, будет ли работать медленный подход, тогда, если у кого-то не будет оснований полагать, что некоторые теоретические преимущества медленного подхода могут быть важными, выяснение того, можно ли использовать медленный подход, чтобы оставить его открытым, его теоретические преимущества могут представлять собой другую форму " преждевременная оптимизация "[не для скорости, а для потенциальной гибкости]. – supercat

+1

В этом случае я согласен. Один из них против этого заключается в том, что переход от 'var' * может * сделать его несколько сложнее для читателей кода, чтобы рассуждать о состоянии' var'. То же самое относится ко всему, что мутирует «var», конечно, делая это через результат «move», не является особенным. Но если использование 'move' заставляет вас не отмечать какой-либо объект' const', который иначе мог бы быть, то вы могли бы попасть в область, где * возможность * ускорения меньше стоимости, чем хранение вещей концептуально просто (при условии неизменных значений проще, что, по моему утверждению, часто верно). –

+0

@SteveJessop Согласен. Хотя std :: move добавляет декларацию о намерениях и возможную оптимизацию, существует множество вариантов примера, где std :: move может добавить ненужную путаницу, сложность и/или риск. В конце концов, программирование r-значения является изученным навыком, как и правильное программирование. – Peter

20

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

Если вы знаете, что не собираетесь использовать var, и вы создали его только для того, чтобы положить его в vector, то это не действительно «преждевременная» оптимизация, как намерение того, что вы пытаетесь сделать.

vector имеет новый метод emplace_back(), который является тем же, но понятным, и использует форвардные аргументы (здесь вы просто делаете emplace_back("Hello World"), если все, что вы делаете, это его создание). В вашем случае, поскольку вы «вносите некоторые изменения с var», emplace_back вряд ли будет уместным.

В старой C++ вы можете оптимизировать копию, выполнив push_back() на пустой строке, а затем заменив ее.

+0

Перед тем, как был введен «emplace_back», как бы вы реализовали ту же функциональность, то есть измените размер и постройте на месте? Я предполагаю, что этот механизм отличается от 'std :: move'. – mostruash

+1

Компилятор должен знать, когда переменная больше не используется, особенно в этом простом случае, и просто вставьте 'move' или даже постройте строку непосредственно в' vector'. Поэтому можно утверждать, что эта микро-оптимизация не должна выполняться вручную. – nwp

+2

@nwp - Как мог компилятор сделать это в общем случае? _We_ знаете, как ведет себя 'string', но как мог компилятор узнать, что деструктор' var' не будет иметь другого поведения в зависимости от того, было ли это 'move'd? –

17

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

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

+1

Не знаете, почему у вас есть только мой взнос - это хороший ответ ИМХО. '// Сделаем некоторые изменения в« var'' части OP, делает 'emplace_back' бесполезным здесь. –

+0

@JonathanWakely: Но можно использовать 'emplace_back' для создания и добавления новой строки на месте, вызывать' back() ', чтобы получить ссылку на вновь созданную строку и затем ее модифицировать. –

+0

@ C.R: вы можете, хотя этот трюк часто усложняет для вашего кода реализацию сильной гарантии исключения при действии на внешний видимый вектор. Если модификация строки не удалась (в частности, броски), вам нужно отменить изменение 'emplace_back', которое вы уже сделали. Конечно, когда вектор локален, такой проблемы нет, но я думаю, что это означает, что люди склонны игнорировать трюк, а не беспокоиться о том, безопасно ли оно в контексте. На практике это менее опасно, чем люди могут подумать, так как использование 'something_back' обычно делает несколько таких, поэтому требует внимания уже. –

27

Я не уверен, как компилятор будет оптимизировать копию (если вообще) без std::move().

Только очень умный компилятор может оптимизировать, что далеко, так что, если копия может быть дорого (например, очень длинная строка), то лучше, чтобы переместить его.

Без переезда код эффективно последовательности звонков:

strlen // count "Hello World" 
malloc // allocate memory for string var 
strcpy // copy data into var 
malloc // re-allocate vector 
free // deallocate old vector 
malloc // allocate new string 
strcpy // copy from var to new string 
free // destroy var 

С ходом он становится:

strlen // count "Hello World" 
malloc // allocate memory for string var 
strcpy // copy data into var 
malloc // re-allocate vector 
free // deallocate old vector 

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

1

Да, это преждевременная оптимизация, если это преждевременная оптимизация.

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


Теперь о целесообразности оптимизации использования std::move():

С std::move() вам избежать поднятия тяжестей строительства копии, как распределение памяти, открепление, копии построения содержат элементы, деконструкции содержали элементы и т.д. Это хороший. Но одна часть решает: строительство/уничтожение контейнерного объекта.

emplace_back() имеет силу полностью избежать строительства временного объекта. Итак, всякий раз, когда вы можете избежать временного объекта, используя emplace_back(), у вас есть небольшая победа.

Учитывая качество кода (читаемость/ремонтопригодность): std::move() имеет недостаток, оставляя вас непригодным для использования объектом, который может быть источником ошибок. Этого не происходит с emplace_back(). Поэтому, опять же, более очевидно, что предпочтительнее.

Конечно, emplace_back() не может использоваться во всех контекстах, позволяющих использовать std::move(). Таким образом, если они критичны по производительности, std::move() может быть путем. Существуют некоторые очень эффективные варианты использования для оптимизации с std::move(). Тем не менее, вы также можете узнать, что вы можете написать код таким образом, чтобы не требовать каких-либо конструкций copy/move/emplace. Никогда не переставайте искать оптимальное решение при оптимизации!

В конце концов, остается, что правильность оптимизации с помощью std::move() полностью зависит от контекста: хорошая оптимизация везде, где это хорошая оптимизация.

+7

Тавтология - это всего лишь шум. – nwp

+0

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

+0

vector - это явный случай преждевременной оптимизации , пытаясь оптимизировать использование памяти. – CashCow

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