пятница, 27 октября 2017 г.

[prog.c++] Библиотека timertt обновилась до версии 1.2.0

Мы обновили свою легковесную библиотеку для работы с отложенными и периодическими таймерами (wallclock-таймеры не поддерживаются в принципе). В этой версии добавлены две важные фичи:

1. Раньше действие для таймера всегда имело тип std::function<void()>, что было гибко и удобно, но имело скрытые накладные расходы, связанные с std::function (по сути, std::function тут выступал как умный указатель для лямбд и функторов). Если от этих скрытых расходов хочется избавиться, то можно задать свой собственный тип. Например:

class operation_canceler {
    operation_manager & manager_;
    operation_id id_;
public:
    operation_canceler(operation_manager & manager, operation_id id)
        : manager_{manager}, id_{id}
    {}
    void operator()() const noexcept
    {
        manager_.cancel(id_);
    }
};
...
// Define type of timer thread which was use operation_canceler as
// a timer action type.
using my_timer_wheel_thread = timertt::timer_wheel_thread_template<
    operation_canceler,
    timertt::default_error_logger,
    timertt::default_actor_exception_handler >;
...
// Create and use this timer thread.
my_timer_wheel_thread tt;
tt.start();
...
tt.activate( std::chrono::milliseconds(750), operation_canceler{manager, current_id});

Тип должен быть Moveable и MoveConstructible. Соответственно, теперь когда создается объект-таймер, там резервируется место для пользовательского объекта-функтора. А при активации таймера пользовательский функтор мувится в этот зарезервированный кусок памяти. Тем самым не происходит дополнительных аллокаций памяти (как это временами может происходить с std::function).

2. Теперь пользователь сам может создавать объекты-таймеры на стеке или внутри других своих объектов. Ранее объекты-таймеры создавались исключительно динамически. Теперь можно избежать дополнительных аллокаций, но зато пользователь сейчас сам должен следить за тем, чтобы таймер не закончил свою жизнь раньше времени. Например:

void do_something_complex() {
    timertt::default_timer_wheel_thread tt;
    tt.start();
    ...
    timertt::default_timer_wheel_thread::scoped_timer_object timer;
    // Activate
    tt.activate(timer, std::chrono::milliseconds(250), ...);
    ...
    // Timer can be deactivated in usual way.
    tt.deactivate(timer);
    ...
    tt.shutdown_and_join();
}

Правда, в этой версии мы несколько сломали совместимость на уровне исходного кода. Поэтому номер версии 1.2.0, а не 1.1.4.

Пару слов о происхождении и назначении библиотеки. Когда-то мы долго и с удовольствием использовали большую библиотеку ACE. В том числе и тамошние таймеры (реализация которых была добротной и продвинутой). Но по мере перехода на C++11 мы постепенно отказывались от ACE и в один прекрасный момент оказалось, что из ACE нам нужны только таймеры. Чтобы не таскать дистрибутив ACE только ради таймеров, мы сделали свою легковесную header-only либу, которая базируется только на штатных возможностях C++11.

У нас timertt в работе уже года три. Проблем не замечено. Работает стабильно, может поддерживать изрядное количество таймеров (десятки и сотни миллионов). Реализует три разных таймерных механизма: wheel, heap и list, каждый из которых хорош в своей ситуации.

Предыдущие версии timertt могли работать и с компиляторами, которые не очень хорошо поддерживали C++11 (в частности, VS2013). Начиная с 1.2.0 мы на такие компиляторы уже не оглядываемся. Нужно что-то более-менее нормальное (gcc 4.8-7.2, clang 3.5-5.0, vs2015/2017). Однако, основная часть кода пока еще под все возможности C++ (вроде noexcept и constexpr там, где это разумно) еще не адаптирована. Сделаем это со временем.

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

Архивы с исходниками библиотеки можно найти здесь. Сами исходники живут здесь. Документация здесь.