СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

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

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

Эта статья затрагивает лишь средства языка, а не конкретные техники оптимизации (т.е. такие вещи как локальность памяти, платформозависимые оптимизации, 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.

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

комментарии (16)

+2
maaGames ,  
> Не передавайте умные указатели по ссылке

И в примере сравнивается «производительность» умного указателя и значения. Тогда уж надо сравнивать код, который генерируется при передаче умного указателя по значению: копирование умного указателя + разыменование.
+1
devbutch ,  
Извините, но что здесь современного (уже во всю обсуждается C++17) и в чём повышение производительности? Это ведь просто выжимки из мануалов.
Предпочитайте emplace копированию

Здесь по факту просто определение методу emplace.

Не используйте shared_ptr, если можно обойтись unique_ptr

ровно как и
Ключевое слово final

Просто перевод первых строчек с http://www.cplusplus.com/reference.

Не передавайте умные указатели по ссылке

Почему? Если на объект создан указатель, то работа должна происходить с ним в контексте умного указателя. Указатели существуют для того, чтобы создать объект в процессе работы, когда мы не знаем на этапе компиляции параметров конструктора для него/количества экземпляров/тип экземпляра/итд.
В примере, вы что так что так передадите адрес объекта (размер которого фиксированный). А при передаче во вторую функцию, вам в любом случае придётся разыменовать unique_ptr. В чем профит?
0
Satus ,  
в чём повышение производительности?
Целью было продемонстрировать, как простое использование новых средств языка без каких-либо дополнительных оптимизаций может дать прирост производительности. Думаю, в тех примерах, где приведен бинарный код, это ясно видно.
Это ведь просто выжимки из мануалов.
70% статей о любом языке программирования — выжимки из мануалов.
Если на объект создан указатель, то работа должна происходить с ним в контексте умного указателя.
Почему? Я уточнил: «Не нужно передавать умные указатели по ссылке в метод, если он не производит никаких операций с самим объектом умного указателя, а лишь с объектом, хранящимся в нём.».
Указатели существуют для того, чтобы создать объект в процессе работы
Указатели — лишь переменные, хранящие адреса в памяти, не более. Умные указатели — средства контроля времени жизни объекта, созданного в куче.
когда мы не знаем на этапе компиляции параметров конструктора
Что вы имеете в виду?
вам в любом случае придётся разыменовать unique_ptr. В чем профит?
И сколько стоит разыменовать указатель? Приведите, пожалуйста, пример кода (скомпилированного с оптимизацией), где передача в функцию по ссылке значения разыменованного unique_ptr будет медленнее или такая же, как и передача самого unique_ptr по ссылке.
0
devbutch ,  
Целью было продемонстрировать, как простое использование новых средств языка без каких-либо дополнительных оптимизаций может дать прирост производительности. Думаю, в тех примерах, где приведен бинарный код, это ясно видно.

А где вы продемонстрировали новые средства? Я ещё раз повторюсь — C++11 это хорошо и круто, но не ново.

70% статей о любом языке программирования — выжимки из мануалов.

У этих 70% должен быть своеобразный подход к написанию — подача материала, сравнение производительности для разных примеров (таблички там свякие, графики), объяснение причин и где выгоднее использовать один кейс, а где другой. Тогда эта статья может быть хорошей, т.к. становится более наглядной, последовательной, изложенной иначе нежели чем в доке (что может быть полезно, т.к. все усваивают материал по разному).
А отличная статья, это когда ещё ко всему прочему описываются нетривиальные вещи. (так сказать проводится ресёч, что подчёркивает профессионализм автора) Вот пример такой статьи.

Почему? Я уточнил: «Не нужно передавать умные указатели по ссылке в метод, если он не производит никаких операций с самим объектом умного указателя, а лишь с объектом, хранящимся в нём.».

Что вы объяснили этим высказыванием? Вот зачем в вашем примере вторая функция? Если я создал smart_ptr, то мне его в любом случае придётся разыменовывать, чтобы передать в эту функцию. А если я создал smart_ptr и храню ещё обычный указатель, то я дебил — т.к. смысла в этом нет (да и философии unique_ptr это противоречит — уже два указателя).

когда мы не знаем на этапе компиляции параметров конструктора

Как пример — простое приложение, которое читает конфиг из файла и на его основе создаёт объекты. На этапе компиляции параметры конструктора для создания объекта не известны, а станут известны после считывания.
Как детальный пример:
1. Вы пишете многофункциональное сетевое приложение. Один из модулей этого приложения анализирует трафик. Модуль запрашивает с удалённого сервера список политик и применяет его к потокам. Здесь не имеет смысла создавать объект «контролЁра политик», пока мы не получим параметры для его создания. Вдруг ответ не придёт или придёт пустой лист — зачем в таком случае держать в памяти объект (особенно если он большой)? Легче держать указатель и как только появятся параметры, создать его(объект).

И сколько стоит разыменовать указатель? Приведите, пожалуйста, пример кода (скомпилированного с оптимизацией), где передача в функцию по ссылке значения разыменованного unique_ptr будет медленнее или такая же, как и передача самого unique_ptr по ссылке.

Если честно, я не совсем понял вопрос и ваш комментарий.
Разыменовывать ничего не стоит по факту — один вызовов функции для объекта умного указателя (operator*) и вы уже имеете указатель на объект.

P.S. ВСЕГДА используйте умные указатели. Это лишний коин в копилку безопасного, более читаемого кода, гибкого кода. #shared_ptr, #unique_ptr, #weak_ptr
–3
Laney1 ,  

10) предпочитайте лямбды указателям на функции

0
+1 –1
Satus ,  
Едва ли это добавит вам производительности. Оптимизированные варианты будут одинаковы.
–2
Laney1 ,  

вот так, бывает, напишешь про общепринятый прием, позволяющий во многих случаях ускорить код в пару раз, и начинаешь получать за это минусы от местных "специалистов"

0
Satus ,  
позволяющий во многих случаях ускорить код в пару раз


Как?
0
midday ,  
Как лямбды ускорят ваш код в несколько раз?
0
GamePad64 ,  

Лямбда — это функциональный объект. В лучшем случае, он будет такой же по скорости, как и указатель. В худшем — медленнее.

–1
Laney1 ,  

не вздумайте сказать такое на собеседовании (по крайней мере, мне)

0
Satus ,  
Эм, вы не правы. Лямба, согласно стандарту, это просто анонимная структура с определённым оператором вызова (operator()). Другое дело, что лябмды обязаны быть по возможности constexpr, но едва ли это вам как-то поможет, если вы передаёте лямбду как фукнциональный объект в какую-то функцию.
0
Satus ,   * (был изменён)
del
+1
+2 –1
GamePad64 ,  

Это какая-то экономия на спичках, имхо. Разница в одну ассемблерную инструкцию? Really? Такие оптимизации следует применять строго после профайлинга, если профайлер показал тормоза именно в этих местах.


Кстати, для правильного овладения умными указателями рекомендую презентацию Герба Саттера "Leak-Freedom in C++… By Default".


И ещё, по поводу unique_ptr: его можно очень удобно использовать для RAII-оборачивания указателей, полученных из сишных библиотек, с помощью кастомных deleter'ов.

+1
Satus ,  
Все эти рекомендации, кроме передачи умных указателей по ссылке, могут дать заметный прирост производительности.
Такие оптимизации следует применять строго после профайлинга
Какие? Использование final? Это должно писаться по умолчанию для тех методов, которые не должны передопределяться. Rule of zero? Тоже, все авторитетные источники рекомендуют использовать его по умолчанию. Emplace? Избегание shared_ptr когда не надо? Тоже просто здравый смысл. Что тут профайлить?
рекомендую презентацию Герба Саттера
Спасибо, я уже посмотрел все видео с CppCon'a 2016.
0
sborisov ,  
Почитайте лучше Саттера, где он разбирает все случаи стоимости передачи умных указателей в функции.
https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/