boost::interprocess::managed_mapped_file 字符串双端队列用完 space

boost::interprocess::managed_mapped_file deque of strings runs out of space

我正在使用 boost::interprocess::deque 和 memory_mapped_file 作为文件缓冲区,让数据在重启等情况下仍然存在。

我正在这样创建缓冲区:

typedef boost::interprocess::allocator<char, boost::interprocess::managed_mapped_file::segment_manager> char_allocator_type;
typedef boost::interprocess::basic_string<char, std::char_traits<char>, char_allocator_type> persisted_string_type;
typedef boost::interprocess::allocator<persisted_string_type, boost::interprocess::managed_mapped_file::segment_manager> persisted_string_allocator_type;
typedef boost::interprocess::deque<persisted_string_type , persisted_string_allocator_type> deque_buf;

auto* mmf = new boost::interprocess::managed_mapped_file(boost::interprocess::open_or_create, "file_name", 1000000);
auto* buffer = mmf.find_or_construct<deque_buf>(boost::interprocess::unique_instance)(mmf_->get_segment_manager());

我这样写到缓冲区的后面:

try {
   char_allocator_type ca(mmf->get_segment_manager());
   persisted_string_type persisted_string(ca);
   persisted_string = "some string";
   buffer->push_back(persisted_string);
} catch (const boost::interprocess::bad_alloc &e) {
   //buffer full, handle it
}

我像这样从缓冲区的前面擦除:

buffer->pop_front();

我像队列一样使用它。因此,每当我无法向背面添加新数据时,我都会从前面擦除数据,直到可以添加新数据为止。 但是,随着它的运行,我必须从前面擦除越来越多的数据,以便在后面添加新数据。最终双端队列几乎不能包含任何元素。增加映射文件大小只会推迟问题。

我做错了什么?

Rgds 克劳斯

首先:为您的代码添加一些平静和安静:)

#include <boost/container/scoped_allocator.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/deque.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>

namespace bip = boost::interprocess;
namespace bc = boost::container;

using Segment = bip::managed_mapped_file;
using Mgr     = Segment::segment_manager;
template <typename T>
using Alloc = bc::scoped_allocator_adaptor<bip::allocator<T, Mgr>>;

using String =
    bip::basic_string<char, std::char_traits<char>, Alloc<char>>;

template <typename T> using Deque = bip::deque<T, Alloc<T>>;

这有助于表达代码。 scoped_allocator_adaptor 减少了您明确(低效地)在分配器实例周围改组的次数:

int main() {
    using Buffers = Deque<String>;

    bip::managed_mapped_file mmf(bip::open_or_create, "file_name", 1 << 20); // 1 MiB

    auto& buffer = *mmf.find_or_construct<Buffers>(bip::unique_instance)(
        mmf.get_segment_manager());

    try {
       buffer.emplace_back("some string");
    } catch (const boost::interprocess::bad_alloc &e) {
       //buffer full, handle it
    }
}

问题

问题可能是碎片化。这里没有真正的解决方案,除非您有能力定期重新填充共享内存段。

另一部分是段管理器开销:

  • How much memory should 'managed_shared_memory' allocate? (boost)
  • 和我自己的答案根据 containers/allocation 模式衡量不同的开销:Bad alloc is thrown This answer also illustrates the effect of fragmentation:

需要考虑的一件事是为字符串使用一个池。通过使用固定大小的分配器,您将在很大程度上避免碎片化问题。

还要记住使用 reserve()shrink_to_fit() 来指导如何管理实际分配模式的实施。

循环缓冲区

但与您的用例更直接匹配的似乎是 ring buffer。我会画出类似的东西:

using Buffer = boost::container::small_vector<char, 100, Alloc<char>>;

template <typename T> using Ring = boost::circular_buffer<T, Alloc<T>>;

如果您事先知道缓冲区的最大大小,您可以使用 static_vector 代替:

using Buffer = boost::container::static_vector<char, 100>;

静态向量从不分配,所以它甚至不需要分配器。

circular_buffer 与作用域分配器的工作效果不尽如人意,因此使用起来有点痛苦,但可能仍然比手动分配器洗牌更优雅。

Live On Coliru

#include <boost/circular_buffer.hpp>
#include <boost/container/small_vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>

namespace bip = boost::interprocess;
namespace bc = boost::container;

using Segment = bip::managed_mapped_file;
using Mgr     = Segment::segment_manager;
template <typename T>
using Alloc       = bip::allocator<T, Mgr>;
using Buffer      = boost::container::small_vector<char, 100, Alloc<char>>;
using BufferAlloc = Buffer::allocator_type;

template <typename T> using Ring = boost::circular_buffer<T, Alloc<T>>;

static inline std::string_view as_sv(Buffer const& b) {
    return {b.data(), b.size()};
}

#include <iomanip>
#include <iostream>
#include <ranges>
namespace v = std::ranges::views;
using std::ranges::subrange;

int main()
{
    using Buffers = Ring<Buffer>;

    bip::managed_mapped_file mmf(bip::open_or_create, "file_name", 1 << 20); // 1 MiB

    auto& sequence = *mmf.find_or_construct<size_t>(bip::unique_instance)(0);
    auto& buffers = *mmf.find_or_construct<Buffers>(bip::unique_instance)(
        1000, // 1000 buffers capacity
        mmf.get_segment_manager());

    auto to_buffer =
        [a = BufferAlloc(buffers.get_allocator())](std::string_view str) {
            return Buffer(str.begin(), str.end(), a);
        };

    int64_t num = buffers.size(); // signed!
    std::cout << "Current size: " << buffers.size() << " ...";

    auto last5 = subrange(buffers) | v::drop(std::max(0l, num - 5));
    for (auto b : last5 | v::transform(as_sv)) {
        std::cout << " " << std::quoted(b);
    }
    std::cout << std::endl;

    try {
       buffers.push_back(to_buffer("some string #" + std::to_string(++sequence)));
    } catch (const boost::interprocess::bad_alloc& e) {
        // buffer full, handle it
        std::cerr << e.what() << "\n";
        return 1;
    }
}

请注意这如何在您不需要任何操作的情况下已经具有 pop_front 行为:

rm file_name; for a in {1..10000}; do if ./sotest; then true; else break; fi; done | nl

版画

     1  Current size: 0 ...
     2  Current size: 1 ... "some string #1"
     3  Current size: 2 ... "some string #1" "some string #2"
     4  Current size: 3 ... "some string #1" "some string #2" "some string #3"
     5  Current size: 4 ... "some string #1" "some string #2" "some string #3" "some string #4"
     6  Current size: 5 ... "some string #1" "some string #2" "some string #3" "some string #4" "some string #5"
     7  Current size: 6 ... "some string #2" "some string #3" "some string #4" "some string #5" "some string #6"
     8  Current size: 7 ... "some string #3" "some string #4" "some string #5" "some string #6" "some string #7"
     9  Current size: 8 ... "some string #4" "some string #5" "some string #6" "some string #7" "some string #8"
    10  Current size: 9 ... "some string #5" "some string #6" "some string #7" "some string #8" "some string #9"
    11  Current size: 10 ... "some string #6" "some string #7" "some string #8" "some string #9" "some string #10"
    12  Current size: 11 ... "some string #7" "some string #8" "some string #9" "some string #10" "some string #11"
    13  Current size: 12 ... "some string #8" "some string #9" "some string #10" "some string #11" "some string #12"
    14  Current size: 13 ... "some string #9" "some string #10" "some string #11" "some string #12" "some string #13"
...
   998  Current size: 997 ... "some string #993" "some string #994" "some string #995" "some string #996" "some string #997"
   999  Current size: 998 ... "some string #994" "some string #995" "some string #996" "some string #997" "some string #998"
  1000  Current size: 999 ... "some string #995" "some string #996" "some string #997" "some string #998" "some string #999"
  1001  Current size: 1000 ... "some string #996" "some string #997" "some string #998" "some string #999" "some string #1000"
  1002  Current size: 1000 ... "some string #997" "some string #998" "some string #999" "some string #1000" "some string #1001"
  1003  Current size: 1000 ... "some string #998" "some string #999" "some string #1000" "some string #1001" "some string #1002"
  1004  Current size: 1000 ... "some string #999" "some string #1000" "some string #1001" "some string #1002" "some string #1003"
  1005  Current size: 1000 ... "some string #1000" "some string #1001" "some string #1002" "some string #1003" "some string #1004"
  1006  Current size: 1000 ... "some string #1001" "some string #1002" "some string #1003" "some string #1004" "some string #1005"
  1007  Current size: 1000 ... "some string #1002" "some string #1003" "some string #1004" "some string #1005" "some string #1006"
...
  9998  Current size: 1000 ... "some string #9993" "some string #9994" "some string #9995" "some string #9996" "some string #9997"
  9999  Current size: 1000 ... "some string #9994" "some string #9995" "some string #9996" "some string #9997" "some string #9998"
 10000  Current size: 1000 ... "some string #9995" "some string #9996" "some string #9997" "some string #9998" "some string #9999"