Я пишу контейнер на основе Octree от 10 до 1 миллиарда точек в памяти. Из-за количества загружаемых данных мне нужно следить за потреблением памяти.Octree: Я делаю это неправильно? Получение очень медленной вставки
Все, кажется, работает правильно и сегментировано по мере необходимости, однако время вставки невероятно медленное. Вероятно, из-за перераспределения данных между родителями и детьми. Есть ли что-нибудь, что я могу сделать, чтобы его оптимизировать? Правильно ли я это сделал? Я мог бы зарезервировать вектор в каждом узле, чтобы включить максимальное количество очков, но это значительно увеличит требуемую память.
Используя простой контейнер типа R-tree, я загружаю 468 миллионов точек за 48 секунд. используя octree ниже, я загружаю 245 секунд.
class OctreeNode {
public:
std::vector<std::shared_ptr<OctreeNode>> Children;
std::vector<TPoint> Data;
BoundingBox Bounds;
OctreeNode(){}
OctreeNode(BoundingBox bounds) : Bounds(bounds){
}
~OctreeNode(void){}
void Split();
};
typedef std::shared_ptr<OctreeNode> OctreeNodePtr;
void OctreeNode::Split()
{
Point box[8];
Bounds.Get8Corners(box);
Point center = Bounds.Center;
Children.reserve(8);
Children.push_back(OctreeNodePtr(new OctreeNode(BoundingBox::From(box[0], center))));
Children.push_back(OctreeNodePtr(new OctreeNode(BoundingBox::From(box[1], center))));
Children.push_back(OctreeNodePtr(new OctreeNode(BoundingBox::From(box[3], center))));
Children.push_back(OctreeNodePtr(new OctreeNode(BoundingBox::From(box[2], center))));
Children.push_back(OctreeNodePtr(new OctreeNode(BoundingBox::From(box[5], center))));
Children.push_back(OctreeNodePtr(new OctreeNode(BoundingBox::From(box[4], center))));
Children.push_back(OctreeNodePtr(new OctreeNode(BoundingBox::From(box[6], center))));
Children.push_back(OctreeNodePtr(new OctreeNode(BoundingBox::From(box[7], center))));
}
Octree::Octree(BoundingBox bounds) : Bounds(bounds)
{
_root = OctreeNodePtr(new OctreeNode(bounds));
_root->Split();
}
Octree::~Octree()
{
}
bool Octree::InsertPoint(TPoint &p)
{
return InsertPoint(p, _root);
}
bool Octree::InsertPoint(TPoint &p, const OctreeNodePtr &parent)
{
if (parent->Children.size() != 0){
for (size_t i = 0; i < parent->Children.size(); i++){
OctreeNodePtr ¤tNode = parent->Children[i];
if (currentNode->Bounds.IsContained(p.ToPoint3d())){
return InsertPoint(p, currentNode);
}
}
// Was not able to insert a point.
return false;
}
BoundingBox newBounds = parent->Bounds;
newBounds.Extend(p.ToPoint3d());
// Check for split condition...
if (parent->Data.size() == MaxPerNode && newBounds.XLength() > 0.01){
// Split it...thus generating children nodes
parent->Split();
// Resize the children arrays so that we don't have to keep allocating when redistributing points..
for (size_t i = 0; i < parent->Children.size(); i++){
parent->Children[i]->Data.reserve(parent->Data.size());
}
// Distribute the points that were in the parent to its children..
for (size_t i = 0; i < parent->Data.size(); i++){
TPoint originalPoint = parent->Data[i];
if (!InsertPoint(originalPoint, parent)){
printf("Failed to insert point\n");
break;
}
}
// Insert the current point.
if (!InsertPoint(p, parent)){
printf("Failed to insert point\n");
}
// Resize the arrays back so it fits the size of the data.....
for (size_t i = 0; i < parent->Children.size(); i++){
parent->Children[i]->Data.shrink_to_fit();
}
// clear out the parent information
parent->Data.clear();
parent->Data.shrink_to_fit();
return true;
} else {
// Node is valid so insert the data..
if (parent->Data.size() <= 100000){
parent->Data.push_back(p);
} else {
printf("Too much data in tiny node... Stop adding\n");
}
return true;
}
}
void Octree::Compress(){
Compress(_root);
}
void Octree::Compress(const OctreeNodePtr &parent){
if (parent->Children.size() > 0){
// Look for and remove useless cells who do not contain children or point cloud data.
size_t j = 0;
bool removed = false;
while (j < parent->Children.size()){
if (parent->Children[j]->Children.size() == 0 && parent->Children[j]->Data.size() == 0){
parent->Children.erase(parent->Children.begin() + j);
removed = true;
} else {
Compress(parent->Children[j]);
++j;
}
}
if (removed)
parent->Children.shrink_to_fit();
return;
}
parent->Data.shrink_to_fit();
}
Я не уверен, какова ваша проблема на самом деле, но я могу предложить несколько указателей. Я не уверен, что вам нужно называть 'shrink_to_fit' так много. На самом деле, попробуйте не называть его вообще. Кроме того, с помощью «вектора», который содержит типы указателей, быстрее вызывать 'resize()', а затем использовать назначение по индексам, а не 'reserve' в сочетании с' push_back', когда вы знаете, что будете вставлять несколько элементов. Если вы используете C++ 11 или выше, вы можете использовать 'emplace_back', когда вы создаете элемент на месте, когда вы вставляете его в контейнер, что должно избегать копирования/перемещения. – AndyG
@ AndyG Спасибо за советы, я не думаю, что резерв + push_back на узлах - это замедление. Без shrink_to_fit для дочерних узлов каждый дочерний узел выделял бы до 25000 точек, даже если бы был добавлен только один. Я не уверен, что понимаю, что делает emplace_back, но я изменил data.push_back на emplace_back и время загрузки увеличилось на одну секунду. Я удалил shrink_to_fit для детей, это уменьшило время загрузки на 10 секунд при компромиссе, требующем больше памяти, до тех пор, пока после создания метода Compress не будет вызван метод. – user1000247
Хммм, 'emplace_back' не должен приводить к увеличению времени загрузки. Можете ли вы показать, как вы его использовали? Независимо от того, что разница между 'emplace_back' и' push_back' должна быть минимальной, если вы не попадаете в миллионы. – AndyG