Производительность с семантикой значений
Меня очень беспокоит производительность и читаемость кода, и я получаю большинство своих идей от 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;
}
}