【问题标题】:Memory optimization in huge data set大数据集中的内存优化
【发布时间】:2011-11-21 12:15:06
【问题描述】:

总之,我已经实现了一些功能,并且想问一些基本的事情,因为我对 C++ 没有扎实的基础知识。我希望,你们都好心地告诉我什么应该是我可以向你们学习的好方法。 (拜托,这不是作业,我身边没有任何专家来问这个问题)

我所做的是;我从一个文件中读取输入 x、y、z 点数据(大约 3GB 数据集),然后为每个点计算一个值并存储在一个向量中(结果)。然后,它将在下一个循环中使用。然后,该向量将不再使用,我需要获取该内存,因为它包含大量数据集。我想我可以通过两种方式做到这一点。 (1) 只需初始化一个向量,然后再将其擦除(参见代码 1)。 (2) 通过分配动态内存,然后再取消分配(参见代码 2)。我听说这种取消分配效率低下,因为取消分配会再次消耗内存,或者我可能误解了。

Q1) 我想知道在内存和效率方面的优化方式是什么。

Q2) 另外,我想知道按引用返回的函数是否是提供输出的好方法。 (请看code-3)

代码-1

int main(){

    //read input data (my_data)

    vector<double) result;
    for (vector<Position3D>::iterator it=my_data.begin(); it!=my_data.end(); it++){

         // do some stuff and calculate a "double" value (say value)
         //using each point coordinate 

         result.push_back(value);

    // do some other stuff

    //loop over result and use each value for some other stuff
    for (int i=0; i<result.size(); i++){

        //do some stuff
    }

    //result will not be used anymore and thus erase data
    result.clear()

代码-2

int main(){

    //read input data

    vector<double) *result = new vector<double>;
    for (vector<Position3D>::iterator it=my_data.begin(); it!=my_data.end(); it++){

         // do some stuff and calculate a "double" value (say value)
         //using each point coordinate 

         result->push_back(value);

    // do some other stuff

    //loop over result and use each value for some other stuff
    for (int i=0; i<result->size(); i++){

        //do some stuff
    }

    //de-allocate memory
    delete result;
    result = 0;
}

code03

vector<Position3D>& vector<Position3D>::ReturnLabel(VoxelGrid grid, int segment) const
{
  vector<Position3D> *points_at_grid_cutting = new vector<Position3D>;
  vector<Position3D>::iterator  point;

  for (point=begin(); point!=end(); point++) {

       //do some stuff         

  }
  return (*points_at_grid_cutting);
}

【问题讨论】:

    标签: c++


    【解决方案1】:

    对于如此庞大的数据集,我将完全避免使用 std 容器并使用内存映射文件。

    如果您更喜欢使用 std::vector,请使用 vector::clear()vector::swap(std::vector()) 释放分配的内存。

    【讨论】:

    • swap 调用不起作用,因为它需要非常量引用。
    • @Kerrek SB:我没听懂你。问题和示例中没有关于 const 的内容,事实上,在许多 std 容器实现中,clear() 是在 swap() 的帮助下实现的。
    • @Kirill: std::vector&lt;something&gt;() 是一个临时对象。它不能绑定到非常量引用。 vector::swap 的参数是一个非常量引用。所以不能写myvector.swap(std::vector&lt;something&gt;());
    • @KerrekSB:对不起,我的错。我只是忘记了这个把戏是什么样子的。右边:std::vector&lt;T&gt;().swap(instance);
    【解决方案2】:

    erase不会释放用于向量的内存。它减小了大小但没有减小容量,因此向量仍然为所有这些双精度数保留足够的内存。

    使内存再次可用的最佳方法就像您的代码 1,但让向量超出范围:

    int main() {
        {
            vector<double> result;
            // populate result
            // use results for something
        }
        // do something else - the memory for the vector has been freed
    }
    

    否则,清除向量并释放内存的惯用方法是:

    vector<double>().swap(result);
    

    这会创建一个空的临时向量,然后将其内容与result 交换(因此result 为空且容量小,而临时具有所有数据且容量大)。最后,它销毁了临时文件,并带走了大缓冲区。

    关于 code03:通过引用返回动态分配的对象并不是一种好的风格,因为它不会向调用者提供太多的提醒,即他们有责任释放它。通常最好的办法是按值返回一个局部变量:

    vector<Position3D> ReturnLabel(VoxelGrid grid, int segment) const
    {
      vector<Position3D> points_at_grid_cutting;
      // do whatever to populate the vector
      return points_at_grid_cutting;
    }
    

    原因是,如果调用者使用对该函数的调用作为他们自己的向量的初始化,那么称为“命名返回值优化”的东西就会启动,并确保虽然你是按值返回,但没有副本价值已定。

    没有实现 NRVO 的编译器是一个糟糕的编译器,并且可能会出现各种其他令人惊讶的性能故障,但在某些情况下 NRVO 不适用 - 最重要的是,当将值分配给变量时由调用者而不是在初始化中使用。对此有三个修复:

    1) C++11 引入了移动语义,它基本上通过确保临时分配的成本较低来对其进行排序。

    2) 在 C++03 中,调用者可以玩一个叫做“swaptimization”的技巧。而不是:

    vector<Position3D> foo;
    // some other use of foo
    foo = ReturnLabel();
    

    写:

    vector<Position3D> foo;
    // some other use of foo
    ReturnLabel().swap(foo);
    

    3) 您编写了一个具有更复杂签名的函数,例如通过非常量引用获取vector 并将值填充到其中,或者将 OutputIterator 作为模板参数。后者还为调用者提供了更大的灵活性,因为他们不需要使用vector 来存储结果,他们可以使用其他容器,甚至一次处理一个,而不是一次存储全部。

    【讨论】:

    • 另一个想法可能是使用deque 而不是vector,因为存储的连续性似乎不是 OP 的要求。
    • 同意。这很可能会首先通过所有push_back 调用来加快构建容器的过程,并且在系统有 3GB 可用内存但无法连续分配它的(不寻常)情况下也可能会有所帮助,并且因此不能将它用于向量。
    • 加快所有push_backing 的另一种可能方法是从results.reserve(my_data.size()); 开始
    • @Steve Jessop:感谢您的评论。我学到了很多。这意味着,new 和 delete(即动态内存分配和解除分配)不是让内存再次可用的好习惯吗?以及使用 new 和 delete 的任何示例。
    • @g_niro:没错,使用newdelete 是没有意义的,这就是为什么我没有提供任何示例的原因。
    【解决方案3】:

    您的代码似乎只在第二个循环中不区分上下文地使用第一个循环的计算值。换句话说,一旦在第一个循环中计算出双精度值,就可以立即对其进行操作,而无需一次存储所有值。

    如果是这种情况,您应该以这种方式实现它。无需担心大量分配、存储或任何事情。更好的缓存性能。幸福。

    【讨论】:

    • 我认为这无疑是最好的主意。当然,在 C++ 中使用 iterator 并不像在其他环境中那样糖衣,但它是数据流的基本概念。
    【解决方案4】:
    vector<double) result;
        for (vector<Position3D>::iterator it=my_data.begin(); it!=my_data.end(); it++){
    
             // do some stuff and calculate a "double" value (say value)
             //using each point coordinate 
    
             result.push_back(value);
    

    如果“结果”向量最终会有数千个值,这将导致许多重新分配。最好用足够大的容量来初始化它来存储,或者使用保留功能:

    vector<double) result (someSuitableNumber,0.0);
    

    这将减少重新分配的次数,并可能进一步优化您的代码。

    我也想写:vector&lt;Position3D&gt;&amp; vector&lt;Position3D&gt;::ReturnLabel(VoxelGrid grid, int segment) const

    像这样:

    void vector<Position3D>::ReturnLabel(VoxelGrid grid, int segment, vector<Position3D> & myVec_out) const //myVec_out is populated inside func
    

    您返回引用的想法是正确的,因为您想避免复制。

    【讨论】:

      【解决方案5】:

      `C++ 中的析构函数不能失败,因此释放不会分配内存,因为内存不能通过不抛出保证分配。

      Apart:与其循环多次,不如以集成的方式进行操作,即不是加载整个数据集,然后减少整个数据集,只需逐个读取点,然后应用直接减少,即代替

      load_my_data()
      for_each (p : my_data)
          result.push_back(p)
      
      for_each (p : result)
          reduction.push_back (reduce (p))
      

      做事

      file f ("file")
      while (f)
          Point p = read_point (f)
          reduction.push_back (reduce (p))
      

      如果您不需要存储这些缩减,只需按顺序输出它们

      file f ("file")
      while (f)
          Point p = read_point (f)
          cout << reduce (p)
      

      【讨论】:

        【解决方案6】:

        code-1 可以正常工作,与 code-2 几乎相同,没有主要优点或缺点。

        code03 其他人应该回答这个问题,但我相信在这种情况下,指针和引用之间的区别是微不足道的,不过我更喜欢指针。

        话虽如此,我认为您可能从错误的角度进行优化。您真的需要所有点来计算第一个循环中某个点的输出吗?或者您可以重写您的算法以仅读取一个点,像在第一个循环中那样计算值,然后立即以您想要的方式使用它?也许不是单点,而是成批的点。这可能会减少你的内存,只需要稍微增加处理时间。

        【讨论】:

          猜你喜欢
          • 2019-11-21
          • 2011-08-30
          • 2018-12-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-07-08
          • 1970-01-01
          • 2017-02-06
          相关资源
          最近更新 更多