LoginSignup
1
0

More than 5 years have passed since last update.

【boost::asio allocation】async関数のハンドラ

Posted at

今回のお題

boost1_57_0 allocation(C++11)
http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/examples/cpp11_examples.html

上記サンプルを CMakeプロジェクトにしたものを
下記リポジトリに置いておきます
https://github.com/MurasameOpen/CPPSample/tree/master/boostSample/boost1_57_0/asio/cpp11/allocation

解析

動作

待ち受けポートをパラメータに 起動し
recvしたデータをそのまま返す echo動作をします

telnetで接続してみましょう

telnet
miyatake_y$ telnet -4 localhost 8080 
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
12345
12345

ソース解析

基本は buffersと同じです
ハンドラーの部分に独自メモリアロケータを使用し
効率化をはかってます

ソース

class server
は、buffers参照。
shared_ptrを使いセッションを管理しています

session

inline custom_alloc_handler<Handler> make_custom_alloc_handler(
    handler_allocator& a, Handler h)
{
  return custom_alloc_handler<Handler>(a, h);
}

class session
  : public std::enable_shared_from_this<session>
{
public:
  session(tcp::socket socket)
    : socket_(std::move(socket))
  {
  }

  void start()
  {
    do_read();
  }

private:
  void do_read()
  {
    auto self(shared_from_this());
    socket_.async_read_some(boost::asio::buffer(data_),
        make_custom_alloc_handler(allocator_,
          [this, self](boost::system::error_code ec, std::size_t length)
          {
            if (!ec)
            {
              do_write(length);
            }
          }));
  }

  void do_write(std::size_t length)
  {
    auto self(shared_from_this());
    boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
        make_custom_alloc_handler(allocator_,
          [this, self](boost::system::error_code ec, std::size_t /*length*/)
          {
            if (!ec)
            {
              do_read();
            }
          }));
  }

  // The socket used to communicate with the client.
  tcp::socket socket_;

  // Buffer used to store data received from the client.
  std::array<char, 1024> data_;

  // The allocator to use for handler-based custom memory allocation.
  handler_allocator allocator_;
};

std::enable_shared_from_this
を使い、shared_ptrをラムダのキャプチャに登録し
sessionがdeleteされるのを防ぐ所はbuffersと同じです

make_custom_alloc_handler(allocator_, ラムダ・・
が今回の重要ポイント。
buffersでは単にラムダ式を入れてました
実際ここも、ラムダ式を直接登録しても動作しますが
毎回ハンドラの登録で newが発生します
custom_alloc_handlerでは そのnewが毎回発生しない仕組みを作っています

custom_alloc_handler
class custom_alloc_handler
{
public:
  custom_alloc_handler(handler_allocator& a, Handler h)
    : allocator_(a),
      handler_(h)
  {
  }

  template <typename ...Args>
  void operator()(Args&&... args)
  {
    handler_(std::forward<Args>(args)...);
  }

  friend void* asio_handler_allocate(std::size_t size,
      custom_alloc_handler<Handler>* this_handler)
  {
    return this_handler->allocator_.allocate(size);
  }

  friend void asio_handler_deallocate(void* pointer, std::size_t /*size*/,
      custom_alloc_handler<Handler>* this_handler)
  {
    this_handler->allocator_.deallocate(pointer);
  }

private:
  handler_allocator& allocator_;
  Handler handler_;
};

handler_allocator のラップをしています。

operator()(Args&&... args)

は、ハンドラが呼ばれた時に呼び出されるメンバ関数
Asioからコールバックが来た際に、sessionで登録した ラムダ式を呼び出します
Args&&... を使えば、色々な引数形式にも対応できますね
コレは便利

asio_handler_allocate、asio_handler_deallocate のフレンド関数
ハンドラーは(恐らく)一度 Asioによってメモリコピーされますが
その時のメモリの allocate deallocateをオーバーロードします
ここでは handler_allocatorの allocate deallocateを呼びます

handle_allocator
class handler_allocator
{
public:
  handler_allocator()
    : in_use_(false)
  {
  }

  handler_allocator(const handler_allocator&) = delete;
  handler_allocator& operator=(const handler_allocator&) = delete;

  void* allocate(std::size_t size)
  {
    if (!in_use_ && size < sizeof(storage_))
    {
      in_use_ = true;
      return &storage_;
    }
    else
    {
      return ::operator new(size);
    }
  }

  void deallocate(void* pointer)
  {
    if (pointer == &storage_)
    {
      in_use_ = false;
    }
    else
    {
      ::operator delete(pointer);
    }
  }

private:
  // Storage space used for handler-based custom memory allocation.
  typename std::aligned_storage<1024>::type storage_;

  // Whether the handler-based custom allocation storage has been used.
  bool in_use_;
};

ハンドラーのメモリ確保&解放時に allocate deallocateが呼ばれます
細かい部分は読んでもらうとして
typename std::aligned_storage<1024>::type storage_;
で 1024Bytesのメモリを確保しています
メモリアライメントのために aligned_storage で確保
今回のサンプルでは この handler_allocator はsessionで作成し参照しているので
session内で何度 async関数を使っても、再アロケートされません
そして、ハンドラーとして確保すべき領域が1024Bytes以下の場合は
storage_ に上書きをするので new deleteが行われずパフォーマンス向上
1024Bytesを超える場合は new deleteを使う

細かいようですが、read writeはかなりの頻度で使うので
ハンドラーの allocate deallocateを大幅に減らす事は
パフォーマンスに影響を及ぼすでしょう

結論

ハンドラーのメモリ確保&開放のコストは少なくはないと思うので
なるべくなら カスタムアロケータを作り
コストを下げるべきだ

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0