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:
@@ -173,7 +173,14 @@ void demo_graph() {
|
||||
|
||||
section("Viewport (drag = pan, wheel = zoom, click = select)");
|
||||
if (s_initialized) {
|
||||
// Avanzamos 1 paso de force layout cada frame mientras layout_running
|
||||
// Avanzamos 1 paso de force layout cada frame mientras layout_running.
|
||||
// Auto-pause: si la energia por nodo cae bajo el umbral durante N
|
||||
// frames consecutivos, paramos la simulacion automaticamente — el
|
||||
// grafo ya esta estable. El usuario lo retoma con "Resume layout"
|
||||
// o "Regenerate".
|
||||
static int s_low_energy_frames = 0;
|
||||
const int k_pause_after_frames = 30;
|
||||
const float k_pause_per_node = 0.001f; // umbral de energia/nodo
|
||||
if (s_state.layout_running) {
|
||||
ForceLayoutConfig cfg;
|
||||
cfg.repulsion = s_repulsion;
|
||||
@@ -181,6 +188,20 @@ void demo_graph() {
|
||||
cfg.gravity = s_gravity;
|
||||
cfg.iterations = 1;
|
||||
s_state.layout_energy = graph_force_layout_step(s_graph, cfg);
|
||||
|
||||
const float per_node = s_graph.node_count > 0
|
||||
? s_state.layout_energy / (float)s_graph.node_count
|
||||
: 0.0f;
|
||||
if (per_node < k_pause_per_node) {
|
||||
if (++s_low_energy_frames >= k_pause_after_frames) {
|
||||
s_state.layout_running = false;
|
||||
s_low_energy_frames = 0;
|
||||
}
|
||||
} else {
|
||||
s_low_energy_frames = 0;
|
||||
}
|
||||
} else {
|
||||
s_low_energy_frames = 0;
|
||||
}
|
||||
graph_viewport("##graph_demo", s_graph, s_state, ImVec2(0, 460));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user