perf(graph): quick wins — OpenMP force step + buffer orphan + auto-pause
Tres atajos de rendimiento sin GPU compute (eso llega en 0049h). Probados
en Linux y cross-compile Windows, todos los tests pasan, OpenMP 4.5
detectado.
1. **OpenMP en graph_force_layout_step** (cpp/functions/viz/...)
- find_package(OpenMP) en cpp/CMakeLists.txt; fn_framework lo enlaza
PUBLIC para que cualquier app/funcion lo herede transparentemente.
Si no esta disponible, los pragmas se ignoran (single-thread).
- #pragma omp parallel for con guard if(N>=1024) en los 4 bucles
embarazosamente paralelos: zero forces, repulsion Barnes-Hut (con
schedule dynamic), gravity, integration (con reduction sobre energy).
La attraction-along-edges se queda secuencial: edges multiples
escriben en el mismo nodo y meterle atomic mata el speedup.
- quad_force usaba un static int stack[1<<20] (4MB compartidos entre
threads — race). Lo reemplazo por int stack[256] en pila: el
quadtree crece como log4(N) ~= 10 niveles para N <= 1M, asi que 256
es holgado y thread-safe sin coste.
- Esperable: ~4-8x menos tiempo CPU/step en 20k nodos en CPU multicore.
2. **Buffer orphan en graph_renderer** (edges + nodes)
- Antes del glBufferData(.., data, DYNAMIC_DRAW), un primer
glBufferData(.., NULL, DYNAMIC_DRAW) que descarta el buffer previo.
El driver da uno fresco sin esperar al frame anterior — evita los
sync stalls clasicos del DYNAMIC_DRAW reuploadeado cada frame.
- Esperable: 2-3x throughput de upload (Mesa/NVIDIA/AMD respetan el
hint).
3. **Auto-pause en demo_graph cuando converge**
- Si energy_per_node < 0.001 durante 30 frames consecutivos, paramos
la simulacion automaticamente. CPU/GPU a 0% cuando el grafo ya
esta estable. Resume con "Resume layout" o "Regenerate".
Lo de OpenMP se sustituye cuando entre 0049h (force layout en compute
shader): cuando llegue, los #pragma omp se borran. Orphan y auto-pause
son keepers definitivos.
This commit is contained in:
@@ -146,8 +146,11 @@ static void quad_insert_body(int qi, int node_idx) {
|
||||
static void quad_force(int qi, float nx, float ny,
|
||||
float theta, float repulsion, float min_dist,
|
||||
float& fx, float& fy) {
|
||||
// Iterative traversal using a small stack to avoid recursion depth issues.
|
||||
static int stack[MAX_QUAD_NODES]; // reuse static stack
|
||||
// Stack en pila de la funcion: thread-safe (la version anterior con
|
||||
// `static` se rompia bajo OpenMP). La profundidad de un quadtree con N
|
||||
// bodies acotada por log4(N) ~= 10 niveles para N <= 1M, asi que 256
|
||||
// entradas son holgadas para todos los pushes simultaneos.
|
||||
int stack[256];
|
||||
int top = 0;
|
||||
stack[top++] = qi;
|
||||
|
||||
@@ -207,6 +210,7 @@ float graph_force_layout_step(GraphData& graph, const ForceLayoutConfig& config)
|
||||
|
||||
for (int iter = 0; iter < config.iterations; ++iter) {
|
||||
// Zero forces
|
||||
#pragma omp parallel for if(graph.node_count >= 1024) schedule(static)
|
||||
for (int i = 0; i < graph.node_count; ++i) {
|
||||
fx_buf[i] = 0.0f;
|
||||
fy_buf[i] = 0.0f;
|
||||
@@ -240,14 +244,16 @@ float graph_force_layout_step(GraphData& graph, const ForceLayoutConfig& config)
|
||||
}
|
||||
|
||||
// ---- Repulsion via Barnes-Hut ----
|
||||
// Cada iteracion lee del quadtree (read-only) y escribe en su propio
|
||||
// slot de fx_buf/fy_buf — embarrassingly parallel. quad_force usa
|
||||
// stack local en pila, asi que es thread-safe.
|
||||
#pragma omp parallel for if(graph.node_count >= 1024) schedule(dynamic, 256)
|
||||
for (int i = 0; i < graph.node_count; ++i) {
|
||||
if (graph.nodes[i].pinned) continue;
|
||||
quad_force(root,
|
||||
graph.nodes[i].x, graph.nodes[i].y,
|
||||
config.theta, config.repulsion, config.min_distance,
|
||||
fx_buf[i], fy_buf[i]);
|
||||
// Subtract self-interaction (the tree includes the node itself)
|
||||
// Self-force: repulsion * 1 / min_dist^2, but direction is (0,0) -> skip
|
||||
}
|
||||
|
||||
// ---- Attraction along edges (spring force) ----
|
||||
@@ -274,6 +280,7 @@ float graph_force_layout_step(GraphData& graph, const ForceLayoutConfig& config)
|
||||
|
||||
// ---- Gravity toward center (0,0) ----
|
||||
if (config.gravity != 0.0f) {
|
||||
#pragma omp parallel for if(graph.node_count >= 1024) schedule(static)
|
||||
for (int i = 0; i < graph.node_count; ++i) {
|
||||
if (graph.nodes[i].pinned) continue;
|
||||
fx_buf[i] -= config.gravity * graph.nodes[i].x;
|
||||
@@ -283,6 +290,7 @@ float graph_force_layout_step(GraphData& graph, const ForceLayoutConfig& config)
|
||||
|
||||
// ---- Integrate: v = v * damping + F; pos += v ----
|
||||
total_energy = 0.0f;
|
||||
#pragma omp parallel for if(graph.node_count >= 1024) schedule(static) reduction(+:total_energy)
|
||||
for (int i = 0; i < graph.node_count; ++i) {
|
||||
GraphNode& n = graph.nodes[i];
|
||||
if (n.pinned) continue;
|
||||
|
||||
Reference in New Issue
Block a user