Быстрее чем std::function

от автора

С момента выхода стандарта С++11 прошло уже больше двух лет. И большинство С++ разработчиков уже знакомы с основными его нововведениями и костылями. Одним из нововведений было добавление в стандартную библиотеку класса std::function.

Классы std::function и boost::function являются высокоуровневыми обертками над функциями и функциональными объектами. Объекты таких классов позволяют хранить и вызывать функции и функторы с заданной сигнатурой, что бывает удобно, например, при создании callback вызовов (например, мы можем регистрировать несколько обработчиков, и это могут быть как обычные функции, так и объекты с определенным оператором =)

©

Этого замечательного шаблонного класса давно не хватало нам. Однако, в погоне за универсальностью разработчикам стандартной библиотеки пришлось пойти на некоторые жертвы. Так как С++ используется в первую очередь там, где необходимо выжать максимальную скорость из железа, со больше всего разочаровывает любой overhead по скорости. В частности, к таким вещам можно отнести:

  1. При создании объекта std::function вызывается оператор new, как следствие того, что std::function позволяет сохранять в себя функтор произвольного размера.
  2. std::function имеет семантику копирования, которая, в принципе, редко когда бывает необходима в при действиях с функторами (в связи с появлением move-семантики), но стоит определенных ресурсов CPU.

Как от этого избавиться?

Для этого, конечно же, нужно написать свой std::function без этих фатальных недостатков =) К счастью, новые возможности С++11 позволяют нам это сделать без лишних телодвижений и обращений к специфичным возможностям компиляторов.

Что хочется?

  • Избавиться от вызовов new при создании объекта функтора.
  • Убрать семантику копирования, чтобы избежать возможного копирования объектов функторов.
  • Синтаксиса близкого к std::function.
  • Понятного кода.
  • Побаловаться с С++11.

Ну и как же это сделать?

Для начала рассмотрим простой способ реализовать делегат на С++, который я подчерпнул из статей Fastest Possible C++ Delegates и Lightweight Generic C++ Callbacks.

class function_t { public:     template <typename U>     fixed_function_t(U &&object)     {         typedef typename std::remove_reference<U>::type unref_type;          m_object_ptr = new unref_type(object);         m_method_ptr = &method_stub<unref_type>;     }      void operator()() const     {         if (m_method_ptr) {             (*m_method_ptr)(m_object_ptr);         }     }  private:     void *m_object_ptr;      typedef R (*method_type)(void *);     method_type m_method_ptr;      template <class T>     static void method_stub(void *object_ptr)     {         static_cast<T *>(object_ptr)->operator()();     } }; 

Пока что закроем глаза на отсутствие вызова delete, чтобы не загружать код излишней информацией.
А дальше, будем добавлять все необходимые на фичи.

Убираем выделение динамической памяти

Для этого будем конструировать объект делегата в уже существующем на момент создания нашего function буфере. Добавим в наш класс следующее поле:

class function_t {     enum {STORAGE_SIZE = 32}; public:     // ... private:     typename std::aligned_storage<STORAGE_SIZE, STORAGE_SIZE>::type m_storage;         // ... }; 

И будем конструировать объект делегата в нём.

    template <typename U>     fixed_function_t(U &&object)     {         typedef typename std::remove_reference<U>::type unref_type;          m_object_ptr = new (&m_storage) unref_type(std::forward<U>(object));         m_method_ptr = &method_stub<unref_type>;     } 

Убираем возможность копирования.

struct noncopyable_t {     noncopyable_t & operator=(const noncopyable_t&) = delete;     noncopyable_t(const noncopyable_t&) = delete;     noncopyable_t() = default; };  class function_t  : private noncopyable_t  {     //... 

При этом надо не забыть реализовать правильно move семантику! В move конструкторе придется делать memcpy для m_storage, и изменить указатель на метод.

Добавляем синтаксиса близкий к std::function

template <typename R, typename... ARGS> class fixed_function_t : private noncopyable_t {     enum {STORAGE_SIZE = 32}; public:      //...      R operator()(ARGS... args) const     {         if (m_method_ptr) {             return (*m_method_ptr)(m_object_ptr, args...);         }         return R();     }  private:      //...      template <class T>     static R method_stub(void *object_ptr, ARGS... args)     {         return static_cast<T *>(object_ptr)->operator()(args...);     } }; 

Готово!

Вместо заключения

Надеюсь мой велосипед будет для вас полезен в качестве источника идей, а, может быть, он даже попадет в каком-то виде в продакшн. Я буду только рад.
Полный код с тестами можно найти, как это принято, на Github.

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

ссылка на оригинал статьи http://habrahabr.ru/post/203888/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *