Подтвердить что ты не робот

Правило большого пальца при прохождении по значению быстрее, чем передача по ссылке const?

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

void foo(T t){ ... }
void foo(const T& t){ ... }

Существует ли эмпирическое правило о том, насколько большой T должен быть до того, как передача по ссылке const станет дешевле, чем передача по значению? Например, предположим, что sizeof(T) == 24. Должен ли я использовать константу-ссылку или значение?

Я предполагаю, что конструктор копирования T тривиален. В противном случае ответ на вопрос зависит от сложности конструктора копирования, конечно.

Я уже искал похожие вопросы и наткнулся на это:

шаблон проходит по значению или константной ссылке или...?

Однако принятый ответ ( qaru.site/info/171147/...) не содержит никаких подробностей, он просто указывает:

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

Так что это не решает мой вопрос, а скорее перефразирует его: насколько мал должен быть тип "очень дешевым для копирования"?

4b9b3361

Ответ 1

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

Тип дешево копировать, если у него нет конструктора с фанкой-копией, а его sizeof мал. Нет большого количества "малых", что оптимально, даже на основе каждой платформы, поскольку оно очень сильно зависит от вызывающего кода и самой функции. Просто иди своим чувством кишки. Один, два, три слова малы. Десять, кто знает. Матрица 4x4 невелика.

Ответ 2

Передача значения вместо ссылки на константу имеет то преимущество, что компилятор знает, что значение не изменится. "const int & x" не означает, что значение не может измениться; это означает, что ваш код не может изменить его, используя идентификатор x (без какого-либо броска, который заметил бы компилятор). Возьмите этот ужасный, но вполне законный пример:

static int someValue;

void g (int i)
{
    --someValue;
}

void f (const int& x)
{
    for (int i = 0; i < x; ++i)
        g (i);
}

int main (void)
{
    someValue = 100;
    f (someValue);
    return 0;
}

Внутренняя функция f, x на самом деле не постоянна! Он изменяется каждый раз, когда вызывается g (i), поэтому цикл работает только от 0 до 49! И поскольку компилятор вообще не знает, написал ли вы такой ужасный код, он должен предположить, что x может измениться при вызове g. В результате вы можете ожидать, что код будет медленнее, чем если бы вы использовали "int x".

То же самое верно для многих объектов, которые могут быть переданы по ссылке. Например, если вы передаете объект const и amp;, и у объекта есть член int или unsigned int, тогда любое присваивание с использованием char *, int * или unsigned int * может изменить этот член, если только компилятор может доказать обратное. Передано по значению, доказательство намного проще для компилятора.

Ответ 3

Самое подходящее эмпирическое правило, на мой взгляд, проходит по ссылке, когда:

sizeof(T) >= sizeof(T*)

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

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

Кроме того, если вы не заботитесь о микрооптимизации, вы можете передать все по ссылке const, на большинстве указателей на машины - 4 или 8 байт, очень мало типов меньше, и даже в этом случае вы потеряете несколько (меньше чем 8) операция копирования байтов и некоторые косвенные действия, которые в современном мире, скорее всего, не станут вашим узким местом:)

Ответ 4

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

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

Я считаю, что этот подход поможет мне сосредоточиться на самой важной части моего программного обеспечения: правильности. И я бы не стал на пути компилятора --- вмешиваться; запретить --- выполнять оптимизацию (я знаю, что не могу победить).

Сказав это, номинально ссылки выполняются как указатели. Таким образом, в вакцине, не рассматривая семантику, копирование, перемещение семантики и т.д., Было бы более "эффективно" передавать по указателю/ссылке все, размер которых больше, чем указатель.

Ответ 5

Для абстрактного "Т" в абстрактном "C++" эмпирическое правило будет состоять в том, чтобы использовать способ, который лучше отражает намерение, которое для аргумента, который не изменяется, почти всегда "проходит по значению". Кроме того, конкретные компиляторы реального мира ожидают такого абстрактного описания и собираются передать ваш T самым эффективным способом, независимо от того, как вы это делаете в источнике.

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

Ответ 6

Если вы собираетесь использовать "правило большого пальца" для сравнения по сравнению с by-const-ссылкой, сделайте следующее:

  • выберите ОДИН подход и используйте его везде
  • согласитесь, какой из них среди ваших коллег
  • только позже, на этапе "настройки ручной настройки", начните изменять вещи.
  • и затем, только измените их, если вы видите измеримое улучшение

Ответ 7

Ребята здесь верны, что большую часть времени это не имеет значения, когда размер структуры/класса мал и нет конструктора фантазийных копий. Однако это не повод для невежества. Здесь что происходит в некоторых современных ABI, таких как x64. Вы можете видеть, что на этой платформе хорошим порогом для вашего эмпирического правила является передача по значению, где тип - тип POD и sizeof() <= 16, поскольку он будет передан в двух регистрах. Правильные правила хороши, они не позволяют вам тратить время и ограниченные умственные способности на небольшие решения, подобные этим, которые не двигают иглой.

Однако иногда это имеет значение. Когда вы определились с профилировщиком, что у вас есть один из этих случаев (и НЕ до этого, если это не супер очевидно!), Тогда вам нужно понять детали низкого уровня - не слышите утешительных банальностей о том, как это не имеет значения, что SO полон. Некоторые вещи, о которых нужно помнить:

  • Передача по значению сообщает компилятору, что объект не изменяется. Существуют злые типы кода, в которых задействованы потоки или глобалы, где вещь, на которую указывает ссылка const, меняется где-то в другом месте. Хотя, конечно, вы не пишете злой код, у компилятора может быть трудное время, доказывающее это, и в результате вы должны создать очень консервативный код.
  • Если слишком много аргументов для передачи по регистру, то не имеет значения, что такое sizeof, объект будет передан в стек.
  • Объект, который является небольшим, и POD сегодня может расти и завтра получить дорогостоящий конструктор. Человек, который добавляет эти вещи, вероятно, не проверял, передается ли он по ссылке или по значению, поэтому код, который использовался для выполнения больших действий, может неожиданно вызвать. Передача с помощью ссылки const является более безопасным вариантом, если вы работаете в команде без всесторонних тестов регрессии производительности. Вы будете укушены этим рано или поздно.