Оптимальный способ вернуть локальное значение в C++11

В старые времена, если бы я хотел строковое представление объекта AЯ бы написал что-нибудь с подписью void to_string(const A& a, string& out) чтобы избежать лишних копий. Является ли это все еще лучшей практикой в ​​C++11 с семантикой перемещения и прочим?

Я прочитал несколько комментариев о других контекстах, которые предлагают полагаться на RVO и вместо этого писать string to_string(const A& a), Но RVO не гарантируется! Итак, как я, как программист to_string, могу гарантировать, что строка не будет скопирована без необходимости (независимо от компилятора)?

3 ответа

Решение

Вот ответ, который я получил из отзывов и других источников:

Простой возврат по значению - идиома, потому что:

  • на практике копирование / перемещение будет происходить большую часть времени;
  • ctor хода будет использоваться при аварийном восстановлении;
  • предотвращение невероятного случая фактического копирования не стоит менее читаемого кода
  • передача ссылки требует, чтобы объект уже был создан
    • не всегда выполнимо (например, не может быть ctor по умолчанию) и какие
    • одна слишком большая инициализация также должна быть принята во внимание, если проблема заключается в производительности

Тем не менее, если типичное использование ожидается что-то вроде

std::string s;
while (i_need_to)
{
    to_string(get_A(), s);
    process(s);
    update(i_need_to);
}

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

* рассматривая строку здесь только в качестве примера, но вопрос и ответы можно обобщить

Предполагая, что код в вашей функции имеет вид:

std::string data = ...;
//do some processing.
return data;

Тогда для этого необходимо позвонить std::stringПереместить конструктор, если elision не доступен. Так что в худшем случае вы можете перейти от своей внутренней строки.

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

При этом... вы беспокоитесь о том, что компиляторы не могут встроить короткие функции? Вы обеспокоены тем, не будут ли небольшие обертки должным образом оптимизированы? Есть ли возможность компилятора не оптимизировать for петли и тому подобное мешают? Вы думаете о том, if(x < y) быстрее чем if(x - y < 0)?

Если нет... тогда почему вы заботитесь о разрешении копирования / перемещения (технический термин для "оптимизации возвращаемого значения", поскольку он используется в большем количестве мест, чем это)? Если вы используете компилятор, который не поддерживает копирование, то вы используете ужасный компилятор, который, вероятно, не может поддерживать множество других оптимизаций. Для повышения производительности лучше потратить время на обновление компилятора, чем превращать возвращаемые значения в ссылки.

предотвращение невероятного случая фактического копирования не стоит... хлопот? менее читаемый код? что именно? какая дополнительная вещь, которая весит на стороне простого возвращения?

"Дополнительная вещь" заключается в том, что это:

std::string aString = to_string(a);

Это более читабельно, чем это:

std::string aString;
to_string(a, aString);

В первом случае сразу видно, что to_string инициализирует строку. Во вторых, это не так; ты должен посмотреть вверх to_stringподпись, чтобы увидеть, что это неconst ссылка.

Первый случай даже не "идиоматический"; так все обычно пишут. Вы никогда не увидите to_int(a, someInt) вызов целых чисел; Это нелепо. Почему целочисленное создание и создание объектов должны быть такими разными? Вам, программисту, не нужно заботиться о том, происходит ли слишком много копий для возвращаемого значения или чего-то еще. Вы просто делаете вещи простым, очевидным и понятным способом.

В старые времена (1970-1980) вы могли в значительной степени предсказать производительность алгоритма путем подсчета делений с плавающей запятой.

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

Посчитайте количество поездок в кучу: оба new/malloc а также delete/free,

Дано:

std::string
to_string(const A& a)
{
    std::string s;
    // fill it up
    return s;
}

std::string s = test();

Я считаю 1 новый, если вы не перераспределите s внутренний к to_string(), Это одно распределение делается, когда вы помещаете данные в s, я знаю это std::string имеет быстрый (без выделения) конструктор перемещения. То, происходит ли RVO или нет, не имеет значения для оценки эффективности to_string(), Там будет 1 распределение в создании s вне to_string(),

Теперь рассмотрим:

void
to_string(const A& a, string& out)
{
    out = ...
}

std::string s;
to_string(a, s);

Как я уже писал, он по-прежнему занимает 1 выделение памяти. Так что это примерно та же скорость, что и версия с возвратом по значению.

Теперь рассмотрим новый вариант использования:

while (i_need_to)
{
    std::string s = to_string(get_A());
    process(s);
    update(i_need_to);
}

Согласно нашему предыдущему анализу, выше будет делать 1 распределение на одну итерацию. Теперь рассмотрим это:

std::string s;
while (i_need_to)
{
    to_string(get_A(), s);
    process(s);
    update(i_need_to);
}

я знаю это string имеет capacity()и эта емкость может быть переработана во многих случаях в вышеуказанном цикле. В худшем случае у меня все еще есть 1 распределение на одну итерацию. В лучшем случае сценарий состоит в том, что первая итерация создаст емкость, достаточную для обработки всех других итераций, и что весь цикл будет выполнять только 1 выделение.

Правда, скорее всего, будет лежать где-то между худшим и лучшим сценариями.

Лучший API будет зависеть от тех сценариев использования, в которых, по вашему мнению, будет работать ваша функция.

Подсчитать распределение, чтобы оценить производительность. А затем измерить то, что вы закодировали. В случае std::string, вероятно, будет короткий строковый буфер, который может повлиять на ваше решение. В случае с libC++ на 64-битной платформе std::string будет хранить до 22 char (плюс завершающий ноль), прежде чем он совершит поездку в кучу.

Другие вопросы по тегам