Seit einigen Tagen versuche ich, meine Force-Directed-Graph-Implementierung zu beschleunigen. Bisher habe ich den Barnes-Hut-Algorithmus implementiert, der Octree verwendet, um die Anzahl der Berechnungen zu reduzieren. Ich habe es mehrfach getestet und die Anzahl der kraftbezogenen Berechnungen ist tatsächlich drastisch verringert. Unten ist das Diagramm der Berechnungen zur Anzahl der Knoten ohne Barns-Hut (blaue Linie) und mit (rote Linie): Auch wenn es jetzt viel schneller sein sollte, ist die Wahrheit, dass in Bezug auf die Geschwindigkeit (Zeit) Das Upgrade ist nur wenige Prozent.Geschwindigkeitsprobleme der Octree-Implementierung
Ein Teil, der vermutlich verursacht wird, ist dies Baumbildung und Elemente in der Baumplatzierung. Da sich Elemente ständig bewegen, muss ich jede Schleife neu erstellen, bis eine Stoppbedingung erreicht ist. Aber wenn ich viel Zeit damit verbringen werde, einen Baum zu schaffen, werde ich dort Zeit verlieren. Das ist zumindest mein Denken. Dies ist, wie ich Elemente in meinem Haupt-Datei Schleife fügt hinzu:
void AddTreeElements(Octree* tree, glm::vec3* boundries, Graph& graph)
{
for(auto& node:graph.NodeVector())
{
node.parent_group = nullptr;
if(node.pos[0] < boundries[1][0] && node.pos[0] > boundries[0][0] &&
node.pos[1] > boundries[4][1] && node.pos[1] < boundries[1][1] &&
node.pos[2] < boundries[0][2] && node.pos[2] > boundries[3][2])
{
tree->AddObject(&node.second);
continue;
}
if(node.pos[0] < boundries[0][0])
{
boundries[0][0] = node.pos[0]-1.0f;
boundries[3][0] = node.pos[0]-1.0f;
boundries[4][0] = node.pos[0]-1.0f;
boundries[7][0] = node.pos[0]-1.0f;
}
else if(node.pos[0] > boundries[1][0])
{
boundries[1][0] = node.pos[0]+1.0f;
boundries[2][0] = node.pos[0]+1.0f;
boundries[5][0] = node.pos[0]+1.0f;
boundries[6][0] = node.pos[0]+1.0f;
}
if(node.pos[1] < boundries[4][1])
{
boundries[4][1] = node.pos[1]-1.0f;
boundries[5][1] = node.pos[1]-1.0f;
boundries[6][1] = node.pos[1]-1.0f;
boundries[7][1] = node.pos[1]-1.0f;
}
else if(node.pos[1] > boundries[0][1])
{
boundries[0][1] = node.pos[1]+1.0f;
boundries[1][1] = node.pos[1]+1.0f;
boundries[2][1] = node.pos[1]+1.0f;
boundries[3][1] = node.pos[1]+1.0f;
}
if(node.pos[2] < boundries[3][2])
{
boundries[2][2] = node.pos[2]-1.0f;
boundries[3][2] = node.pos[2]-1.0f;
boundries[6][2] = node.pos[2]-1.0f;
boundries[7][2] = node.pos[2]-1.0f;
}
else if(node.pos[2] > boundries[0][2])
{
boundries[0][2] = node.pos[2]+1.0f;
boundries[1][2] = node.pos[2]+1.0f;
boundries[4][2] = node.pos[2]+1.0f;
boundries[5][2] = node.pos[2]+1.0f;
}
}
}
Was ich hier tue gehen durch alle meine Elemente in Graph ist und fügen Sie sie in Baumwurzel. Außerdem erweitere ich meine Box, die meine Octree-Grenzen für die nächste Schleife darstellt, so dass alle Knoten hineinpassen.
Felder sind wichtig Struktur Update OCTREE wie folgt:
Octree* trees[2][2][2];
glm::vec3 vBoundriesBox[8];
bool leaf;
float combined_weight = 0;
std::vector<Element*> objects;
und Teil des Codes verantwortlich für Update: nicht
#define MAX_LEVELS 5
void Octree::AddObject(Element* object)
{
this->objects.push_back(object);
}
void Octree::Update()
{
if(this->objects.size()<=1 || level > MAX_LEVELS)
{
for(Element* Element:this->objects)
{
Element->parent_group = this;
}
return;
}
if(leaf)
{
GenerateChildren();
leaf = false;
}
while (!this->objects.empty())
{
Element* obj = this->objects.back();
this->objects.pop_back();
if(contains(trees[0][0][0],obj))
{
trees[0][0][0]->AddObject(obj);
trees[0][0][0]->combined_weight += obj->weight;
} else if(contains(trees[0][0][1],obj))
{
trees[0][0][1]->AddObject(obj);
trees[0][0][1]->combined_weight += obj->weight;
} else if(contains(trees[0][1][0],obj))
{
trees[0][1][0]->AddObject(obj);
trees[0][1][0]->combined_weight += obj->weight;
} else if(contains(trees[0][1][1],obj))
{
trees[0][1][1]->AddObject(obj);
trees[0][1][1]->combined_weight += obj->weight;
} else if(contains(trees[1][0][0],obj))
{
trees[1][0][0]->AddObject(obj);
trees[1][0][0]->combined_weight += obj->weight;
} else if(contains(trees[1][0][1],obj))
{
trees[1][0][1]->AddObject(obj);
trees[1][0][1]->combined_weight += obj->weight;
} else if(contains(trees[1][1][0],obj))
{
trees[1][1][0]->AddObject(obj);
trees[1][1][0]->combined_weight += obj->weight;
} else if(contains(trees[1][1][1],obj))
{
trees[1][1][1]->AddObject(obj);
trees[1][1][1]->combined_weight += obj->weight;
}
}
for(int i=0;i<2;i++)
{
for(int j=0;j<2;j++)
{
for(int k=0;k<2;k++)
{
trees[i][j][k]->Update();
}
}
}
}
bool Octree::contains(Octree* child, Element* object)
{
if(object->pos[0] >= child->vBoundriesBox[0][0] && object->pos[0] <= child->vBoundriesBox[1][0] &&
object->pos[1] >= child->vBoundriesBox[4][1] && object->pos[1] <= child->vBoundriesBox[0][1] &&
object->pos[2] >= child->vBoundriesBox[3][2] && object->pos[2] <= child->vBoundriesBox[0][2])
return true;
return false;
}
Weil ich Zeiger verwende ich um Baumelemente zu bewegen, tun denke, dass die Erstellung/Zerstörung von Objekten hier ein Problem darstellt. Der einzige Ort, wo ich Einfluss auf die Geschwindigkeit haben annehmen könnte, ist diese:
Element* obj = this->objects.back();
this->objects.pop_back();
if(contains(trees[0][0][0],obj))
Obwohl ich nicht sicher bin, wie ich ommit kann/Geschwindigkeit es auf. Hat jemand Vorschläge, was hier gemacht werden kann?
EDIT:
ich einige Serviette Mathe getan habe, und ich nehme an, sich ein weiterer Platz ist die Hauptgeschwindigkeitsabnahme verursacht werden könnte. Boundries in Update
Methode Überprüfung sieht aus wie eine Menge zu tun und was ich berechnet, dass die zusätzliche Komplexität zu dieser wegen in schlimmsten Fall ist:
number_of_elements * number_of_childern * number_of_faces * MAX_LEVELS
was in meinem Fall ist gleich zu Anzahl_der_ Elemente * 240.
Kann jemand bitte bestätigen, wenn meine Idee vernünftig ist?
http://codereview.stackexchange.com/ – Mihai
@Mihai Ich habe es dort nach Ihrem Vorschlag veröffentlicht: http://codereview.stackexchange.com/questions/127693/speed-concerns-of-octree-implementation – sebap123
Was DrunkCoder sagt, wird wahrscheinlich helfen, aber erinnere dich an die ersten drei Regeln der Leistungsoptimierung: messen, messen, messen! Verwenden Sie einen Sampling-CPU-Profiler für Ihre Plattform (z. B. perf + hotspot unter Linux, Visual Studio-Profiler unter Windows oder Instruments unter macOS) und verwenden Sie diese Daten dann, um die Performance-Täter zu finden. – milianw