Использование современного С++ для повышения производительности

в 11:07, , рубрики: c++, c++11, C++14

В данной статье я хотел бы рассказать, как использование средств современных стандартов С++ позволяет повысить производительность программ без каких-либо особых усилий от программиста.

Эта статья затрагивает лишь средства языка, а не конкретные техники оптимизации (т.е. такие вещи как локальность памяти, платформозависимые оптимизации, 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 <memory>
void f1(std::unique_ptr<int>& i) {
	*i += 1;
}
void f2(int& i) {
	i += 1;
}

Результат

f1(std::unique_ptr<int, std::default_delete<int> >&):
        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 <string>

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.

Спасибо за внимание!

Автор: Satus

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js