diff options
| author | blasty <blasty@local> | 2026-08-07 15:02:05 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-08-07 15:02:05 +0200 |
| commit | 55e9d9fdca4baafce728659d43614e203bfbea9a (patch) | |
| tree | d8f7b3bd70aa9e7aa795a3a9eaaa3cfc0258f31b /idatui/__init__.py | |
| parent | Record the test-speed findings in .fastfeedback/SPEED.md (force-added) (diff) | |
| download | ida-tui-55e9d9fdca4baafce728659d43614e203bfbea9a.tar.gz ida-tui-55e9d9fdca4baafce728659d43614e203bfbea9a.tar.xz ida-tui-55e9d9fdca4baafce728659d43614e203bfbea9a.zip | |
codemode: close the performance gap with the old worker (heads 35x -> 2.2x)
Two changes, both about work that was never ours to do, found by profiling the
A/B benchmark rather than guessing.
1. Serialise inside the database process.
Code Mode runs to_jsonable() over whatever a snippet returns, walking the
entire structure to make it JSON-safe. Our answers are already JSON-safe and
they are large: a 200-row listing page is ~10k small objects, and walking
them cost 66ms of the page's 92ms -- 114x what json.dumps of the very same
data costs (0.58ms). Snippets now return one pre-serialised string, so that
walk is O(1) and the client parses a payload it was going to parse anyway.
heads(200): 92ms -> 24.7ms.
2. Detach the runtime's trace hook while our snippet runs.
ida_codemode.runtime wraps every execute_python in sys.settrace(timeout_trace)
to enforce deadlines, and timeout_trace RETURNS ITSELF -- which switches on
LINE tracing in every frame it sees. Every line of every function we call
pays a Python-level callback. Measured here:
ida_bytes.get_flags 0.106us untraced 5.49us traced 52x
(plain idalib, no Code Mode: 0.119us -- i.e. untraced == native)
heads(200 rows) 2.0ms untraced 20.2ms traced 10x
That one hook was the entire residual gap against the old unix-socket worker.
The snippet now detaches it and restores it in a finally.
What that gives up, stated plainly: the deadline is no longer enforced for a
pure-Python loop inside our snippet. The runtime's other cancellation path --
a threading.Timer calling ida_kernwin.set_cancelled() -- does not go through
the trace and still fires, so a long IDA operation remains interruptible, and
every operation here is bounded by its own count/limit argument. Set
IDATUI_CODEMODE_TRACE=1 to keep the stock behaviour.
Against the worker backend, same box, targets/echo (worker -> codemode):
heads_200 2.65ms -> 5.85ms 2.2x (was 35x)
heads_500 6.21ms -> 10.67ms 1.7x
heads expect-hit 2.16ms -> 4.61ms 2.1x
disasm_200 9.03ms -> 5.25ms 0.6x faster
decompile_cold 162.62ms -> 30.66ms 0.2x faster
decompile_warm 30.02ms -> 25.39ms 0.8x faster
decomp_map 45.82ms -> 47.85ms 1.0x parity
pc_nums 19.90ms -> 22.56ms 1.1x parity
rename_func 254.08ms -> 255.54ms 1.0x parity
connect 550.0ms -> 410.0ms 0.7x faster
What is left is the transport floor: an empty execute_python round trip is
2.0ms, so trivial calls (data_type 0.07ms -> 2.63ms, force_recompile, a single
xref query) look like 40x while being 2.5ms of wall clock. Reducing those needs
fewer calls, not faster ones -- the digest/expect path already does that for the
listing, which is where call volume actually is.
Full suite: 788 passed, 0 failed, 115.3s (was 146.3s; the pilot alone went
80.9s -> 62.2s).
Diffstat (limited to 'idatui/__init__.py')
0 files changed, 0 insertions, 0 deletions
