【问题标题】:c++ read file and store integer in vectors. ends up taking around 5 time more resident memory than actual file sizec++ 读取文件并将整数存储在向量中。最终占用的驻留内存比实际文件大小多 5 倍
【发布时间】:2016-07-03 00:37:55
【问题描述】:

我需要读取几个输入文件(每个文件都包含一个二维整数矩阵)并将它们存储在一个二维向量的向量中。下面是我写的代码:

int main(int argc, char *argv[]) {
  /*
    int my_rank;
    int p;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &p);
  */

  std::vector<std::vector<std::vector<int > > > matrices(argc);
  for(int i=1; i<argc; ++i){
      std::string line;
      std::ifstream fp(argv[i]);
      std::vector<std::vector<int> > matrix;

      if (fp.is_open()) {
          while (getline(fp, line)) {
              if(line!=""){
                  //add a new row to file
                  std::vector<int> newRow;
                  //parse each row put the values in the file buffer
                  std::stringstream buff(line);
                  //buffValue is each number in a row
                  int buffValue;
                  while (buff >> buffValue) {
                      newRow.push_back(buffValue);
                  }
                  matrix.push_back(newRow);
              }
          }
      }
      else {
          std::cout << "Failed to read files" << std::endl;
      }
     matrices.push_back(matrix);
  }
  //MPI_Finalize();
  return 0;
}

我有两个问题:

  1. 当我读入一个 175M 的文件时,程序最终在常驻内存中占用了 900M。这是一个问题,因为我通常需要读取 4 个文件,每个文件有几百个 M。它最终会占用多个 G 的内存。这是因为我读取/存储整数的方式吗?

  2. 如果我取消注释涉及 MPI 的行,则常驻内存使用量上升到 1.7G,这是正常的还是我在这里做错了,我正在使用 MPICH。

【问题讨论】:

  • 纯文本文件中的数字1占用一个字节,但int a = 1;可能占用四个字节的内存。只有一个原因。
  • @JonathanPotter 嗨乔纳森,这是否意味着文件中的四位数字 1234 占用与 int a=1234 相同的内存量; ?
  • 代码可能会导致堆碎片化。在创建vector 对象时尝试预分配空间:std::vector&lt;int&gt; newRow(best_guess); 等,其中best_guess 是对要进入的元素数量的猜测。
  • @IanLi ,假设典型的 ASCII 编码和 32 位 int,是的。 4 个字符将是 4 个字节,int 将是 4 个字节。您可能还有一个 64 位(8 字节)int,具体取决于您的平台和编译器选项。
  • 离题:既然您使用的是 MPI,我假设您的目标是速度。因为vectorvectors 不是连续存储(每个vector 指向它自己分配的内存块),当您从一个向量遍历到下一个向量时,您可能会受到性能损失。如果数据中的每一行长度相同,您可以分配一维数组并使用row * numberColumns + column 伪造二维索引。这样可以将所有数据保存在一个大块中,并使 CPU 轻松预测和缓存。

标签: c++ memory-management vector mpi


【解决方案1】:

Vector-of-vector-of-vector 不是一种有效的结构。您有向量类本身的内存开销,以及push_back 的标准行为。

push_back 之后需要调整大小时,vector 的内存将呈指数增长,以满足时间复杂度要求。如果您的向量容量当前为 10 个值,并且您添加 11 个值,那么它很可能会将其容量调整为 20 个值。

这种增长的副作用是潜在的内存碎片。向量内存被定义为连续的。标准分配器没有realloc 能力,就像在 C 中一样。因此,它们必须在别处分配更多内存、移动数据并释放旧存储。这可能会在内存中留下您的程序无法用于其他任何内容的漏洞。更不用说减少数据的缓存局部性,从而导致性能下降。

您最好为矩阵创建一个内存效率更高的二维结构,然后将它们推送到deque 而不是vectorHere's one I prepared earlier ;)。至少,如果你必须对矩阵使用vector-of-vector,那么使用vector::reserve预先分配它。

如果内存对您来说比 I/O 更重要,那么读取文件两次并非不可能。第一次,您会获得有关矩阵大小和行长的信息。然后您预先分配所有结构,并再次读取文件。

否则,使用某种临时池来存储矩阵的值是可以接受的:

std::deque< std::vector< std::vector< int > > > matrices;
std::vector< size_t > columns;  // number of columns, indexed by row
std::vector< int > values;      // all values in matrix

columns.reserve( 1000 );   // Guess a reasonable row count to begin with
values.reserve( 1000000 ); // Guess reasonable value count to begin with

while( getline(fp, line) ) {
    if( line.empty() ) {
        AddMatrix( matrices, columns, values );
    } else {
        std::istringstream iss( line );
        size_t count = 0;
        for( int val; iss >> val; ) {
            values.push_back( val );
            count++;
        }
        columns.push_back( count );
    }
}

// In case last line in file was not empty, add the last matrix.
AddMatrix( matrices, columns, values );

然后添加这样的矩阵:

void AddMatrix( std::deque< std::vector< std::vector< int > > > & matrices,
                std::vector< size_t > & columns,
                std::vector< int > & values )
{
    if( columns.empty() ) return;

    // Reserve matrix rows
    size_t num_rows = columns.size();
    std::vector< std::vector< int > > matrix;
    matrix.reserve( num_rows );

    // Copy rows into matrix
    auto val_it = values.begin();
    for( size_t num_cols : columns )
    {
        std::vector< int > row;
        row.reserve( num_cols );
        std::copy_n( val_it, num_cols, std::back_inserter( row ) );
        matrix.emplace_back( row );
        val_it += num_cols;
    }

    // Clear the column and value pools for re-use.
    columns.clear();
    values.clear();
}

最后,我建议您从&lt;cstdint&gt; 中选择一个合适的整数类型,而不是将其留给编译器。如果您只需要 32 位整数,请使用 int_least32_t。如果您的数据范围适合 16 位整数,则使用 int_least16_t 将节省大量内存。

【讨论】:

  • “创建内存效率更高的二维结构”是什么意思?在这种情况下我应该只使用 3D 双端队列还是更快的插入?并感谢阅读文件两次提示!不知道浏览一个1000万行的文件需要多长时间,我得测试一下!
  • 我的意思是某种可以预分配的结构。矩阵不适合向量的向量,因为它们是矩形的。而vector-of-vector的每一行可以有任意的行长度。
  • 我添加了一些示例代码,以提高读取矢量数据的效率。我使用deque 结构作为一个简单的内存池——在添加矩阵之前保存有关矩阵的临时信息,然后被回收。完全未经测试。没试过编译什么的。
  • 哦,我刚刚意识到池容器应该是vector,因为vector::clear 保证不会释放内存。我不确定deque 是否也是如此。
  • 非常感谢,这个和模板真的帮助我弄清楚了,我会玩它看看!
【解决方案2】:

我猜你看到了两种效果的组合:int 的不同大小 + vector 中的额外内存。

我不确定你是否能看到第一个效果,尽管一个 int 需要大约 4 个字节的内存(我认为他们可以使用 8 个字节,尽管我还没有看到它的实现)。另一端的字符每个数字/字符只占用 1 个字节 + 1 个字节作为空格。所以如果你有很多小整数,内部表示会更大,但如果你有很多大数字,它会更小。 还要检查您是否在比较文件大小或磁盘大小,因为某些文件系统支持压缩!

您最有可能注意到的下一个影响是向量的容量,因为您很可能有很多,这可能会产生相当多的开销。 为了不必重新分配每次插入,std::vector 类有一个容量,这是它实际使用的大小,并将填充您添加的对象。

根据实施情况,容量可以增长。一个例子:每次超过容量翻倍:如果你从容量 10 开始,达到 11,容量可以到 20,如果你达到 21,容量可以到 40 ...(注意:这也是保留很重要的原因,因为它会直接为您提供合适的尺寸)

因此,如果您检查每个单独向量的容量和大小,这可能会有所不同。如果这对您来说真的很戏剧化,您可以在向量上调用 shrink_to_fit 以将容量重新分配到实际存储的大小。

最后,程序的大小也受应用程序本身的影响。我认为它不会在这里产生影响,但如果您碰巧链接了很多共享对象并且所有共享对象都在启动期间加载,那么一些内存测量可以包括这些共享对象的大小作为程序内存的一部分。

【讨论】:

  • 只是想知道如何检查我是否指的是实际文件大小,我想我指的是磁盘上的大小 (ls -lh)。而且我只是尝试使用shrink_to_fit,由于某种原因它没有影响内存使用...
  • 我知道在 Windows 上您可以/可以在该文件的属性中看到。还没查过linux
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-30
  • 1970-01-01
  • 1970-01-01
  • 2014-02-05
  • 1970-01-01
相关资源
最近更新 更多