在标准容器上执行一系列操作时最小化峰值内存使用

Minimizing peak memory usage while performing a series of operations on std containers

我有一个 "pipeline" 函数可以产生最终结果和一些中间结果。我正在寻找一种方法来通过清理不再需要的中间结果来减少峰值内存。

如果我不关心中间结果的记忆,这就是它的样子。 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);
}

这些是我想出的方法:

一个。一旦不再需要,请使用 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);
}

乙。使用 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);
}

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

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

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

不过,要真正解决您的问题,了解更多有关您正在做的事情会有所帮助。您真的需要使用 STD 容器吗?你能用数组代替吗?容器中的对象是否对每个容器都是唯一的,还是同一堆对象被重新排列到不同的容器中?有没有办法将操作分解成更小的数据集,而不是 运行通过一系列类似的容器?

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

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

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