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:
2026-04-29 21:38:13 +02:00
parent 96db47f083
commit 9a4ff33e68
4 changed files with 55 additions and 6 deletions
+9 -1
View File
@@ -389,6 +389,11 @@ unsigned int graph_renderer_draw(GraphRenderer* r, const GraphData& graph,
glBindVertexArray(r->edge_vao);
glBindBuffer(GL_ARRAY_BUFFER, r->edge_vbo);
// Orphan: descarta el buffer anterior antes de subir el nuevo. Evita
// que el driver bloquee esperando que termine el frame previo (sync
// stall) y nos da un VBO fresco. Coste: ~0; ganancia: 2-3x upload
// throughput en drivers que respetan el hint (Mesa, NVIDIA, AMD).
glBufferData(GL_ARRAY_BUFFER, vi * (int)sizeof(float), nullptr, GL_DYNAMIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, vi * (int)sizeof(float), edge_buf, GL_DYNAMIC_DRAW);
glDrawArrays(GL_LINES, 0, vi / 6);
glBindVertexArray(0);
@@ -422,7 +427,10 @@ unsigned int graph_renderer_draw(GraphRenderer* r, const GraphData& graph,
glBindVertexArray(r->node_vao);
glBindBuffer(GL_ARRAY_BUFFER, r->node_instance_vbo);
glBufferData(GL_ARRAY_BUFFER, graph.node_count * 7 * (int)sizeof(float), node_buf, GL_DYNAMIC_DRAW);
// Orphan + reupload (ver comentario en edge upload arriba).
const GLsizeiptr node_bytes = graph.node_count * 7 * (GLsizeiptr)sizeof(float);
glBufferData(GL_ARRAY_BUFFER, node_bytes, nullptr, GL_DYNAMIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, node_bytes, node_buf, GL_DYNAMIC_DRAW);
// Draw 4 vertices (triangle strip quad) x node_count instances
// Pass per-instance node_px uniform via the average size (approximation)