在标准容器上执行一系列操作时最大限度地减少峰值内存使用

Posted

技术标签:

【中文标题】在标准容器上执行一系列操作时最大限度地减少峰值内存使用【英文标题】:Minimizing peak memory usage while performing a series of operations on std containers 【发布时间】:2015-08-29 20:54:11 【问题描述】:

我有一个函数“管道”,可以产生最终结果和一些中间结果。我正在寻找一种通过在不再需要中间结果时立即清理它们来减少峰值内存的方法。

如果我不关心中间结果的记忆,这就是它的外观。 B、C、D、E 和 F 都是 std 容器,每个容器都有超过一百万个对象。

void A::getF(F &f) 
  B b;
  C c;
  computeBAndC(b, c);
  D d;
  computeD(b, c, d);
  // b and c no longer needed
  E e;
  computeE(d, e);
  // d no longer needed
  computeF(e, f);

这些是我想出的方法:

A.不再需要时使用newdelete 进行清理。但是,对 std 容器使用 new 和 delete 有意义吗?

void A::getF(F &f) 
  B *b = new B;
  C *c = new C;
  computeBAndC(b, c);
  D *d = new D;
  computeD(b, c, d);
  // b and c no longer needed
  delete b;
  delete c;
  E e;
  computeE(d, e);
  // d no longer needed
  delete d;
  computeF(e, f);

B.使用 std 容器的swap 函数在使用后清除它们的内存。

void A::getF(F &f) 
  B b;
  C c;
  computeBAndC(b, c);
  D d;
  computeD(b, c, d);
  // b and c no longer needed
  B().swap(b);
  C().swap(c);
  E e;
  computeE(d, e);
  // d no longer needed
  D().swap(d);
  computeF(e, f);

C.使用块。

void A::computeF(F &f) 
  E e;
  
    D d;
    
      B b;
      C c;
      computeBAndC(b, c);
      computeD(b, c, d);
      // b and c no longer needed
    
    computeE(d, e);
    // d no longer needed
  
  computeF(e, f);

D.重组,以便在函数范围结束时删除中间结果:

void A::getF(F &f) 
  E e;
  getE(e);
  computeF(e, f);


void A::getE(E &e) 
  D d;
  getD(d);
  computeE(d, e);


void A::getD(D &d) 
  B b;
  C c;
  computeBAndC(b, c);
  computeD(b, c, d);

这些方法的优缺点是什么?这些真的会减少峰值内存使用吗?有没有更好的办法?

【问题讨论】:

不,不幸的是只有 C++98。 要计算 d 的单个元素,你需要 bc 的所有元素吗?如果不是惰性求值形式可能是您的朋友,尽管 C++98 的限制(真的吗?甚至不是 03!?这是一个 17 岁的标准)使这比必要的更难。 是否可以就地完成任何计算。即不是创建一个全新的容器,而是将以前的容器逐个元素转换为新容器? @DanielJour:是的,我愿意。你能指出我提到的懒惰评估吗?很高兴知道。 @ChrisDrew:感谢您的建议。 computeE 和 computeF 确实可以就地完成。 D、E 和 F 都是相同键的 unordered_map,但值类型不同(set、int 和 float)。我是否可以使用联合作为值类型才能做到这一点? 【参考方案1】:

老实说,我会选择选项 D,因为鉴于您的某些变量之间存在某种联系,将这段代码提取到函数中不仅可以解决您的内存问题,而且还可以更好地使用您的代码本身记录您的意图(我相信你可以在你的代码中想出很好的不言自明的名字)。如果您对函数签名使用更好的方法,唯一的问题当然是性能(假设您不控制 compute 函数:即

E A::computeE(const D& d) 
    E e;
    computeE(d, e);
    return E;

我从您的评论中了解到,您只能使用没有移动语义的 C++98。但是,编译器长期以来一直能够执行命名的返回值优化 - 在你的情况下,我会尝试测试你的编译器使用 NRVO (和复制省略)的能力,如果它很好 - 使用更自然的(IMO ) 我概述的签名 - 否则我会使用您的选项 D。

【讨论】:

【参考方案2】:

如果您在这些容器中存储指向对象的指针,那么删除它们(或只是让它们超出范围)不会释放对象正在使用的内存,只会释放容器本身的内存。您还必须删除容器中的每个对象。

否则,是的,在 STD 容器上使用 new 和 delete 非常有意义。这也意味着在堆而不是堆栈上为它们分配内存,这可能是明智的,因为虽然堆栈有利于快速分配,但如果你在它上面转储太多,你可能会用完。您可能还会遇到分页问题。

不过,要真正解决您的问题,了解更多有关您在做什么的信息会有所帮助。你真的需要使用 STD 容器吗?你能用数组代替吗?容器中的对象是否对每个容器都是唯一的,或者它们是被重新排列到不同容器中的同一组对象?有没有办法将操作分解成更小的数据集,而不是像那样运行一系列容器?

【讨论】:

容器正在存储指向对象的指针。他们指向的对象将在很久以后被释放。如果现在不释放该内存是可以的。我只希望指针本身使用的内存是空闲的,因为会有很多。 std 容器包括堆栈、集合和 unordered_sets。它们指向图中的节点。操作包括拓扑排序、图着色,然后使用拓扑顺序和颜色再次遍历并计算图中每个节点的度量,然后根据该度量对节点进行排序并选择子集等。 无论存储在其中的元素数量如何,甚至这些元素的大小如何,STD 容器都非常小。 (例如,试试sizeof(std::vector<int[100]>)。)容器结构本身只包含几个指针;数据本身是动态分配的。因此,您对堆分配容器的优势的评论毫无意义。并且更改为数组(这几乎可以肯定是一个坏主意)对于所提出的实际问题(即管理对象)将无济于事。

以上是关于在标准容器上执行一系列操作时最大限度地减少峰值内存使用的主要内容,如果未能解决你的问题,请参考以下文章

当我的结账流程有确认页面时,最大限度地减少 PCI 合规性

通过基于 Web 的实时音频捕获和广播,最大限度地减少延迟

持续唤醒和睡眠以最大限度地减少 Linux 上的功耗?

最大限度地减少 Salesforce Streaming API 和 Heroku Connect 之间的延迟

robotium和appium区别

有没有一种好方法可以最大限度地减少 Google Maps Roads API 的负载?