将文件读入struct时,大小增加10倍

时间:2014-05-29 13:47:25

标签: c++ struct

我正在尝试将csv文件读入包含字符串向量向量的结构中。该文件包含大约200万行,磁盘大小约为350 mb。当我将文件读入struct top向我显示正在读取完整文件时,该程序现在使用了近3.5GB的内存。我使用向量保留试图限制push_back上的向量容量增加。

#include<iomanip>
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<fstream>
#include<string.h>
#include<sstream>
#include<math.h>
#include<vector>
#include<algorithm>
#include<array>
#include<ctime>
#include<boost/algorithm/string.hpp>
using namespace std;

struct datStr{
  vector<string> colNames;
  vector<vector<string>> data;
};

datStr readBoost(string fileName)
{
  datStr ds;
  ifstream inFile;
  inFile.open(fileName);
  string line;
  getline(inFile, line);
  vector<string> colNames;
  stringstream ss(line);
  string item;
  int i = 0;
  vector<int> colTypeInt;
  while(getline(ss, item, ','))
  {
      item.erase( remove( item.begin(), item.end(), ' ' ), item.end() );
      colNames.push_back(item);
      vector<string> colVec;
      ds.data.push_back(colVec);
      ds.data[i].reserve(3000000);
      i++;
  }

  int itr = 0;
  while(getline(inFile, line))
  {
      vector<string> rowStr;
      boost::split(rowStr, line, boost::is_any_of(","));
      for(int ktr = 0; ktr < rowStr.size(); ktr++)
      {
          rowStr[ktr].erase( remove( rowStr[ktr].begin(), rowStr[ktr].end(), ' ' ), rowStr[ktr].end() );
          ds.data[ktr].push_back(rowStr[ktr]);
      }
      itr++;
  }
 int main()
 {
  datStr ds = readBoost("file.csv");
  while(true)
  {
  }
 }

PS:最后while就是这样我可以在程序完成时监控内存使用情况。 这是在使用向量时预期的东西还是我在这里遗漏了什么? 另一个有趣的事实我开始在读取循环中为每个字符串添加大小和容量。令人惊讶的是,它只增加了我在ubuntu顶部显示的1/10?可能是top是误报还是我的编译器分配了太多空间?

4 个答案:

答案 0 :(得分:7)

我使用输入文件测试了您的代码,该文件包含1886850行文字,大小为105M

使用您的代码,内存消耗约为2.5G。

然后,我开始修改数据的存储方式。

第一次测试:

datStr更改为:

struct datStr{
    vector<string> colNames;
    vector<string> lines;
};

这将内存消耗减少到206M。这大小减少了10倍以上。很清楚使用

的惩罚
vector<vector<string>> data;

相当僵硬。

第二次测试:

datStr更改为:

struct datStr{
    vector<string> colNames;
    vector<string> lines;
    vector<vector<string::size_type>> indices;
};

indices跟踪lines中令牌的开始位置。您可以使用linesindices

从每行中提取令牌

通过此更改,内存消耗量达到543MB,但是比原始值小五倍。

第三次测试

dataStr更改为:

struct datStr{
    vector<string> colNames;
    vector<string> lines;
    vector<vector<unsigned int>> indices;
};

通过此更改,内存消耗降至455MB。如果您不希望自己的行数超过或等于UINT_MAX,这应该会有效。

第四次测试

dataStr更改为:

struct datStr{
    vector<string> colNames;
    vector<string> lines;
    vector<vector<unsigned short>> indices;
};

通过此更改,内存消耗降至278MB。如果您不希望您的线条长于或等于USHRT_MAX,这应该有用。对于这种情况,indices的开销非常小,仅为72MB

这是我用于测试的修改后的代码。

#include<iomanip>
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<fstream>
#include<string.h>
#include<sstream>
#include<math.h>
#include<vector>
#include<algorithm>
#include<array>
#include<ctime>
// #include<boost/algorithm/string.hpp>
using namespace std;

struct datStr{
    vector<string> colNames;
    vector<string> lines;
    vector<vector<unsigned short>> data;
};

void split(vector<unsigned short>& rowStr, string const& line)
{
   string::size_type begin = 0;
   string::size_type end = line.size();
   string::size_type iter = begin;
   while ( iter != end)
   {
      ++iter;
      if ( line[iter] == ',' )
      {
         rowStr.push_back(static_cast<unsigned short>(begin));
         ++iter;
         begin = iter;
      }
   }
   if (begin != end )
   {
      rowStr.push_back(static_cast<unsigned short>(begin));
   }
}

datStr readBoost(string fileName)
{
   datStr ds;
   ifstream inFile;
   inFile.open(fileName);
   string line;
   getline(inFile, line);
   vector<string> colNames;
   stringstream ss(line);
   string item;
   int i = 0;
   vector<int> colTypeInt;
   while(getline(ss, item, ','))
   {
      item.erase( remove( item.begin(), item.end(), ' ' ), item.end() );
      ds.colNames.push_back(item);
   }

   int itr = 0;
   while(getline(inFile, line))
   {
      ds.lines.push_back(line);
      vector<unsigned short> rowStr;
      split(rowStr, line);
      ds.data.push_back(rowStr);
   }
}

int main(int argc, char** argv)
{
   datStr ds = readBoost(argv[1]);
   while(true)
   {
   }
}

答案 1 :(得分:2)

您的vector<vector<string>>会受到间接费用(指向动态分配内存的指针),内务管理(支持size() / end() / capacity()的成员)以及内务管理的费用动态内存分配函数内部的四舍五入...如果你看第一个标题为不同字符串长度的实际内存消耗 here的图表,它建议总开销大约为40-45字节每个字符串用于使用G ++ 4.6.2构建的32位应用程序,尽管对于最多约4个字符的字符串,实现可能会低至4个字节。然后有vector开销的浪费......

您可以通过多种方式解决问题,具体取决于您的输入数据和效率需求:

  • 存储vector<std::pair<string, Column_Index>>,其中Column_Index是您编写的一个类,用于记录每个字段出现的字符串中的偏移量

  • 存储vector<std::string>,其中列值填充到已知的最大宽度,如果长度很小,固定和/或类似(例如日期/时间,小金额,年龄),这将有助于大多数< / p>

  • 内存映射文件,然后存储偏移量(但是取消引用/取消内容是一个问题 - 您可以就地执行此操作,例如abc""defabc\"def(无论您支持哪个) - &gt ; abc"deff

  • 使用最后两种方法,你可以用NUL覆盖每个字段之后的字符,如果这对你有用,那么你可以将这些字段视为&#34; C&#34; - 样式ASCIIZ NUL终止字符串

  • 如果某些/所有字段包含类似值的值,例如1.23456789012345678 ...其中文本表示可能比二进制内置类型(floatdouble,{{1}更长}),在存储之前进行转换是有意义的

  • 类似地,如果有一组重复值 - 比如逻辑枚举标识符的字段,您可以将它们编码为整数,或者如果值是重复的但在运行时才知道,则可以创建从递增索引到值的双向映射

答案 2 :(得分:1)

有几件事情浮现在脑海中:

您说您的文件大约有200万行,但您为每列保留了300万strings 的空间。即使你只有一列,这也浪费了很多空间。如果你有一堆列,那就是浪费空间的 ton 。如果你完全没有reserve,那么看看它有多大的空间差异可能会提供信息。

string有一个小的 * ,但是您为200万行文件中的每个字段支付的非零开销。如果您确实需要一次性将所有数据保存在内存中并且导致出现问题,那么实际情况可能是您只使用char*代替{{string。 1}}。但是,如果调整reserve没有帮助,我只会诉诸于此。

* 由于元数据导致的开销很小,但如果string为其内部缓冲区分配额外的容量,那可能真的会增加。请参阅this recent question


更新:您的更新问题是您在std::string中存储指向临时datStr对象的指针。当你开始打印时,那些string已经被破坏,你的指针也很疯狂。

如果您想要一种简单,安全的方式将字符串存储在datStr中并且不会分配超出其需要的空间,您可以使用以下内容:

class TrivialReadOnlyString
{
private:
    char* m_buffer;

public:
    TrivialReadOnlyString(const std::string& src)
    {
       InitFrom(src.c_str());
    }

    TrivialReadOnlyString(const TrivialReadOnlyString& src)
    {
       InitFrom(src.m_buffer);
    }

    ~TrivialReadOnlyString()
    {
       delete[] m_buffer;
    }

    const char* Get()
    {
       return m_buffer;
    }

private:
    void InitFrom(const char* src)
    {
       // Can switch to the safe(r) versions of these functions
       // if you're using vc++ and it complains.
       size_t length = strlen(src);
       m_buffer = new char[length + 1];
       strcpy(m_buffer, src);
    }
};

本课程还有很多进一步的改进,但我认为这足以满足您所需的课程需求。这将比Blastfurnace将整个文件存储在单个缓冲区中的想法更多地分解内存。但是。

如果您的数据中有很多重复,您可能还会考虑&#34;折叠&#34;重复到一个对象,以避免在内存中重复存储相同的字符串(flyweight模式)。

答案 3 :(得分:0)

当我采用非常不同的方法回答你的问题时,请尽情享受。其他人已经很好地回答了你的直接问题,所以让我完全提出另一个观点。

您是否意识到可以使用单个分配将该数据存储在内存中,每行添加一个指针,或者每个单元可能有一个指针?

在32位机器上,这是350MB + 8MB(或8MB *数列)。

您是否知道并行化CSV解析很容易?

你遇到的问题是臃肿的层次和层次。如果您不关心大小或速度,ifstreamstringstreamvector<vector<string>>boost::split都会非常棒。所有这些都可以更直接,更低成本地完成。

在这种情况下,尺寸和速度都很重要,你应该考虑以手动方式做事。使用操作系统中的API读取文件。将其读入单个内存位置,并使用&#39; \ 0&#39;替换逗号或EOL来修改内存。存储指向datStr中那些C字符串的指针,然后就完成了。

您可以针对问题的变体编写类似的解决方案。如果文件对于内存来说太大,您可以将其分段处理。如果您需要将数据转换为浮点等其他格式,那很容易做到。如果您希望并行化解析,那么在您和数据之间没有额外层次的情况下会更容易。

每个程序员都应该能够选择使用便利层或使用更简单的方法。如果你缺乏这个选择,你就无法解决一些问题。