2

下面的代码使用提供自定义分配器的处理程序。分配器提供了一个BucketPool可以分配不同大小的实例,但是需要在编译时指定可以分配的大小。

要使用BucketPool,我只需指定一些任意模板参数,然后查看编译错误以查看实际需要的大小,然后将该大小添加为模板参数,然后迭代直到没有编译错误。

我注意到需要进行的分配大小可能会发生变化,例如在将 boost 更新到较新版本时。

如何在编译时确定大小,以便在迁移到新版本时不需要更新它?也许有一些特性可以用于这个......

编辑:c++17 是我可用的。因此,任何使用它的解决方案都是可取的。

#include <chrono>
#include <iostream>
#include <utility>

#include "boost/asio.hpp"
#include <boost/pool/pool.hpp>
#include <boost/make_shared.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/bind/bind.hpp>

namespace details {

template <size_t SIZE, size_t... REST>
class Bucket;

template <size_t SIZE>
class Bucket<SIZE> {

public:
  Bucket() = default;

protected:
  void* do_alloc(const std::size_t numElements) {
    assert(numElements == 1);
    return allocator_.malloc();
  }

  void do_dealloc(void* const ptr, const std::size_t numElements) {
    assert(numElements == 1);
    allocator_.free(ptr, numElements);
  }

private:
  boost::pool<boost::default_user_allocator_new_delete> allocator_{SIZE};
};

template <size_t SIZE, size_t... REST>
class Bucket
  : public Bucket<SIZE>
  , public Bucket<REST>... {};

}  // namespace details

template <size_t SIZE, size_t... REST>
class BucketPool : public details::Bucket<SIZE, REST...> {
public:
  template <size_t S>
  void* alloc(const std::size_t numElements) {
    return details::Bucket<S>::do_alloc(numElements);
  }

  template <size_t S>
  void dealloc(void* const ptr, const std::size_t numElements) {
    assert(numElements == 1);
    details::Bucket<S>::do_dealloc(ptr, numElements);
  }
};


using strand_t = boost::asio::strand<boost::asio::io_context::executor_type>;

template <typename T, typename PoolType>
class ObjectAllocator {
public:
  using value_type = T;

  explicit ObjectAllocator(PoolType& bucketPool) : bucketPool_(bucketPool) {}

  template <typename U, typename K>
  explicit ObjectAllocator(const ObjectAllocator<U, K>& other)
  : bucketPool_(other.bucketPool_) {}

  bool operator==(const ObjectAllocator& lhs) const noexcept {
    return bucketPool_ == lhs.bucketPool_;
  }

  bool operator!=(const ObjectAllocator& lhs) const noexcept {
    return bucketPool_ != lhs.bucketPool_;
  }

  T* allocate(const std::size_t numElements) const {
    return static_cast<T*>(bucketPool_.template alloc<sizeof(T)>(numElements));
  }

  void deallocate(T* const ptr, const std::size_t numElements) const {
    bucketPool_.template dealloc<sizeof(T)>(ptr, numElements);
  }

private:
  template <typename, typename>
  friend class ObjectAllocator;

  PoolType& bucketPool_;
};

template <typename HandlerT>
class AsioTimer {

  class HandlerWrapper : public boost::enable_shared_from_this<HandlerWrapper> {
  public:
    HandlerWrapper(strand_t strand, HandlerT handler)
    : timer_(strand), handler_(handler), milliseconds_(0) {}

    void startTimer(const std::chrono::milliseconds& everyMilliseconds) {
      milliseconds_ = everyMilliseconds;
      timer_.expires_from_now(everyMilliseconds);
      startAsyncWait();
    }

  private:
    void startAsyncWait() {
      timer_.async_wait(MakeCustomAllocationHandler(
        memory_,
        boost::bind(&HandlerWrapper::handleTimerCallback, this->shared_from_this(),
                    boost::asio::placeholders::error)));
    }

    void handleTimerCallback(const boost::system::error_code& e) {
      if (e != boost::asio::error::operation_aborted) {
        handler_();
      }
      timer_.expires_at(timer_.expires_at() + milliseconds_);
      startAsyncWait();
    }

    BucketPool<128> memory_;
    boost::asio::steady_timer timer_;
    HandlerT handler_;
    std::chrono::milliseconds milliseconds_;
  };

public:
  AsioTimer(strand_t strand, HandlerT handler)
  : handlerWrapper_(boost::make_shared<HandlerWrapper>(strand, handler)) {}

  void startTimer(const std::chrono::milliseconds& everyMilliseconds) {
    handlerWrapper_->startTimer(everyMilliseconds);
  }

private:
  boost::shared_ptr<HandlerWrapper> handlerWrapper_;
};

template <typename HandlerT, typename PoolT>
class CustomAllocationHandler {
public:
  using allocator_type = ObjectAllocator<HandlerT, PoolT>;

  CustomAllocationHandler(PoolT& memory, HandlerT handler)
  : memory_(memory), handler_(std::move(handler)) {}

  allocator_type get_allocator() const noexcept {
    return allocator_type(memory_);
  }

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

private:
  PoolT& memory_;
  HandlerT handler_;
};

template <typename HandlerT, typename PoolT>
CustomAllocationHandler<HandlerT, PoolT> MakeCustomAllocationHandler(PoolT& memory,
                                                                     HandlerT handler) {
  return CustomAllocationHandler<HandlerT, PoolT>(memory, std::move(handler));
}


int main() {
  boost::asio::io_context ioContext;

  strand_t myStrand(make_strand(ioContext));

  AsioTimer timer(myStrand, [] { std::cout << "timer called" << std::endl; });
  timer.startTimer(std::chrono::milliseconds(20));

  auto fut = std::async([&ioContext] {
    ioContext.run();
  });

  std::this_thread::sleep_for(std::chrono::seconds(1));
  ioContext.stop();
  fut.get();
}

4

1 回答 1

0

我注意到需要进行的分配大小可能会发生变化,例如在将 boost 更新到较新版本时。

情况总是如此,因为这些是实现细节。

这并不是说您无法获得一些有用的信息,但您必须准备好进行更多工作以手动执行这种特定类型的 PGO(配置文件引导优化)。

我想象你为程序设置的一个标志来报告分配大​​小的直方图(可能随着时间/程序阶段) - 甚至可能扣除寿命。

您可以设计一种格式来保存该配置文件并使用它来指导您在后续运行中的池参数。

您甚至可以一路走来使文件可包含,以便您可以在下一次构建时编译配置文件的值。

旁白:一些编译器已经允许 PGO 用于代码生成(参见例如GCC Profile Guided Optimization (PGO) 收集哪些信息以及哪些优化使用它?)。这就是给我这个想法的原因。

于 2021-09-03T20:52:23.083 回答