高效解析mmap文件

时间:2013-06-24 10:34:51

标签: c++ boost io memory-mapped-files boost-iostreams

以下是使用boost创建内存映射文件的代码。

boost::iostreams::mapped_file_source file;  
boost::iostreams::mapped_file_params param;  
param.path = "\\..\\points.pts";  //! Filepath  
file.open(param, fileSize);  
if(file.is_open())  
{  
  //! Access the buffer and populate the ren point buffer  
  const char* pData = file.data();  
  char* pData1 = const_cast<char*>(pData);  //! this gives me all the data from Mmap file  
  std::vector<RenPoint> readPoints;  
  ParseData( pData1, readPoints);
}  

ParseData的实现如下

void ParseData ( char* pbuffer , std::vector<RenPoint>>& readPoints)    
{
  if(!pbuffer)
throw std::logic_error("no Data in memory mapped file");

stringstream strBuffer;
strBuffer << pbuffer;

//! Get the max number of points in the pts file
std::string strMaxPts;
std::getline(strBuffer,strMaxPts,'\n');
auto nSize = strMaxPts.size();
unsigned nMaxNumPts = GetValue<unsigned>(strMaxPts);
readPoints.clear();

//! Offset buffer 
pbuffer += nSize;
strBuffer << pbuffer;
std::string cur_line;
while(std::getline(strBuffer, cur_line,'\n'))
{
       //! How do I read the data from mmap file directly and populate my renpoint structure    
           int yy = 0;
}

//! Working but very slow
/*while (std::getline(strBuffer,strMaxPts,'\n'))
{
    std::vector<string> fragments;

    istringstream iss(strMaxPts);

    copy(istream_iterator<string>(iss),
        istream_iterator<string>(),
        back_inserter<vector<string>>(fragments));

    //! Logic to populate the structure after getting data back from fragments
    readPoints.push_back(pt);
}*/
}  

我说我的数据结构中至少有100万个点,我想优化我的解析。有任何想法吗 ?

4 个答案:

答案 0 :(得分:2)

  1. 读入标题信息以获得分数
  2. 在std :: vector中为N * num_points保留空间(N = 3,假设只有X,Y,Z,6表示法线,9表示法线和rgb)
  3. 将文件的其余部分加载到字符串
  4. boost :: spirit :: qi :: phrase_parse到向量中。
  5. //这里的代码可以在我2岁的macbook上解析一个大约14s的40M点(> 1GB)的文件:

    #include <boost/spirit/include/qi.hpp>
    #include <fstream>
    #include <vector>
    
    template <typename Iter>
    bool parse_into_vec(Iter p_it, Iter p_end, std::vector<float>& vf) {
        using boost::spirit::qi::phrase_parse;
        using boost::spirit::qi::float_;
        using boost::spirit::qi::ascii::space;
    
        bool ret = phrase_parse(p_it, p_end, *float_, space, vf);
        return p_it != p_end ? false : ret;
    }
    
    int main(int argc, char **args) {
        if(argc < 2) {
            std::cerr << "need a file" << std::endl;
            return -1;
        }
        std::ifstream in(args[1]);
    
        size_t numPoints;
        in >> numPoints;
    
        std::istreambuf_iterator<char> eos;
        std::istreambuf_iterator<char> it(in);
        std::string strver(it, eos);
    
        std::vector<float> vf;
        vf.reserve(3 * numPoints);
    
        if(!parse_into_vec(strver.begin(), strver.end(), vf)) {
            std::cerr << "failed during parsing" << std::endl;
            return -1;
        }
    
        return 0;
    }
    

答案 1 :(得分:1)

AFAICT,您目前正在将文件的全部内容复制到strBuffer

我认为您想要做的是将boost::iostreams::streammapped_file_source一起使用。

这是一个未经测试的示例,基于链接文档:

// Create the stream
boost::iostreams::stream<boost::iostreams::mapped_file_source> str("some/path/file");
// Alternately, you can create the mapped_file_source separately and tell the stream to open it (using a copy of your mapped_file_source)
boost::iostreams::stream<boost::iostreams::mapped_file_source> str2;
str2.open(file);

// Now you can use std::getline as you normally would.
std::getline(str, strMaxPts);

顺便说一句,我会注意到默认情况下mapped_file_source会映射整个文件,因此无需明确传递大小。

答案 2 :(得分:1)

你可以选择这样的东西(只是一个快速的概念,你需要添加一些额外的错误检查等)。

#include "boost/iostreams/stream.hpp"
#include "boost/iostreams/device/mapped_file.hpp"
#include "boost/filesystem.hpp"
#include "boost/lexical_cast.hpp"

double parse_double(const std::string & str)
{
  double value = 0;
  bool decimal = false;
  double divisor = 1.0;
  for (std::string::const_iterator it = str.begin(); it != str.end(); ++it)
  {
    switch (*it)
    {
    case '.':
    case ',':
      decimal = true;
      break;
    default:
      {
        const int x = *it - '0';
        value = value * 10 + x;
        if (decimal)
          divisor *= 10;
      }
      break;
    }
  }
  return value / divisor;
}


void process_value(const bool initialized, const std::string & str, std::vector< double > & values)
{
  if (!initialized)
  {
    // convert the value count and prepare the output vector
    const size_t count = boost::lexical_cast< size_t >(str);
    values.reserve(count);
  }
  else
  {
    // convert the value
    //const double value = 0; // ~ 0:20 min
    const double value = parse_double(str); // ~ 0:35 min
    //const double value = atof(str.c_str()); // ~ 1:20 min
    //const double value = boost::lexical_cast< double >(str); // ~ 8:00 min ?!?!?
    values.push_back(value);
  }
}


bool load_file(const std::string & name, std::vector< double > & values)
{
  const int granularity = boost::iostreams::mapped_file_source::alignment();
  const boost::uintmax_t chunk_size = ( (256 /* MB */ << 20 ) / granularity ) * granularity;
  boost::iostreams::mapped_file_params in_params(name);
  in_params.offset = 0;
  boost::uintmax_t left = boost::filesystem::file_size(name);
  std::string value;
  bool whitespace = true;
  bool initialized = false;
  while (left > 0)
  {
    in_params.length = static_cast< size_t >(std::min(chunk_size, left));
    boost::iostreams::mapped_file_source in(in_params);
    if (!in.is_open())
      return false;
    const boost::iostreams::mapped_file_source::size_type size = in.size();
    const char * data = in.data();
    for (boost::iostreams::mapped_file_source::size_type i = 0; i < size; ++i, ++data)
    {
      const char c = *data;
      if (strchr(" \t\n\r", c))
      {
        // c is whitespace
        if (!whitespace)
        {
          whitespace = true;
          // finished previous value
          process_value(initialized, value, values);
          initialized = true;
          // start a new value
          value.clear();
        }
      }
      else
      {
        // c is not whitespace
        whitespace = false;
        // append the char to the value
        value += c;
      }
    }
    if (size < chunk_size)
      break;
    in_params.offset += chunk_size;
    left -= chunk_size;
  }
  if (!whitespace)
  {
    // convert the last value
    process_value(initialized, value, values);
  }
  return true;
}

请注意,您的主要问题是从字符串到float的转换,这非常慢(在boost :: lexical_cast的情况下非常慢)。使用我的自定义特殊parse_double功能它更快,但它只允许一种特殊的格式(例如,如果允许负值,你需要添加符号检测等等 - 或者如果需要所有可能的格式,你可以使用atof)。

如果你想更快地解析文件,你可能需要进行多线程处理 - 例如,一个线程只解析字符串值,而另一个或多个线程将加载的字符串值转换为浮点数。在这种情况下,您可能甚至不需要内存映射文件,因为常规缓冲文件读取可能就足够了(文件将只读取一次)。

答案 3 :(得分:0)

对您的代码进行一些快速评论: 1)你没有为你的矢量保留空间,所以每次你添加一个值时都会进行扩展。您已经从文件中读取了点数,因此在clear()之后调用reserve(N)。

2)你在一次点击中强制整个文件的映射,它将在64位上工作,但可能很慢并且正在强制用strBuffer&lt;&lt;&lt;&lt;&lt; p缓冲器;

http://www.boost.org/doc/libs/1_53_0/doc/html/interprocess/sharedmemorybetweenprocesses.html#interprocess.sharedmemorybetweenprocesses.mapped_file.mapped_file_mapping_regions显示了如何getRegion

使用循环getRegion来加载包含许多行的估计数据块。您将不得不处理部分缓冲区 - 每个getRegion都可能以您需要保留的行的一部分结束并加入到下一个区域的下一个部分缓冲区。

相关问题