【问题标题】:is there any way to adopt a memory resource for a vector?有没有办法为向量采用内存资源?
【发布时间】:2021-06-21 16:28:28
【问题描述】:

我已经开始在我的项目中使用 pmr::allocators,并且我已经看到使用它们带来了很多性能提升和优势。我使用的分配器与我在下面的简单示例中展示的非常相似:

#include <array>
#include <boost/container/flat_map.hpp>
#include <cassert>
#include <iostream>
#include <memory_resource>
#include <string>
#include <vector>

struct MessageBody {
  using map_t = boost::container::flat_map<
      char, char, std::less<char>,
      std::pmr::polymorphic_allocator<std::pair<char, char>>>;

  using vector_t = std::vector<char, std::pmr::polymorphic_allocator<char>>;

  MessageBody(std::pmr::memory_resource& mem_v,
              std::pmr::memory_resource& mem_m)
      : vec_(0, &mem_v), map_(&mem_m) {}

  vector_t vec_;
  map_t map_;
};

int main() {
  std::array<char, 1000> buffer;
  buffer.fill('\0');
  std::pmr::monotonic_buffer_resource vec_mem(buffer.data(), 500,
                                              std::pmr::null_memory_resource());
  std::pmr::monotonic_buffer_resource map_mem(buffer.data() + 500, 500,
                                              std::pmr::null_memory_resource());
  {
    MessageBody message(vec_mem, map_mem);
    message.vec_.push_back('1');
    assert(message.vec_.size() == 1);
  }

  {
    MessageBody message(vec_mem, map_mem);
    assert(message.vec_.size() == 1);   /// I want to adopt the previous class for a new class.
  }
}

我的问题是,是否有任何方法可以将 memory_resources 用于另一个类,而无需在 vectormap 中重新填充整个数据。

我能想到的唯一方法(我知道这是一个糟糕的主意)是实现一个继承自 std::vector 的新类,该类有一个采用数据方法来设置 size在不修改缓冲区的情况下将向量内部的大小调整为先前使用的向量的大小。

这里是示例的godbolt链接。

https://godbolt.org/z/fcox5vTdE

【问题讨论】:

  • 我认为你最好使用移动构造来实现这一点。多态分配器就是这样,分配器。没有期望它们在提供给容器时会被解释为一些缓冲区(此外,使用std::pmr::vector
  • @AndyG 也许我应该使用不同的示例。假设您正在从文件或其他形式的 IO 中读取缓冲区。那么移动几乎没有意义。如果可能的话,你想采用记忆并使用它。类似的想法,如 gsl::span for std::vector
  • @apramc 问题说明“为另一个班级”。这似乎排除了从文件或其他 I/O 读取的可能性。顺序不是:从文件读取到第一类,然后将数据从第一类移动到另一个类吗?问题适用于移动而不是阅读?在阅读完成之前,你不会有什么可以采用的。
  • 看来我正确地理解了这里的用例。这不是 pmr 分配器会做的事情。需要有状态的分配器和花式指针both of which are not supported by PMR。可悲,但可以理解。目的并不一致。

标签: c++ boost c++14 c++17 allocator


【解决方案1】:

你想要的不是采用内存资源——因为你可以并且做——而是也将向量本身存储在那里

你可以,例如使用帮助器创建带有自定义删除器的 unique_ptr:

template <typename T>
inline static auto make_T(Mem& mem) {
    std::pmr::polymorphic_allocator<T> alloc(&mem);
    return std::unique_ptr{
        alloc.template new_object<T>(), // allocator is propagated
        [alloc](T* p) { alloc.delete_object(p); }};
}

当库对 C++20 的支持不完整时,需要做更多的工作,所以让我在Compiler Explorer上的现场演示中展示这一点

但是,您无法使用 C++ 内存抽象机模型在同一内存上“复活”非平凡类型。

缓冲区中的活动对象 - 进入 Boost Interprocess

您真正想要的是在那里存储 /live objects/ 并从其他地方访问它们。

您可以使用共享内存分配器来执行此操作,例如来自 Boost Interprocess。你可以这样做

  • 手动分配的缓冲区(类似于单调的内存资源)
  • 托管共享内存段
  • 托管映射文件

当然,在后一种情况下,持久性和多进程访问也有好处。

如果您不需要 IPC 或持久性,但喜欢托管分段功能,请使用 managed_external_buffer

使用托管外部缓冲区的演示

需要几个 typedef 来设置:

namespace Shared {
    using Mem = bip::managed_external_buffer;

    template <typename T>
    using Alloc = boost::container::scoped_allocator_adaptor<
        bip::allocator<T, Mem::segment_manager>>;

    template <typename T>
    using Vector = boost::container::vector<T, Alloc<T> >;

    template <typename K, typename V, typename Cmp = std::less<K>>
    using Map =
        boost::container::flat_map<K, V, std::less<K>, Alloc<std::pair<K, V>>>;
} // namespace Shared

请注意我如何使用 scoped_allocator_adaptor 来尽可能接近 PMR 根据uses_allocator&lt;&gt; 将容器分配器传播到元素类型的行为。

struct MessageBody
{
    using map_t = Shared::Map<char, char>;
    using vector_t = Shared::Vector<char>;

    template <typename Alloc>
    MessageBody(Alloc alloc) : vec_(alloc), map_(alloc)
    { }

    vector_t vec_;
    map_t    map_;
};

不再需要 unique_ptr,您的数据结构基本上就是您拥有的,但我们将所有内容都存储在一个内存资源中,因此整个东西可以“复活”为一个:

int main() {
  std::array<char, 1000> buffer;
  buffer.fill('\0');

  {
      Shared::Mem mem(bip::create_only, buffer.data(), buffer.size());

      auto& message = *mem.find_or_construct<MessageBody>("message")(
          mem.get_segment_manager());

      message.vec_.push_back('1');
      assert(message.vec_.size() == 1);
  }

  {
      Shared::Mem mem(bip::open_only, buffer.data(), buffer.size());

      auto& message = *mem.find_or_construct<MessageBody>("message")(
          mem.get_segment_manager());

      assert(message.vec_.size() == 1);
  }
  std::cout << "Bye" << "\n";
}

get_segment_manager() 调用返回一个指针,该指针用作Shared::Alloc&lt;&gt; 实例的初始化器。

现在它通过了断言:Live On Coliru

只是打印

Bye

【讨论】:

  • 添加使用managed_mapped_file that adds persistence to the picture的demo。请注意 (a) 它现在如何存储一个字符串向量,只是为了炫耀 (b) 它在程序的每次运行时都会增长 (c) 文件是稀疏分配的(不可见,但您可以选择像 10&lt;&lt;30 这样的大小(10 gibibytes)并观察实际磁盘使用情况)。
  • 我希望评论部分的精彩演示不会消失。我可能希望以后能找到它(而且我真的觉得 boost 文档很麻烦)。好东西!
  • @TedLyngmo 该示例实际上只是我的答案代码,但将managed_external_buffer 替换为managed_mapped_file。如果有人需要它并且它已经消失了,我会再次输入它:) 干杯
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-12
  • 2017-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-29
相关资源
最近更新 更多