环绕文件映射会导致性能下降

ron*_*nag 13 c++ windows boost interprocess file-mapping

我有一个循环缓冲区,支持文件映射内存(缓冲区的大小范围为8GB-512GB).

我从开始到结束以顺序的方式写入(8个实例)此内存,此时它循环回到开头.

它工作正常,直到它需要执行两个文件映射并循环内存,此时IO性能完全被破坏并且无法恢复(即使在几分钟后).我无法弄明白.

using namespace boost::interprocess;

class mapping
{
public:

  mapping()
  {
  }

  mapping(file_mapping& file, mode_t mode, std::size_t file_size, std::size_t offset, std::size_t size)
    : offset_(offset)
    , mode_(mode)
  {     
    const auto aligned_size         = page_ceil(size + page_size());
    const auto aligned_file_size    = page_floor(file_size);
    const auto aligned_file_offset  = page_floor(offset % aligned_file_size);
    const auto region1_size         = std::min(aligned_size, aligned_file_size - aligned_file_offset);
    const auto region2_size         = aligned_size - region1_size;

    if (region2_size)
    {
      const auto region1_address  = mapped_region(file, read_only, 0, (region1_size + region2_size) * 2).get_address(); 
      const auto region2_address  = reinterpret_cast<char*>(region1_address) + region1_size;  

      region1_ = mapped_region(file, mode, aligned_file_offset, region1_size, region1_address);
      region2_ = mapped_region(file, mode, 0,                   region2_size, region2_address);
    }
    else
    {
      region1_ = mapped_region(file, mode, aligned_file_offset, region1_size);
      region2_ = mapped_region();
    }

    size_ = region1_.get_size() + region2_.get_size();
    offset_ = aligned_file_offset;
  }

  auto offset() const   -> std::size_t  { return offset_; }
  auto size() const     -> std::size_t  { return size_; }
  auto data() const     -> const void*  { return region1_.get_address(); }
  auto data()           -> void*        { return region1_.get_address(); }
  auto flush(bool async = true) -> void
  {
    region1_.flush(async);
    region2_.flush(async);
  }
  auto mode() const -> mode_t { return mode_; }

private:
  std::size_t   offset_ = 0;
  std::size_t   size_ = 0;
  mode_t        mode_;
  mapped_region region1_;
  mapped_region region2_;
};

struct loop_mapping::impl final
{     
  std::tr2::sys::path         file_path_;
  file_mapping                file_mapping_;    
  std::size_t                 file_size_;
  std::size_t                 map_size_     = page_floor(256000000ULL);

  std::shared_ptr<mapping>    mapping_ = std::shared_ptr<mapping>(new mapping());
  std::shared_ptr<mapping>    prev_mapping_;

  bool                        write_;

public:
  impl(std::tr2::sys::path path, bool write)
    : file_path_(std::move(path))
    , file_mapping_(file_path_.string().c_str(), write ? read_write : read_only)
    , file_size_(page_floor(std::tr2::sys::file_size(file_path_)))
    , write_(write)
  {     
    REQUIRE(file_size_ >= map_size_ * 3);
  }

  ~impl()
  {
    prev_mapping_.reset();
    mapping_.reset();
  }

  auto data(std::size_t offset, std::size_t size, boost::optional<bool> write_opt) -> void*
  { 
    offset = offset % page_floor(file_size_);

    REQUIRE(size < file_size_ - map_size_ * 3);

    const auto write = write_opt.get_value_or(write_);

    REQUIRE(!write || write_);          

    if ((write && mapping_->mode() == read_only) || offset < mapping_->offset() || offset + size >= mapping_->offset() + mapping_->size())
    {
      auto new_mapping = std::make_shared<loop::mapping>(file_mapping_, write ? read_write : read_only, file_size_, page_floor(offset), std::max(size + page_size(), map_size_));

      if (mapping_)
        mapping_->flush((new_mapping->offset() % file_size_) < (mapping_->offset() % file_size_));

      if (prev_mapping_)
        prev_mapping_->flush(false);

      prev_mapping_ = std::move(mapping_);
      mapping_    = std::move(new_mapping);
    }

    return reinterpret_cast<char*>(mapping_->data()) + offset - mapping_->offset();
  }
}
Run Code Online (Sandbox Code Playgroud)

-

// 8 processes to 8 different files 128GB each.
loop_mapping loop(...);
for (auto n = 0; true; ++n)
{
     auto src = get_new_data(5000000/8);
     auto dst = loop.data(n * 5000000/8, 5000000/8, true);
     std::memcpy(dst, src, 5000000/8); // This becomes very slow after loop around.
     std::this_thread::sleep_for(std::chrono::seconds(1));
}
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

目标系统:

  • 1x 3TB Seagate Constellation ES.3
  • 2x Xeon E5-2400(6芯,2.6Ghz)
  • 6x 8GB DDR3 1600Mhz ECC
  • Windows Server 2012

Dam*_*mon 1

在具有 48GiB 物理内存的系统上,8 个缓冲区,每个缓冲区的大小为 8 到 512GiB,这意味着必须交换映射。这并不奇怪。
正如您自己已经评论过的那样,问题在于,在能够写入页面之前,您遇到了错误,并且该页面被读入。第一次运行时不会发生这种情况,因为只有一个零页面用过的。更糟糕的是,再次读入页面会与脏页面的后写竞争。

现在,不幸的是,没有办法告诉 Windows “无论如何我都要覆盖它”,也没有办法让磁盘更快地加载你的东西。但是,您可以提前开始传输(也许当您已通过缓冲区的 3/4 时)。

Windows Server 2012(您正在使用的)支持PrefetchVirtualMemory,它是 POSIX 的半途而废的替代品madvise(MADV_WILLNEED)

当然,当您已经知道无论如何都会覆盖整个内存页(或其中几个)时,这并不完全是您想要做的,但它是您能得到的最好的结果。无论如何,这都值得一试。

理想情况下,您希望madvise(MADV_DONTNEED)在覆盖页面之前立即执行类似在 Linux(我相信 FreeBSD 也)下实现的破坏性操作,但我不知道在 Windows 下执行此操作的任何方法(...缺少从头开始破坏视图和映射和映射,但随后你会丢弃所有数据,所以这有点无用)。

即使提前预取,您仍然会受到磁盘 I/O 带宽的限制,但至少可以隐藏延迟。

另一个“明显”(但可能没那么容易)的解决方案是让消费者更快。这将允许一开始就使用较小的缓冲区,甚至在一个巨大的缓冲区上,它也会使工作集保持较小(生产者和消费者在访问它们时都会将页面强制写入 RAM,因此,如果消费者在生产者访问数据后以较小的延迟访问数据)编写它们时,它们都将使用大部分相同的页面集。)较小的工作集更容易装入 RAM。
但我意识到您可能没有无缘无故地选择了几千兆字节的缓冲区。