【问题标题】:Memory mapped file storage in stl vectorstl向量中的内存映射文件存储
【发布时间】:2020-02-14 01:59:24
【问题描述】:

我正在尝试实现自定义allocator 以将内存映射文件存储在std::vector 中。 boost::iostreams::mapped_file执行的文件映射

文件内存映射的分配器类型:

template<typename T>
class mmap_allocator 
{
public:
  typedef T value_type;

  mmap_allocator(const std::string& filename) 
  : _mmfile(filename) {  } 

  T* allocate (size_t n) 
  { 
     return reinterpret_cast<T*>(_mmfile.data());
  }
  void deallocate (T* p, size_t n) 
  { 
     p = nullptr;
     _mmfile.close();
  }

private:
  boost::iostreams::mapped_file _mmfile;
};

内存映射文件的容器,基于std::vector

//Get file size
long GetFileSize(std::string filename)
{
    FILE *p_file = NULL;
    p_file = fopen(filename.c_str(),"rb");
    fseek(p_file,0,SEEK_END);
    int size = ftell(p_file);
    fclose(p_file);
    return size;
}

template<typename T>
class mm_vector : public std::vector<T, mmap_allocator<T> >
{
public:
  typedef mmap_allocator<T> allocator_type;
  typedef std::vector<T, allocator_type > b_vector;

  mm_vector(const std::string filename) : b_vector(GetFileSize(filename)/sizeof(T), allocator_type(filename)) 
  {  
    b_vector::reserve(GetFileSize(filename)/sizeof(T));
  }
};

测试代码:

int main()
{
  mm_vector<int> v("test.f");//test.f - binary file contain several integers
  for(auto x : v) std::cout<<x<<"  ";
}

此代码无法正常工作 - 输出始终为零。文件包含正确的内容 - 几个整数。此代码运行良好:

boost::iostreams::mapped_file _mmfile("test.f");
int* p = (int*)(_mmfile.data());
std::cout<<p[0];

我做错了什么?

【问题讨论】:

  • GetFileSize 函数中没有任何错误检查。 fopen 和其中的其他功能可能会失败,如果它们失败,它们会返回错误代码以告诉您出了什么问题。你应该检查它们。
  • 您的 allocate() 函数总是返回相同的值。它应该像 malloc() 并在每次调用时返回差异值。

标签: c++ vector stl memory-mapped-files allocator


【解决方案1】:

问题是零初始化,调用接收大小的构造函数,分配器会将向量元素初始化为元素类型的默认值(在本例中为 0)。这是标准规定的。

23.3.7.2 向量构造函数、复制和赋值 [vector.cons] § 23.3.7.2 789

explicit vector(size_type n, const Allocator& = Allocator());

-效果:使用指定的分配器构造一个带有 n 个默认插入元素的向量。
-要求:T 应默认可插入 *this。
- 复杂性:n 中的线性。

在我的情况下,使用的文件也填充了 0。在 GCC 4.9.0 中测试。有逻辑,因为 mapped_file 的默认 mapmode 是 readwrite

在示例代码中,我在分配发生(在自定义分配器中)、向量的构造和存在时添加了映射内存内容的打印主要打印。第一个 print 输出文件的正确数据,第二个输出零版本。

#include <vector>
#include <iostream>
#include <chrono>
#include <iomanip>
#include <boost/iostreams/device/mapped_file.hpp>

template <typename T>
class mmap_allocator {
public:
    typedef T value_type;

    mmap_allocator(const std::string& filename) : _mmfile(filename) {}

    T* allocate(size_t n) {
        std::cout << "OUTPUT 1:" << std::endl;
        auto v = reinterpret_cast<T*>(_mmfile.data());
        for (unsigned long idx = 0; idx < _mmfile.size()/sizeof(int); idx++)
            std::cout << v[idx] << " ";
        return reinterpret_cast<T*>(_mmfile.data());
    }
    void deallocate(T* p, size_t n) {
        p = nullptr;
        _mmfile.close();
    }

private:
    boost::iostreams::mapped_file _mmfile;
};

// Get file size
long GetFileSize(std::string filename) {
    FILE* p_file = NULL;
    p_file = fopen(filename.c_str(), "rb");
    fseek(p_file, 0, SEEK_END);
    int size = ftell(p_file);
    fclose(p_file);
    return size;
}

template <typename T>
class mm_vector : public std::vector<T, mmap_allocator<T>> {
public:
    typedef mmap_allocator<T> allocator_type;
    typedef std::vector<T, allocator_type> b_vector;

    mm_vector(const std::string filename)
        : b_vector(GetFileSize(filename) / sizeof(T),
                   allocator_type(filename)) {
        std::cout << std::endl << std::endl << "OUTPUT 2:" << std::endl;
        for (auto x : *this)
            std::cout << x << "  ";
        b_vector::reserve(GetFileSize(filename) / sizeof(T));
    }
};

int main(int argc, char* argv[]) {
    std::chrono::system_clock::time_point begin_time =
        std::chrono::system_clock::now();

    mm_vector<int> v("H:\\save.txt");
    std::cout << std::endl << std::endl << "OUTPUT 2:" << std::endl;
    for (auto x : v)
        std::cout << x << "  ";

    std::chrono::system_clock::time_point end_time =
        std::chrono::system_clock::now();
    long long elapsed_miliseconds =
        std::chrono::duration_cast<std::chrono::milliseconds>(
            end_time - begin_time).count();
    std::cout << "Duration (min:seg:mili): " << std::setw(2)
              << std::setfill('0') << (elapsed_miliseconds / 60000) << ":"
              << std::setw(2) << std::setfill('0')
              << ((elapsed_miliseconds / 1000) % 60) << ":" << std::setw(2)
              << std::setfill('0') << (elapsed_miliseconds % 1000) << std::endl;
    std::cout << "Total milliseconds: " << elapsed_miliseconds << std::endl;

    return 0;
}

【讨论】:

    【解决方案2】:

    你可能想给

    https://github.com/johannesthoma/mmap_allocator

    试一试。它使用 mmap 文件的内容作为矢量的后备存储,并且是 LGPL,因此您应该能够在您的项目中使用它。请注意,目前 gcc 是必需的,但可以轻松扩展。

    【讨论】:

      【解决方案3】:

      为了明确 NetVipeC 的答案中的建议(在 Johannes Thoma 建议的 mmap_allocator 库的帮助下),如果您使用的是 GNU 标准 C++ 库,则以下替换 mm_vector 类可以防止内存映射向量的内容从被初始化为零(并且消除了对 GetFileSize 函数的需要):

      template <typename T>
      class mm_vector : public std::vector<T, mmap_allocator<T>> {
      public:
          typedef mmap_allocator<T> allocator_type;
          typedef std::vector<T, allocator_type> b_vector;
      
          mm_vector(const std::string filename)
              : b_vector(allocator_type(filename)) {
      
              allocator_type * a = &b_vector::_M_get_Tp_allocator();
              size_t n = a->size() / sizeof(T);
              b_vector::reserve(n);
              // _M_set_finish(n);
              this->_M_impl._M_finish = this->_M_impl._M_end_of_storage = this->_M_impl._M_start + n;
          }
      };
      

      我们通过允许向量的默认大小为 0 进行初始化来防止向量的内容被归零,然后再调整其内部以调整大小。这不太可能是一个完整的解决方案。例如,我还没有检查改变向量大小的操作是否正常工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-23
        相关资源
        最近更新 更多