Подтвердить что ты не робот

Скопление файлов вокруг файлов

У меня есть циклический буфер, который поддерживается файловой памятью (буфер находится в размере 8 ГБ-512 ГБ).

Я записываю (8 экземпляров) эту память последовательным образом от начала до конца, после чего он поворачивается назад к началу.

Он отлично работает, пока не достигнет конца, где ему нужно выполнить два сопоставления файлов и обход вокруг памяти, после чего производительность ввода-вывода полностью уничтожается и не восстанавливается (даже через несколько минут). Я не могу это понять.

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();
  }
}

-

// 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));
}

Любые идеи?

Целевая система:

  • 1x 3TB Seagate Constellation ES.3
  • 2x Xeon E5-2400 (6 ядер, 2,6 ГГц)
  • 6x 8GB DDR3 1600Mhz ECC
  • Windows Server 2012
4b9b3361

Ответ 1

8 буферов размером от 8 до 512 Гбит в системе с 48 ГБ физической памяти означает, что ваше сопоставление должно быть заменено. Не удивительно. Проблема, как вы уже заметили, заключается в том, что до того, как вы можете писать на страницу, вы сталкиваетесь с ошибкой, и страница читается. Это не происходит при первом запуске, поскольку просто нулевая страница используемый. Хуже того, чтение на страницах снова конкурирует с записью грязных страниц.

Теперь, к несчастью, нельзя сказать Windows "Я все равно перезапишу это", и нет способа заставить диск быстрее загружать ваши вещи. Однако вы можете начать передачу раньше (возможно, когда вы 3/4 через буфер).

Windows Server 2012 (который вы используете) поддерживает PrefetchVirtualMemory, который является несколько наполовину замененным для POSIX madvise(MADV_WILLNEED).

Это, конечно, не совсем то, что вы хотите сделать, когда уже знаете, что в любом случае вы перепишете всю страницу памяти (или несколько из них), но это так хорошо, как вы можете получить. В любом случае стоит попробовать.

В идеале вы хотели бы сделать что-то вроде деструктивного madvise(MADV_DONTNEED), как реализовано, например. под Linux (и я считаю, что FreeBSD тоже), прежде чем перезаписывать страницу, но я не знаю, как это сделать в Windows (... не до конца уничтожая представление и сопоставление и сопоставление с нуля, но тогда вы выбросить все данные, так что немного бесполезно).

Даже при предварительной выборке вы все равно будете ограничены пропускной способностью ввода-вывода на диске, но по крайней мере вы можете скрыть задержку.

Другим "очевидным" (но, вероятно, не таким простым) решением было бы сделать потребителя быстрее. Это позволило бы создать меньший буфер, и даже на огромном буфере он оставил бы рабочий набор меньшим (как страницы производителя, так и страницы потребителей в оперативной памяти при доступе к ним, поэтому, если потребитель обращается к данным с меньшей задержкой после того, как производитель они будут использовать в основном один и тот же набор страниц.) Меньшие рабочие наборы легче вписываются в ОЗУ.
Но я понимаю, что вы, вероятно, не выбрали буфер с несколькими гигабайтами без каких-либо причин.

Ответ 2

Так как ваш код лишен каких-либо комментариев, заполненных автоматическими переменными, не компилируемыми, как есть, и у меня нет 512Gb, доступных на моем ПК, чтобы проверить его в любом случае, это будет оставаться утомленным от моей головы.

каждый ваш процесс записывает всего несколько сотен Кбит/с, поэтому должно быть достаточно времени, чтобы очистить его до диска в фоновом режиме.

Однако, похоже, вы просите систему сопоставления форматирования сбрасывать либо синхронно, либо асинхронно предыдущий фрагмент в зависимости от ваших таинственных вычислений смещения:

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

Я думаю, что опрокидывание запускает синхронный флеш, который является вероятным виновником внезапного замедления.

То, что операционная система делает в этот момент, зависит от реализации boost, которая не описана (или, по крайней мере, достаточно очевидна для меня, чтобы получить ее после беглого взгляда на свою страницу руководства). Если бы форсировало ваш 48 Гб памяти с несплошными страницами, вы наверняка испытали бы внезапное и продолжительное замедление.

По крайней мере, стоит комментарий в вашем коде, если эта таинственная линия делает что-то умное и совершенно другое, я пропустил полностью.

Ответ 3

Если вы можете отменить сопоставление памяти с файлом страницы, а не с определенным файлом, вы можете использовать флаг MEM_RESET с VirtualAlloc для предотвращения Windows из пейджинга в старом содержимом.

Основная проблема, которую я ожидал при использовании этого подхода, заключается в том, что вы не можете легко восстановить дисковое пространство, когда закончите. Он также может потребовать изменения параметров файла системной страницы; Я полагаю, что он будет работать с настройками по умолчанию, но не с максимальным размером файла страницы.

Ответ 4

Я собираюсь предположить, что "Loop around" вы подразумеваете, что RAM заполнена. Что происходит, так это то, что до полного заполнения ОЗУ все, что вам нужно сделать, это выделить страницу и записать в нее (скорость ОЗУ), после того, как ОЗУ будет заполнено, каждое распределение страниц превратится в 2 действия: 1. вам нужно написать грязную страницу назад (скорость DISK) 2. и выделите страницу (скорость ОЗУ)

И в худшем случае вам также придется вывести страницу из файла на диске (скорость DISK), если вы что-то читаете. Таким образом, вместо того, чтобы работать только с оперативной скоростью (распределение страниц), каждое распределение страниц выполняется с частотой DISK. Это не происходит с 2x8GB, потому что он достаточно мал, чтобы память обоих файлов оставалась полностью в ОЗУ.

Ответ 5

Проблема здесь заключается в том, что при перезаписи допустимой страницы в памяти сначала нужно прочитать страницу с диска перед ее перезаписью. Невозможно обойти эту проблему, насколько я знаю при использовании файлов с отображением памяти.

Причина, по которой это не происходит во время первого прохода, заключается в том, что перезаписываемые страницы не являются "действительными", и поэтому их не нужно читать.