вторник, 20 мая 2014 г.

[prog.c++] Как разогнать вчерашний пример

В продолжение вчерашней темы про очередь из std::function-объектов. С подачи ув.тов.Timur Rakhmatullaev в код были внесены две правки, которые существенно ускорили работу варианта с std::function.

Первая правка заключалась в использовании метода queue::emplace вместо queue::push. Огромное спасибо Тимуру за напоминание об этом методе -- в C++11 расширился API стандартных контейнеров, но я об этом постоянно забываю.

Вторая же правка, которая, по-моему, и привела к существенному ускорению работы, заключалась в том, чтобы хитрым образом забирать значение объекта из головы очереди. Вместо копирования в старом варианте кода сейчас задействован перенос значения объекта (посредством move-конструктора):

void
fn_test()
   {
      fn_demand_queue_t queue;
      fn_demand_generator_t generator;

      generator.on_type_1( queue );
      forsize_t i = 0; i != ITERATIONS; ++i )
         {
            fn_demand_t d = std::move(queue.front());
            queue.pop();

            d();
         }
   }

Результаты замеров существенно изменились. Так, если под GCC 4.8.2 я получал в старом варианте вот такие результаты:

fn_demands: 31.302s
oo_demands: 21.397s

То в новом варианте получается уже вот так:

fn_demands: 17.039s
oo_demands: 19.798s

Интересный эксперимент получился. И да, сутью эксперимента было посмотреть, какие накладные расходы будет нести средней руки разработчик, если начнет использовать очереди из std::function-объектов.

Под катом обновленный код теста.

#include <iostream>
#include <queue>
#include <memory>
#include <functional>
#include <ctime>

const size_t ITERATIONS = 100000000;

//
// Functional demands
//
typedef std::function< void(void) > fn_demand_t;

typedef std::queue< fn_demand_t > fn_demand_queue_t;

class fn_demand_generator_t
   {
   public :
      void
      on_type_1( fn_demand_queue_t & queue )
         {
            queue.emplace(
                  [this, &queue] { this->on_type_2( queue ); } );
         }

      void
      on_type_2( fn_demand_queue_t & queue )
         {
            queue.emplace(
                  [this, &queue] { this->on_type_3( queue ); } );
         }

      void
      on_type_3( fn_demand_queue_t & queue )
         {
            queue.emplace(
                  [this, &queue] { this->on_type_1( queue ); } );
         }
   };

void
fn_test()
   {
      fn_demand_queue_t queue;
      fn_demand_generator_t generator;

      generator.on_type_1( queue );
      forsize_t i = 0; i != ITERATIONS; ++i )
         {
            fn_demand_t d = std::move(queue.front());
            queue.pop();

            d();
         }
   }

//
// Object-oriented demands.
//

class oo_demand_t
   {
   public :
      virtual ~oo_demand_t()
         {}

      virtual void
      handle() = 0;
   };

typedef std::shared_ptr< oo_demand_t > oo_demand_shptr_t;

typedef std::queue< oo_demand_shptr_t > oo_demand_queue_t;

class oo_demand_generator_t
   {
   public :
      void
      on_type_1( oo_demand_queue_t & queue );

      void
      on_type_2( oo_demand_queue_t & queue );

      void
      on_type_3( oo_demand_queue_t & queue );
   };

class oo_demand_type_1 : public oo_demand_t
   {
   private :
      oo_demand_generator_t & m_generator;
      oo_demand_queue_t & m_queue;

   public :
      oo_demand_type_1(
         oo_demand_generator_t & generator,
         oo_demand_queue_t & queue )
         :  m_generator( generator )
         ,  m_queue( queue )
         {}

      virtual void
      handle()
         {
            m_generator.on_type_1( m_queue );
         }
   };

class oo_demand_type_2 : public oo_demand_t
   {
   private :
      oo_demand_generator_t & m_generator;
      oo_demand_queue_t & m_queue;

   public :
      oo_demand_type_2(
         oo_demand_generator_t & generator,
         oo_demand_queue_t & queue )
         :  m_generator( generator )
         ,  m_queue( queue )
         {}

      virtual void
      handle()
         {
            m_generator.on_type_2( m_queue );
         }
   };

class oo_demand_type_3 : public oo_demand_t
   {
   private :
      oo_demand_generator_t & m_generator;
      oo_demand_queue_t & m_queue;

   public :
      oo_demand_type_3(
         oo_demand_generator_t & generator,
         oo_demand_queue_t & queue )
         :  m_generator( generator )
         ,  m_queue( queue )
         {}

      virtual void
      handle()
         {
            m_generator.on_type_3( m_queue );
         }
   };

inline void
oo_demand_generator_t::on_type_1( oo_demand_queue_t & queue )
   {
      queue.emplace( std::make_shared< oo_demand_type_2 >( *this, queue ) );
   }

inline void
oo_demand_generator_t::on_type_2( oo_demand_queue_t & queue )
   {
      queue.emplace( std::make_shared< oo_demand_type_3 >( *this, queue ) );
   }

inline void
oo_demand_generator_t::on_type_3( oo_demand_queue_t & queue )
   {
      queue.emplace( std::make_shared< oo_demand_type_1 >( *this, queue ) );
   }

void
oo_test()
   {
      oo_demand_queue_t queue;
      oo_demand_generator_t generator;

      generator.on_type_1( queue );
      forsize_t i = 0; i != ITERATIONS; ++i )
         {
            oo_demand_shptr_t d = std::move(queue.front());
            queue.pop();

            d->handle();
         }
   }

//
// Benchmarking
//

templateclass TEST_CASE >
void
run_and_measure(
   const char * test_name,
   TEST_CASE test_case )
   {
      const std::clock_t begin = std::clock();

      test_case();

      const std::clock_t end = std::clock();

      const double duration = std::difftime(end,begin) / CLOCKS_PER_SEC;
      std::cout << test_name << ": " << duration << "s" << std::endl;
   }

int
main()
   {
      run_and_measure( "fn_demands", fn_test );
      run_and_measure( "oo_demands", oo_test );
   }

Комментариев нет: