Практическое правило, когда передача по значению быстрее, чем передача по константной ссылке?

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

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

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

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

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

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

Однако принятый ответ ( https://stackoverflow.com/a/4876937/1408611 ) не сообщает никаких подробностей, он просто утверждает:

  

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

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

31 голос | спросил gexicide 15 +04002014-10-15T20:32:14+04:00312014bEurope/MoscowWed, 15 Oct 2014 20:32:14 +0400 2014, 20:32:14

7 ответов


0

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

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

ответил 15 +04002014-10-15T21:01:40+04:00312014bEurope/MoscowWed, 15 Oct 2014 21:01:40 +0400 2014, 21:01:40
0

Передача значения вместо константной ссылки имеет то преимущество, что компилятор знает , что значение не изменится. «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 & и объект имеет член, который является int или unsigned int, то любое назначение, использующее char *, int * или unsigned int * , может изменить это член, если компилятор не может доказать обратное. Передано по значению, доказательство гораздо проще для компилятора.

ответил gnasher729 15 +04002014-10-15T21:15:39+04:00312014bEurope/MoscowWed, 15 Oct 2014 21:15:39 +0400 2014, 21:15:39
0

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

sizeof(T) >= sizeof(T*)

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

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

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

ответил Drax 15 +04002014-10-15T20:34:54+04:00312014bEurope/MoscowWed, 15 Oct 2014 20:34:54 +0400 2014, 20:34:54
0

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

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

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

Сказав это, номинально ссылки реализуются как указатели. Так что в вакууме, без учета семантики, копий-опущений, семантики перемещения и тому подобного, было бы более «эффективно» передавать по указателю /ссылке все, чей размер больше, чем указатель.

ответил Escualo 15 +04002014-10-15T21:14:35+04:00312014bEurope/MoscowWed, 15 Oct 2014 21:14:35 +0400 2014, 21:14:35
0

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

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

ответил Андрей Вахрушев 15 +04002014-10-15T21:30:51+04:00312014bEurope/MoscowWed, 15 Oct 2014 21:30:51 +0400 2014, 21:30:51
0

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

  • выберите ОДИН подход и используйте его везде
  • согласуйте , какой из всех ваших коллег
  • только позже, на этапе "ручная настройка производительности", начните что-то менять
  • а затем изменяйте их только в том случае, если вы видите измеримое улучшение
ответил hoosierEE 15 +04002014-10-15T22:23:42+04:00312014bEurope/MoscowWed, 15 Oct 2014 22:23:42 +0400 2014, 22:23:42
0

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

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

  • Передача по значению говорит компилятору, что объект не изменяется. Существуют злые виды кода, включающие потоки или глобальные переменные, в которых то, на что указывает ссылка на const, изменяется где-то еще. Хотя, конечно, вы не пишете злой код, компилятору может быть непросто доказать это, и в результате ему придется генерировать очень консервативный код.
  • Если слишком много аргументов для передачи по регистру, то не имеет значения, какой размер sizeof, объект будет передан в стек.
  • Небольшой объект POD сегодня может вырасти и завтра получить дорогостоящий конструктор копирования. Человек, который добавляет эти вещи, вероятно, не проверял, передавались ли они по ссылке или по значению, поэтому код, который раньше выполнялся замечательно, может внезапно выползти. Передача по константной ссылке является более безопасным вариантом, если вы работаете в команде без всесторонних регрессионных тестов производительности. Это рано или поздно вас укусит.
ответил Eloff 17 Mayam16 2016, 01:10:13

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132