【问题标题】:How can I get the sizes of the allocations that needs to be done for this handler?如何获得需要为此处理程序完成的分配大小?
【发布时间】:2021-11-01 22:25:21
【问题描述】:

以下代码使用提供自定义分配器的处理程序。分配器提供了一个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();
}

【问题讨论】:

    标签: c++ templates boost-asio allocation boost-pool


    【解决方案1】:

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

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

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

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

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

    您甚至可以一直使用该文件并将文件包含在内,以便您可以在下一次构建时编译配置文件中的值。

    旁白:一些编译器已经允许 PGO 进行代码生成(参见例如What information does GCC Profile Guided Optimization (PGO) collect and which optimizations use it?)。这就是给我这个想法的原因。

    【讨论】:

    • 我不是在寻找某个类型 T 通过调用 T* allocate(const std::size_t numElements) const { return static_cast&lt;T*&gt;(bucketPool_.template alloc&lt;sizeof(T)&gt;(numElements)); } 分配了多少次。我正在寻找需要在BucketPool&lt;128&gt; memory_; 中指定的模板参数。如果您将128 更改为其他内容,则程序无法编译。例如,在这种情况下 (godbolt.org/z/6Es8Y937v) 和其他版本的 clang 需要为 144。
    • 所以,你确实是在静态编译大小之后。在我看来,编译器很方便地告诉你大小。您可以显然深入研究导致编译器检测错误并复制该错误以确定所需大小的实现细节。但是,如果这是一项优化工作,我会毫不犹豫地为支持的平台手动配置大小(或者设计一个分配器来满足所有可能的值而无需额外的运行时开销)
    • 快速扫描告诉我实际大小主要随完成处理程序类型(即调用者定义)和执行程序类型(同上)而变化。所有这些都是实现定义的,所以如果你想以任何方式依赖它们,你必须努力为它们做准备。如果您的接口允许用户定义的处理程序和执行程序,您可能希望分配器能够自动支持所有可能分组在多个池中的分配大小。
    猜你喜欢
    • 2012-09-13
    • 1970-01-01
    • 1970-01-01
    • 2017-03-15
    • 1970-01-01
    • 1970-01-01
    • 2017-12-29
    • 1970-01-01
    • 2021-07-27
    相关资源
    最近更新 更多