Собственные и огромные плотные 2D массивы - PullRequest
0 голосов
/ 30 июня 2018

Я использую 2D Eigen::Array s для проекта, и мне нравится продолжать использовать их в случае огромных 2D массивов.

Чтобы избежать проблем с памятью, я подумал о том, чтобы использовать сопоставленные с памятью файлы для управления (чтения / изменения / записи) этими массивами, но я не могу найти рабочие примеры.

Самый близкий пример, который я нашел, это это на основе boost::interprocess, но он использует общую память (хотя я бы предпочел иметь постоянное хранилище).

Отсутствие примеров заставляет меня беспокоиться, если есть лучшее альтернативное решение основной проблемы для моей проблемы. Это тот случай? Минимальный пример был бы очень удобен.

EDIT:

Это минимальный пример, объясняющий мой вариант использования в комментариях:

#include <Eigen/Dense>


int main()
{
    // Order of magnitude of the required arrays
    Eigen::Index rows = 50000;
    Eigen::Index cols = 40000;

    {
        // Array creation (this is where the memory mapped file should be created)
        Eigen::ArrayXXf arr1 = Eigen::ArrayXXf::Zero( rows, cols );

        // Some operations on the array
        for(Eigen::Index i = 0; i < rows; ++i)
        {
            for(Eigen::Index j = 0; j < cols; ++j)
            {
                arr1( i, j ) = float(i * j);
            }
        }

        // The array goes out of scope, but the data are persistently stored in the file
    }

    {
        // This should actually use the data stored in the file
        Eigen::ArrayXXf arr2 = Eigen::ArrayXXf::Zero( rows, cols );

        // Manipulation of the array data
        for(Eigen::Index i = 0; i < rows; ++i)
        {
            for(Eigen::Index j = 0; j < cols; ++j)
            {
                arr2( i, j ) += 1.0f;
            }
        }

        // The array goes out of scope, but the data are persistently stored in the file
    }

}

Ответы [ 3 ]

0 голосов
/ 10 июля 2018

Я думаю, что не было бы так сложно написать собственный класс для этого.

Чтобы инициализировать массив в первый раз, создайте файл размером x * y * elem_size и memory map it.

Вы можете даже добавить небольшой заголовок с такой информацией, как размер, x, y и т. Д., Чтобы при повторном открытии вы получили всю необходимую информацию.

Теперь у вас есть один большой блок памяти, и вы можете использовать функцию-член elem(x,y) или get_elem() / set_elem() или использовать [] operator, и в этой функции вычислить положение элемента данных.

Закрытие файла или фиксация между ними сохранит данные.

Для действительно больших файлов может быть лучше map только части файла, когда они необходимы, чтобы избежать создания очень большой таблицы страниц.

Для Windows (не уверен, что они доступны в Linux):

  • Если вам не нужно хранить данные на диске, вы можете открыть файл с флагом delete on close. Это только (временно) записывает на диск, если память становится недоступной.

  • Для разреженных массивов можно использовать разреженный файл. Эти файлы используют дисковое пространство только для блоков, которые содержат данные. Все остальные блоки являются виртуальными и по умолчанию имеют все нули.

0 голосов
/ 14 июля 2018

На основании этого комментария и этих ответов (https://stackoverflow.com/a/51256963/2741329 и https://stackoverflow.com/a/51256597/2741329), это мое рабочее решение:

#include <boost/interprocess/file_mapping.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <Eigen/Dense>
#include <iostream>
#include <fstream>
#include <filesystem>

namespace fs = std::experimental::filesystem;
namespace bi = boost::interprocess;

int main() {

  std::string array_bin_path = "array.bin";
  const int64_t nr_rows = 28000;
  const int64_t nr_cols = 35000;
  const int64_t array_size = nr_rows * nr_cols * sizeof(float);
  std::cout << "array size: " << array_size << std::endl;

  // if the file already exists but the size is different, remove it
  if(fs::exists(array_bin_path))
  {
    int64_t file_size = fs::file_size(array_bin_path);
    std::cout << "file size: " << file_size << std::endl;
    if(array_size != file_size)
    {
      fs::remove(array_bin_path);
    }
  }

  // create a binary file of the required size
  if(!fs::exists(array_bin_path))
  {
    std::ofstream ofs(array_bin_path, std::ios::binary | std::ios::out | std::ios::trunc);
    ofs.seekp(array_size - 1);
    ofs.put(0);
    ofs.close();
  }

  // use boost interprocess to memory map the file
  const bi::file_mapping mapped_file(array_bin_path.c_str(), bi::read_write);
  bi::mapped_region region(mapped_file, bi::read_write);

  // get the address of the mapped region
  void * addr = region.get_address();

  const std::size_t region_size = region.get_size();
  std::cout << "region size: " << region_size << std::endl;

  // map the file content into a Eigen array
  Eigen::Map<Eigen::ArrayXXf> my_array(reinterpret_cast<float*>(addr), nr_rows, nr_cols);

  // modify the content
  std::cout << "initial array(0, 1) value: " << my_array(0, 1) << std::endl;
  my_array(0, 1) += 1.234f;
  std::cout << "final array(0, 1) value: " << my_array(0, 1) << std::endl;

  return 0;
}

Используется:

  • boost::interprocess вместо boost::iostreams, потому что это только заголовок. Кроме того, mapped_region удобно, если я хочу сохранить несколько массивов в одном отображенном файле.
  • std::fstream для создания двоичного файла и std::experimental::filesystem для его проверки.
  • Eigen::ArrayXXf как требуется в моем вопросе.
0 голосов
/ 10 июля 2018

Так что я погуглил

файл с увеличенной памятью

и натолкнулся на boost::iostreams::mapped_file в первом результате.

В сочетании со ссылкой на Eigen::Map из этот комментарий я проверил следующее:

#include <boost/iostreams/device/mapped_file.hpp>
#include <Eigen/Dense>
boost::iostreams::mapped_file file("foo.bin");

const std::size_t rows = 163840;
const std::size_t columns = 163840;
if (rows * columns * sizeof(float) > file.size()) {
    throw std::runtime_error("file of size " + std::to_string(file.size()) + " couldn’t fit float Matrix of " + std::to_string(rows) + "×"  + std::to_string(columns));
}

Eigen::Map<Eigen::MatrixXf> matrix(reinterpret_cast<float*>(file.data()), rows, columns);

std::cout << matrix(0, 0) << ' ' << matrix(rows - 1, columns - 1) << std::endl;
matrix(0, 0) = 0.5;
matrix(rows - 1, columns - 1) = 0.5;

с использованием

find_package(Boost REQUIRED COMPONENTS iostreams)
find_package(Eigen3 REQUIRED)
target_link_libraries(${PROJECT_NAME} Boost::iostreams Eigen3::Eigen)

Тогда я погуглил

Windows создает фиктивный файл

и первый результат дал мне

fsutil file createnew foo.bin 107374182400

Запуск программы дважды дает:

0 0

0,5 0,5

без разрыва использования памяти.

Так что это работает как шарм.

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