本征和巨大的密集二维阵列
Eigen and huge dense 2D arrays
我正在为一个项目使用 2D Eigen::Array
s,我喜欢在大型 2D 数组的情况下继续使用它们。
为了避免内存问题,我想使用内存映射文件来管理(read/modify/write)这些数组,但我找不到工作示例。
我找到的最接近的例子是 this 基于 boost::interprocess
,但它使用共享内存(虽然我更喜欢持久存储)。
缺少示例让我担心是否有更好的主流替代解决方案来解决我的问题。是这样吗?一个最小的例子会非常方便。
编辑:
这是一个最小的例子,在评论中解释了我的用例:
#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
}
}
所以我用谷歌搜索
boost memory mapped file
并在第一个结果中出现 boost::iostreams::mapped_file
。
结合 link 到 Eigen::Map
from 我测试了以下内容:
#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;
使用cmake
find_package(Boost REQUIRED COMPONENTS iostreams)
find_package(Eigen3 REQUIRED)
target_link_libraries(${PROJECT_NAME} Boost::iostreams Eigen3::Eigen)
然后我用谷歌搜索
windows create dummy file
first result 给了我
fsutil file createnew foo.bin 107374182400
运行 程序两次给出:
0 0
0.5 0.5
不会增加内存使用量。
所以它很有魅力。
我认为为此编写自己的 class 并不难。
要首次初始化数组,请创建一个大小为 x * y * elem_size
和 memory map
的文件。
您甚至可以添加一个小的 header,其中包含大小、x、y 等信息 - 这样,如果您重新打开这些信息,您就会获得所需的所有信息。
现在你有一个大内存块,你可以使用成员函数 elem(x,y)
或 get_elem()
/ set_elem()
或使用 [] operator
,并在该函数中计算数据元素的位置。
关闭文件或在两者之间提交,将保存数据。
对于非常大的文件,最好只 map
文件中需要的部分以避免创建非常大的页面 table。
Windows 具体(不确定 Linux 中是否可用):
如果不需要将数据保存在磁盘上,可以使用delete on close
标志打开文件。这只会在内存不可用时(临时)写入磁盘。
对于稀疏数组,可以使用稀疏文件。这些文件只对包含数据的块使用磁盘 space。所有其他块都是虚拟的,默认为全零。
基于this comment and these answers (https://whosebug.com/a/51256963/2741329 and https://whosebug.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
因为它是 header-only。此外,如果我想在单个映射文件中存储多个数组,mapped_region
会很方便。
std::fstream
创建二进制文件并 std::experimental::filesystem
检查它。
Eigen::ArrayXXf
根据我的问题要求。
我正在为一个项目使用 2D Eigen::Array
s,我喜欢在大型 2D 数组的情况下继续使用它们。
为了避免内存问题,我想使用内存映射文件来管理(read/modify/write)这些数组,但我找不到工作示例。
我找到的最接近的例子是 this 基于 boost::interprocess
,但它使用共享内存(虽然我更喜欢持久存储)。
缺少示例让我担心是否有更好的主流替代解决方案来解决我的问题。是这样吗?一个最小的例子会非常方便。
编辑:
这是一个最小的例子,在评论中解释了我的用例:
#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
}
}
所以我用谷歌搜索
boost memory mapped file
并在第一个结果中出现 boost::iostreams::mapped_file
。
结合 link 到 Eigen::Map
from
#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;
使用cmake
find_package(Boost REQUIRED COMPONENTS iostreams)
find_package(Eigen3 REQUIRED)
target_link_libraries(${PROJECT_NAME} Boost::iostreams Eigen3::Eigen)
然后我用谷歌搜索
windows create dummy file
first result 给了我
fsutil file createnew foo.bin 107374182400
运行 程序两次给出:
0 0
0.5 0.5
不会增加内存使用量。
所以它很有魅力。
我认为为此编写自己的 class 并不难。
要首次初始化数组,请创建一个大小为 x * y * elem_size
和 memory map
的文件。
您甚至可以添加一个小的 header,其中包含大小、x、y 等信息 - 这样,如果您重新打开这些信息,您就会获得所需的所有信息。
现在你有一个大内存块,你可以使用成员函数 elem(x,y)
或 get_elem()
/ set_elem()
或使用 [] operator
,并在该函数中计算数据元素的位置。
关闭文件或在两者之间提交,将保存数据。
对于非常大的文件,最好只 map
文件中需要的部分以避免创建非常大的页面 table。
Windows 具体(不确定 Linux 中是否可用):
如果不需要将数据保存在磁盘上,可以使用
delete on close
标志打开文件。这只会在内存不可用时(临时)写入磁盘。对于稀疏数组,可以使用稀疏文件。这些文件只对包含数据的块使用磁盘 space。所有其他块都是虚拟的,默认为全零。
基于this comment and these answers (https://whosebug.com/a/51256963/2741329 and https://whosebug.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
因为它是 header-only。此外,如果我想在单个映射文件中存储多个数组,mapped_region
会很方便。std::fstream
创建二进制文件并std::experimental::filesystem
检查它。Eigen::ArrayXXf
根据我的问题要求。