我遇到了一个很好的策略来减少以下问题的内存分配:
我正在建造一棵树。一开始,我只有一个包含一些数据的根(索引列表(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();
}
答案 0 :(得分:3)
如果每个节点都必须自己维护一个动态列表,那么您可以在调用所有push_back()
之前使用std::vector::reserve()
。
但是,如果设置了根并且初始向量保持不变,则确定整个长度,然后您只需将其拆分即可。在每个节点之间,节点本身可以简单地保存指向初始向量内部数据的指针 - 从而消除了围绕此数据结构的几乎所有分配。
答案 1 :(得分:0)
基本上如果你不能reserve
基于某些启发式的向量,你就会成为Schlemiel's algorithm的受害者(虽然这是一个较温和的版本,因为几何增长确保了{{1}的O(n)时间复杂度连续插入而不是O(n ^ 2))。
但是,如果首先通过节点的索引并记录给定的索引是否应该转到左子节点,右子节点或两者,那么你可以获得恒定数量的分配。还要跟踪左/右子节点的索引计数:
n
这样您只需进行3次分配,即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]);
}
,foo
,leftNodes
。虽然你必须遍历指数两次,但是重量提升(几何计算)只在第一个循环中完成。