«Преждевременная оптимизация - корень всего зла»
Я думаю, что с этим мы все можем согласиться. И я очень стараюсь избегать этого.
Но в последнее время меня интересует практика передачи параметров по константной ссылке, а не по значению . Меня учили / учили, что аргументы нетривиальных функций (то есть большинство не примитивных типов) желательно передавать по константной ссылке - довольно много книг, которые я прочитал, рекомендуют это как «лучшую практику».
Тем не менее я не могу не задаться вопросом: современные компиляторы и новые языковые функции могут творить чудеса, поэтому знания, которые я выучил, вполне могут быть устаревшими, и я никогда не удосужился описать, есть ли различия в производительности между
void fooByValue(SomeDataStruct data);
и
void fooByReference(const SomeDataStruct& data);
Является ли практика, которую я изучил - передача константных ссылок (по умолчанию для нетривиальных типов) - преждевременная оптимизация?
источник
Ответы:
«Преждевременная оптимизация» - это не использование оптимизаций на ранней стадии . Речь идет об оптимизации до понимания проблемы, до понимания времени выполнения, и часто делает код менее читаемым и менее поддерживаемым для сомнительных результатов.
Использование «const &» вместо передачи объекта по значению является хорошо понятной оптимизацией, с хорошо понятными эффектами на время выполнения, практически без усилий и без каких-либо отрицательных последствий для читабельности и удобства сопровождения. Это на самом деле улучшает оба, потому что говорит мне, что вызов не изменит передаваемый объект. Поэтому добавление «const &» прямо при написании кода НЕ ПРЕДВАРИТЕЛЬНО.
источник
const&
, поэтому я думаю, что вопрос довольно разумный.const& foo
говорит, что функция не будет модифицировать foo, поэтому вызывающая сторона безопасна. Но скопированное значение говорит, что никакой другой поток не может изменить foo, поэтому вызываемый объект безопасен. Правильно? Таким образом, в многопоточном приложении ответ зависит от правильности, а не от оптимизации.TL; DR: передача по константной ссылке все еще хорошая идея в C ++, учитывая все обстоятельства. Не преждевременная оптимизация.
TL; DR2: большинство пословиц не имеет смысла, пока они не делают.
цель
Этот ответ просто пытается немного расширить связанный элемент в C ++ Core Guidelines (впервые упоминается в комментарии amon).
Этот ответ не пытается решить вопрос о том, как правильно мыслить и применять различные пословицы, которые широко распространялись в кругах программистов, особенно вопрос о согласовании противоречивых выводов или доказательств.
применимость
Этот ответ применяется только к вызовам функций (не отсоединяемые вложенные области в одном потоке).
(Примечание.) Когда проходимые вещи могут покинуть область действия (т. Е. Иметь время жизни, которое потенциально может превышать внешнюю область), становится более важным удовлетворение потребности приложения в управлении временем жизни объекта, прежде чем что-либо еще. Обычно это требует использования ссылок, которые также способны управлять временем жизни, таких как умные указатели. Альтернативой может быть использование менеджера. Обратите внимание, что лямбда - это своего рода отрезная область; Лямбда-захваты ведут себя так, как будто имеют объем объекта. Поэтому будьте осторожны с лямбда-захватами. Также будьте осторожны с тем, как передается сама лямбда - копией или ссылкой.
Когда передавать по значению
Для значений, которые являются скалярными (стандартные примитивы, которые вписываются в машинный регистр и имеют значение семантическое), для которых нет необходимости в связи по изменчивости (совместно используемая ссылка), передайте по значению.
В ситуациях, когда вызываемый объект требует клонирования объекта или агрегата, передайте по значению, в котором копия вызываемого объекта удовлетворяет потребность в клонированном объекте.
Когда переходить по ссылке и т. Д.
во всех остальных ситуациях проходите мимо указателей, ссылок, умных указателей, дескрипторов (см .: идиома «тело-дескриптор») и т. д. Когда бы ни следовал этому совету, применяйте принцип правильной константности как обычно.
Вещи (агрегаты, объекты, массивы, структуры данных), которые занимают достаточно много места в памяти, всегда должны быть спроектированы для облегчения передачи по ссылке по соображениям производительности. Этот совет определенно применяется, когда он составляет сотни или более байтов. Этот совет является пограничным, когда он составляет десятки байтов.
Необычные парадигмы
Существуют специальные парадигмы программирования, которые намеренно копируют. Например, обработка строк, сериализация, сетевое взаимодействие, изоляция, упаковка сторонних библиотек, межпроцессное взаимодействие с разделяемой памятью и т. Д. В этих прикладных областях или парадигмах программирования данные копируются из структур в структуры или иногда перепаковываются в байтовые массивы.
Как спецификация языка влияет на этот ответ, прежде чем рассматривается вопрос оптимизации.
Sub-TL; DR Распространение ссылки не должно вызывать код; передача по const-ссылке удовлетворяет этому критерию. Однако все остальные языки удовлетворяют этому критерию без особых усилий.
(Начинающим программистам на C ++ рекомендуется полностью пропустить этот раздел.)
(Начало этого раздела частично навеяно ответом gnasher729. Однако, другой вывод сделан.)
C ++ допускает определяемые пользователем конструкторы копирования и операторы присваивания.
(Это (был) смелый выбор, который был (был) и удивительным, и прискорбным. Это определенно отклонение от сегодняшней приемлемой нормы в языковом дизайне.)
Даже если программист C ++ не определяет его, компилятор C ++ должен сгенерировать такие методы на основе принципов языка, а затем определить, нужно ли выполнять дополнительный код, кроме
memcpy
. Например,class
/,struct
который содержитstd::vector
член, должен иметь конструктор копирования и нетривиальный оператор присваивания.В других языках конструкторы копирования и клонирование объектов не рекомендуется (за исключением случаев, когда это абсолютно необходимо и / или имеет смысл для семантики приложения), поскольку объекты имеют ссылочную семантику в соответствии с языковым дизайном. Эти языки обычно имеют механизм сборки мусора, основанный на достижимости вместо владения на основе области или подсчета ссылок.
Когда в C ++ (или C) передается ссылка или указатель (включая константную ссылку), программисту гарантируется, что никакой специальный код (пользовательские или сгенерированные компилятором функции) не будет выполнен, кроме распространения значения адреса (ссылка или указатель). Это - ясность поведения, с которым программисты на C ++ чувствуют себя комфортно.
Однако фоном является то, что язык C ++ неоправданно сложен, так что эта ясность поведения похожа на оазис (живую среду обитания) где-то вокруг зоны радиоактивного выпадения.
Чтобы добавить больше благословений (или оскорблений), C ++ вводит универсальные ссылки (r-значения), чтобы упростить пользовательские операторы перемещения (конструкторы перемещения и операторы назначения перемещения) с хорошей производительностью. Это выгодно для весьма актуального варианта использования (перемещение (перенос) объектов из одного экземпляра в другой) за счет уменьшения потребности в копировании и глубоком клонировании. Однако на других языках нелогично говорить о таком перемещении объектов.
(Не по теме) Раздел, посвященный статье «Хотите скорость? Передайте по значению!» написано около 2009 года.
Эта статья была написана в 2009 году и объясняет обоснование дизайна для r-значения в C ++. Эта статья представляет собой действительный контраргумент моего заключения в предыдущем разделе. Однако пример кода статьи и претензия к производительности уже давно опровергнуты.
Sub-TL; DR Конструкция семантики r-значения в C ++ допускает удивительно элегантную семантику на стороне пользователя
Sort
, например, для функции. Этот элегантный невозможно моделировать (подражать) на других языках.Функция сортировки применяется ко всей структуре данных. Как упомянуто выше, было бы медленно, если бы много копировалось. Как оптимизация производительности (это практически актуально), функция сортировки разработана так, чтобы быть разрушительной во многих языках, кроме C ++. Деструктивный означает, что целевая структура данных модифицируется для достижения цели сортировки.
В C ++ пользователь может выбрать вызов одной из двух реализаций: деструктивной с лучшей производительностью или обычной, которая не изменяет ввод. (Шаблон опущен для краткости.)
Помимо сортировки, эта элегантность также полезна при реализации алгоритма деструктивного поиска медианы в массиве (изначально не отсортированного) путем рекурсивного разбиения.
Однако обратите внимание, что в большинстве языков для сортировки применяется подход сбалансированного бинарного дерева поиска вместо применения алгоритма деструктивной сортировки к массивам. Поэтому практическая значимость этого метода не так высока, как кажется.
Как оптимизация компилятора влияет на этот ответ
Когда встраивание (а также оптимизация всей программы / оптимизация времени соединения) применяется к нескольким уровням вызовов функций, компилятор может видеть (иногда исчерпывающе) поток данных. Когда это происходит, компилятор может применить много оптимизаций, некоторые из которых могут исключить создание целых объектов в памяти. Как правило, когда эта ситуация применима, не имеет значения, переданы ли параметры по значению или по const-ссылке, потому что компилятор может провести тщательный анализ.
Однако, если функция более низкого уровня вызывает что-то, что находится за пределами анализа (например, что-то из другой библиотеки вне компиляции или граф вызовов просто слишком сложен), то компилятор должен оптимизировать защиту.
Объекты, размер которых превышает значение регистра компьютера, могут быть скопированы с помощью явных инструкций загрузки / сохранения памяти или путем вызова уважаемой
memcpy
функции. На некоторых платформах компилятор генерирует SIMD-инструкции для перемещения между двумя ячейками памяти, каждая из которых перемещается на десятки байтов (16 или 32).Обсуждение вопроса о многословии или визуальном беспорядке
Программисты C ++ привыкли к этому, то есть, если программист не ненавидит C ++, накладные расходы по написанию или чтению const-ссылки в исходном коде не являются ужасными.
Анализ затрат и выгод, возможно, проводился много раз раньше. Я не знаю, есть ли какие-нибудь научные, на которые следует ссылаться. Я думаю, что большинство анализов были бы ненаучными или невоспроизводимыми.
Вот что я представляю (без доказательств или достоверных ссылок) ...
источник
Это не советует программистам использовать самые медленные из доступных методов. Речь идет о четкости при написании программ. Часто ясность и эффективность являются компромиссом: если вам нужно выбрать только один, выберите ясность. Но если вы можете легко достичь и того, и другого, вам не нужно наносить ущерб ясности (например, сигнализировать, что что-то является константой), просто чтобы избежать эффективности.
источник
Передача ([const] [rvalue] ссылка) | (значение) должна быть о намерении и обещаниях, сделанных интерфейсом. Это не имеет ничего общего с производительностью.
Правило большого пальца Ричи:
источник
Теоретически, ответ должен быть да. И действительно, это да, иногда - фактически, передача по константной ссылке вместо простой передачи значения может быть пессимизацией даже в тех случаях, когда переданное значение слишком велико, чтобы поместиться в одном зарегистрироваться (или большинство других эвристиков, которые пытаются использовать, чтобы определить, когда передавать по значению или нет). Несколько лет назад Дэвид Абрахамс написал статью под названием «Хотите скорость? Передайте по значению!» покрывая некоторые из этих случаев. Это уже не легко найти, но если вы можете выкопать копию, ее стоит прочитать (IMO).
В конкретном случае перехода по ссылке сопзЬ, однако, я бы сказал , что идиомы настолько хорошо установлена , что ситуация более или менее наоборот: если вы не знаете типа будет
char
/short
/int
/long
, люди ожидают увидеть , что прошло по константному ссылка по умолчанию, так что, вероятно, лучше согласиться с этим, если у вас нет достаточно конкретной причины поступить иначе.источник