Производительность с семантикой значений

Меня очень беспокоит производительность и читаемость кода, и я получаю большинство своих идей от Chandler Carruth от Google. Я бы хотел применить следующие правила для С++ для чистого кода, не теряя при этом производительности вообще.

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

Таким образом, функции не имеют побочных эффектов. Это необходимо для чтения кода и делает С++ видом функционального. Теперь идет производительность. Что вы можете сделать, если хотите написать функцию, которая добавляет 1 к каждому элементу std::vector? Вот мое решение.

std::vector<int> add_one(std::vector<int> v) {
    for (std::size_t k = 0; k < v.size(); ++k) {
        v[k] += 1;
    }
    return v;
}

...
v = add_one(std::move(v));
...

Я нахожу это очень элегантным и делает всего 2 хода. Вот мои вопросы:

  • Является ли это законным С++ 11?
  • Как вы думаете, какой недостаток в этом дизайне?
  • Не удалось ли компилятору автоматически преобразовать v = f (v) в это? Это своего рода копия.

PS: Люди спрашивают меня, почему я не люблю проходить по ссылке. У меня есть 2 аргумента:

1 - Он не делает явным на сайте вызова, какой аргумент может быть мутирован.

2 - Иногда это убивает производительность. Ссылки и указатели являются ад для компилятора из-за сглаживания. Возьмем следующий код

std::array<double, 2> fval(const std::array<double, 2>& v) {
    std::array<double, 2> ans;
    ans[0] = cos(v[0] + v[1]);
    ans[1] = sin(v[0] + v[1]);
    return ans;
}

Тот же код, который принимает as в качестве ссылки, в 2 раза медленнее:

std::array<double, 2> fref(const std::array<double, 2>& v,
        std::array<double, 2>& ans) {
    ans[0] = cos(v[0] + v[1]);
    ans[1] = sin(v[0] + v[1]);
}

Сглаживание указателя не позволяет компилятору вычислять sin и cos с помощью одной машинной команды (Gcc в настоящее время не выполняет эту оптимизацию, но icpc делает оптимизацию с семантикой значений).

Ответ 1

Мне кажется, что это законный С++ 11. Недостатки могут быть основаны на мнениях, поэтому я не буду это решать, но что касается вашей третьей точки, компилятор мог сделать это преобразование только в том случае, если он может доказать, что функция также не имеет псевдонима v. Поскольку такие авторы-компиляторы, возможно, не выбрали такую ​​оптимизацию для простых случаев, они могут анализировать и оставлять эту нагрузку (без сглаживания) на программиста.

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

template <typename Iterator>
void add_one(Iterator first, Iterator last)
{
    for(; first != last; ++first)
    {
        (*first) += 1;
    }
}