aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/graph.py
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-07 03:28:55 +0200
committeruser <user@clank>2026-08-07 03:28:55 +0200
commit98b3b98eb1bf8f195ef4ebb1375c9f161d758c17 (patch)
tree810ec6ef2e88453a9fc076216dbc8a9b7e797a45 /idatui/graph.py
parentBaseline for the v5 bench (landing polls every 2ms instead of 10ms; the poll ... (diff)
downloadida-tui-98b3b98eb1bf8f195ef4ebb1375c9f161d758c17.tar.gz
ida-tui-98b3b98eb1bf8f195ef4ebb1375c9f161d758c17.tar.xz
ida-tui-98b3b98eb1bf8f195ef4ebb1375c9f161d758c17.zip
Three targeted cuts: the graph's transposition pass counts keep and swap in one pass over the neighbour pairs (was four _pair_cross calls); the barycentre median answers degree 1 and 2 without sorting; and the search body is built from windowed model reads instead of one locked row lookup per line.
Result: {"status":"keep","total_ms":17944.4,"lg_boot_ms":703.7,"lg_decomp_ms":2453.7,"lg_graph_ms":1000,"lg_hex_ms":478,"lg_index_ms":96,"lg_listing_cold_ms":453,"lg_listing_warm_ms":411.2,"lg_nav_ms":6555.9,"lg_palette_ms":4.9,"lg_render_ms":218.4,"lg_search_ms":1308.8,"pure_graph_ms":212.3,"sm_boot_ms":432.4,"sm_decomp_ms":1267.8,"sm_graph_ms":742.7,"sm_hex_ms":440,"sm_index_ms":2.5,"sm_listing_cold_ms":264.3,"sm_listing_warm_ms":289.5,"sm_nav_ms":305.4,"sm_palette_ms":0.3,"sm_render_ms":258.6,"sm_search_ms":45,"fails":0}
Diffstat (limited to 'idatui/graph.py')
-rw-r--r--idatui/graph.py47
1 files changed, 41 insertions, 6 deletions
diff --git a/idatui/graph.py b/idatui/graph.py
index e0a9422..baee597 100644
--- a/idatui/graph.py
+++ b/idatui/graph.py
@@ -280,6 +280,33 @@ def _pair_cross(a: int, b: int, side: dict[int, list[int]],
return n
+def _swap_delta(a: int, b: int, down: dict[int, list[int]],
+ up: dict[int, list[int]], pos: dict[int, int]) -> tuple[int, int]:
+ """``(keep, swap)`` for the adjacent pair (a, b), both sides, in one pass.
+
+ The same as calling :func:`_pair_cross` four times, which is what the
+ transposition loop used to do: every neighbour pair was visited twice (once
+ per direction) and each visit was a python call. Counting both outcomes
+ while the pair is in hand halves the comparisons and removes three calls per
+ candidate swap — and this runs a third of a million times over a corpus.
+ """
+ keep = swap = 0
+ for side in (down, up):
+ va = side[a]
+ vb = side[b]
+ if not va or not vb:
+ continue
+ pbs = [pos[v] for v in vb]
+ for u in va:
+ pu = pos[u]
+ for pv in pbs:
+ if pu > pv:
+ keep += 1
+ elif pu < pv:
+ swap += 1
+ return keep, swap
+
+
def crossings(layers: list[list[int]], down: dict[int, list[int]],
pos: dict[int, int]) -> int:
return sum(_cross_below(l, down, pos) for l in layers)
@@ -306,11 +333,20 @@ def _order_layers(g: _Graph, root: int, sweeps: int = 6) -> list[list[int]]:
pos = {i: k for layer in layers for k, i in enumerate(layer)}
def median(i: int, side: dict[int, list[int]]) -> float:
- ps = sorted(pos[j] for j in side[i])
- if not ps:
+ # Almost every node in a control-flow graph has one or two neighbours
+ # on a given side, so answer those without building and sorting a list:
+ # this runs tens of thousands of times per corpus layout.
+ js = side[i]
+ n = len(js)
+ if n == 1:
+ return float(pos[js[0]])
+ if n == 2:
+ return (pos[js[0]] + pos[js[1]]) / 2
+ if not n:
return -1.0
- m = len(ps) // 2
- return float(ps[m]) if len(ps) % 2 else (ps[m - 1] + ps[m]) / 2
+ ps = sorted(pos[j] for j in js)
+ m = n // 2
+ return float(ps[m]) if n % 2 else (ps[m - 1] + ps[m]) / 2
best, best_x = [list(l) for l in layers], crossings(layers, down, pos)
for s in range(sweeps):
@@ -327,8 +363,7 @@ def _order_layers(g: _Graph, root: int, sweeps: int = 6) -> list[list[int]]:
for layer in layers:
for k in range(len(layer) - 1):
a, b = layer[k], layer[k + 1]
- keep = _pair_cross(a, b, down, pos) + _pair_cross(a, b, up, pos)
- swap = _pair_cross(b, a, down, pos) + _pair_cross(b, a, up, pos)
+ keep, swap = _swap_delta(a, b, down, up, pos)
if swap < keep:
layer[k], layer[k + 1] = b, a
pos[a], pos[b] = k + 1, k