【问题标题】:Decrease memory allocations C++减少内存分配 C++
【发布时间】:2016-02-09 12:44:33
【问题描述】:

我很难想出一个好的策略来减少以下问题的内存分配:

我正在构建一棵树。一开始,我只有包含一些数据的根(索引列表(std::vector))。我分成两部分,其中一部分索引到左孩子,另一部分到右孩子。我不知道有多少人会去左/右。有一次,我完成了对根的处理,我不再需要为它存储索引。事实上,我只对那些叶子感兴趣。此外,可以在每次拆分时添加其他数据! 所以,如果根有 20 个元素,那么拆分后左边的可能有 12 个元素,右边的有 10 个。

在每个节点中,我保留一个包含这些索引的std::vector。当我添加元素时,我push_back() 导致许多内存分配的元素。

保留索引的好策略是什么?

这个问题与 SBVH 数据结构的生成有关。

代码:

struct Node {
     std::vector<unsigned> indices_;
     // ... some other stuff here
}
void partition(Node* node) {
    Node* left = new Node();
    Node* right = new Node();
    float axis = findSplitPosition(node);

    for(size_t i = 0; i < node->indices_.size(); ++i) {
        if (index is strictly left on axis) {
            left->indices_.push_back(node->indices_[i]);
        } else if(index is strictly right on axis) {
            right->indices_.push_back(node->indices_[i]);
        } else {
            // it intersects the axis -> add to left and right
            left->indices_.push_back(node->indices_[i]);
            right->indices_.push_back(node->indices_[i]);
        }
    }

    // root indices no longer needed
    node->indices_.clear();

}

【问题讨论】:

  • 代码可以解释1000多行
  • std::vector&lt;T&gt;::reserve() 怎么样?
  • 您确定分配时间与整体性能相关吗?还是因为树很大而担心(整个程序、进程)内存消耗?
  • 它确实发挥了作用。树可能包含数百万个索引。树可能会有点深(它不是平衡树)。像这样分离数据会导致更多的缓存未命中,并且内存分配也会减慢构建速度。如果我正在使用另一种构造算法,它或多或少地完成相同的工作,只是它不执行这么多的分配,它会快 3 倍。
  • @МартинРадев:这应该进入你的问题!

标签: c++ memory-management dynamic-memory-allocation bounding-volume


【解决方案1】:

如果每个节点必须自己维护一个动态列表,那么您可以在调用所有这些push_back()s 之前使用std::vector::reserve()

但是,如果在设置根后确定整个长度并且初始向量保持不变,然后您只是在每个节点之间“拆分”它,那么节点本身可以​​简单地保存指向内部数据的指针初始向量——从而消除了围绕该数据结构的几乎所有分配。

【讨论】:

  • 我事先不知道尺寸。根可能有 30 个元素,但在进行拆分后,左 + 右可能总共有 50 个元素。即,某些元素可能会向左和向右。我可以保留记忆,但我必须检查它们,然后数数。我不想重复两次。我知道整个长度。在标准的 BVH 构造中,我只是对元素进行排序并保留开始和结束索引。在这里我不能这样做。
  • @МартинРадев 我的意思是,如果基本的可拆分向量永远不会被修改(添加或删除元素),那么您甚至不必为每个节点维护单独的内存块。您可以在根向量中添加一个偏移量指针 + 大小。
【解决方案2】:

基本上,如果你不能reserve 基于一些启发式的向量,你就会成为Schlemiel's algorithm 的牺牲品(不过,这是一个更温和的版本,因为几何增长确保了n 连续插入的 O(n) 时间复杂度,而不是O(n^2))。

但是,如果您首先检查节点的索引并记录给定的索引是应该分配到左子节点、右子节点还是两者兼而有之,那么您就可以摆脱恒定数量的分配。还要跟踪左/右子节点的索引计数:

struct Foo {
    bool goesIntoLeft;
    bool goesIntoRight;
};

std::vector<Foo> foo;
foo.reserve(node->_indices.size());
int leftCount = 0;
int rightCount = 0;
for (auto i = 0; i < node->_indices.size(); ++i) {
    if (index goes into left) {
        foo.push_back({ true, false });
        ++leftCount;
    } else if (index goes into right) {
        foo.push_back({ false, true });
        ++rightCount;
    } else { // goes into both
        foo.push_back({ true, true });
        ++leftCount;
        ++rightCount;
    }
}

std::vector<Node> leftNodes;
leftNodes.reserve(leftCount);
std::vector<Node> rightNodes;
rightNodes.reserve(rightCount);

for (auto i = 0; i < node->_indices.size(); ++i) {
    if (foo[i].goesIntoLeft) leftNodes.push_back(nodes._indices[i]);
    if (foo[i].goesIntoRight) rightNodes.push_back(nodes._indices[i]);
}

这样你只做3个分配,即fooleftNodesrightNodes。尽管您必须对索引进行两次迭代,但繁重的工作(几何计算)仅在第一个循环中完成。

【讨论】:

    猜你喜欢
    • 2012-10-09
    • 1970-01-01
    • 1970-01-01
    • 2015-09-02
    • 2016-04-19
    • 2015-05-24
    • 2021-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多