[Из песочницы] Использование современного С++ для повышения производительности
В данной статье я хотел бы рассказать, как использование средств современных стандартов С++ позволяет повысить производительность программ без каких-либо особых усилий от программиста.
Эта статья затрагивает лишь средства языка, а не конкретные техники оптимизации (т.е. такие вещи как локальность памяти, платформозависимые оптимизации, lockfree и прочее остаются за бортом).
Виртуальный метод класса, объявленный с использованием ключевого слова final, не может быть переопределен наследниками.
В некоторых случаях это позволяет компилятору избежать обычных расходов на вызов виртуальной функции (девиртуализация).
Ассемблерный код (здесь и далее: ggc 6.2, -O3 -std=c++14):
Умные указатели должны использоваться для определения срока жизни объекта. Не нужно передавать умные указатели по ссылке в метод, если он не производит никаких операций с самим объектом умного указателя, а лишь с объектом, хранящимся в нём.
Rule of zero гласит, что для класса, в котором не нужно явно определять деструктор, также не нужно явно определять конструкторы/операторы копирования/перемещения.
Начиная с С++11 стандартные контейнеры позволяют конструировать элемент напрямую внутри контейнера, избегая лишнего копирования.
Использования shared_ptr несёт за собой определённые расходы. При создании, копировании, удалении shared_ptr обновляет внешний счётчик ссылок на хранимый объект. Также shared_ptr обязан быть потокобезопасным, что тоже может нести за собой соответствующие расходы. В то время как выделение и удаление памяти с использованием unique_ptr вообще никак не отличается от использования ручного управления памятью с использованием new/delete.
Эта статья затрагивает лишь средства языка, а не конкретные техники оптимизации (т.е. такие вещи как локальность памяти, платформозависимые оптимизации, lockfree и прочее остаются за бортом).
Ключевое слово final
Виртуальный метод класса, объявленный с использованием ключевого слова final, не может быть переопределен наследниками.
class A {
public:
virtual void f() {}
};
class B : public A {
public:
virtual void f() override final {}
};
class C : public B {
public:
virtual void f() override {} // ошибка
};
В некоторых случаях это позволяет компилятору избежать обычных расходов на вызов виртуальной функции (девиртуализация).
Пример:
Код
class A {
public:
virtual void f() = 0;
};
class B1 : public A {
public:
void f() final override;
};
class B2 : public A {
public:
void f() override;
};
void with_final(B1* b) {
b->f();
}
void no_final(B2* b) {
b->f();
}
Ассемблерный код (здесь и далее: ggc 6.2, -O3 -std=c++14):
Результат
with_final(B1*):
jmp B1::f()
no_final(B2*):
mov rax, QWORD PTR [rdi]
jmp [QWORD PTR [rax]]
Не передавайте умные указатели по ссылке
Умные указатели должны использоваться для определения срока жизни объекта. Не нужно передавать умные указатели по ссылке в метод, если он не производит никаких операций с самим объектом умного указателя, а лишь с объектом, хранящимся в нём.
Пример:
Код
#include
void f1(std::unique_ptr& i) {
*i += 1;
}
void f2(int& i) {
i += 1;
}
Результат
f1(std::unique_ptr >&):
mov rax, QWORD PTR [rdi]
add DWORD PTR [rax], 1
ret
f2(int&):
add DWORD PTR [rdi], 1
ret
Используйте Rule of zero
Rule of zero гласит, что для класса, в котором не нужно явно определять деструктор, также не нужно явно определять конструкторы/операторы копирования/перемещения.
Явное определение конструктора копирования запрещает компилятору генерировать конструктор перемещения, что в некоторых случаях может значительно снизить производительность.
Пример:
#include
class A {
std::string s;
public:
A() = default;
A(const A& a) = default; // конструктор перемещения не будет сгенерирован
};
class B {
std::string s;
public:
B() = default;// конструктор копирования и перемещения сгенерирован автоматически
};
auto f()
{
return std::make_pair(A(), B());// для А будет вызван конструктор копирования, для B - перемещения
}
Предпочитайте emplace копированию
Начиная с С++11 стандартные контейнеры позволяют конструировать элемент напрямую внутри контейнера, избегая лишнего копирования.
Использования emplace быстрее не только простого копирования, но даже перемещения.
Не используйте shared_ptr, если можно обойтись unique_ptr
Использования shared_ptr несёт за собой определённые расходы. При создании, копировании, удалении shared_ptr обновляет внешний счётчик ссылок на хранимый объект. Также shared_ptr обязан быть потокобезопасным, что тоже может нести за собой соответствующие расходы. В то время как выделение и удаление памяти с использованием unique_ptr вообще никак не отличается от использования ручного управления памятью с использованием new/delete.
Спасибо за внимание!
Комментарии (2)
13 октября 2016 в 14:26
+1↑
↓
> Не передавайте умные указатели по ссылкеИ в примере сравнивается «производительность» умного указателя и значения. Тогда уж надо сравнивать код, который генерируется при передаче умного указателя по значению: копирование умного указателя + разыменование.
13 октября 2016 в 14:41
+1↑
↓
Извините, но что здесь современного (уже во всю обсуждается C++17) и в чём повышение производительности? Это ведь просто выжимки из мануалов.Предпочитайте emplace копированию
Здесь по факту просто определение методу emplace.Не используйте shared_ptr, если можно обойтись unique_ptr
ровно как иКлючевое слово final
Просто перевод первых строчек с http://www.cplusplus.com/reference.Не передавайте умные указатели по ссылке
Почему? Если на объект создан указатель, то работа должна происходить с ним в контексте умного указателя. Указатели существуют для того, чтобы создать объект в процессе работы, когда мы не знаем на этапе компиляции параметров конструктора для него/количества экземпляров/тип экземпляра/итд.
В примере, вы что так что так передадите адрес объекта (размер которого фиксированный). А при передаче во вторую функцию, вам в любом случае придётся разыменовать unique_ptr. В чем профит?