diff options
| author | user <user@clank> | 2026-08-07 04:16:01 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-07 04:16:01 +0200 |
| commit | b2b59e0ed8bbd63877700292337eca4153f09bd4 (patch) | |
| tree | 2252a0531b8e8a36bdceb889794bbda9658bf7fd /idatui | |
| parent | autoresearch: final playbook update - budget, floors, dead ends (diff) | |
| download | ida-tui-b2b59e0ed8bbd63877700292337eca4153f09bd4.tar.gz ida-tui-b2b59e0ed8bbd63877700292337eca4153f09bd4.tar.xz ida-tui-b2b59e0ed8bbd63877700292337eca4153f09bd4.zip | |
Keep a listing row's spans and operand extents exactly as they came off the wire instead of copying them into tuples. The copy re-proved types the worker's own tool guarantees, and it destroyed the object sharing the worker's line cache had created — 228k rows now reference 125k span lists, not 228k private tuples.
Result: {"status":"keep","total_ms":17700.4,"lg_boot_ms":776,"lg_decomp_ms":2690.1,"lg_graph_ms":888.1,"lg_hex_ms":449.1,"lg_index_ms":97.4,"lg_listing_cold_ms":439.4,"lg_listing_warm_ms":404.6,"lg_nav_ms":6762.9,"lg_palette_ms":4.6,"lg_render_ms":230.8,"lg_search_ms":762.9,"pure_graph_ms":217.1,"sm_boot_ms":452.6,"sm_decomp_ms":1270,"sm_graph_ms":698,"sm_hex_ms":433.2,"sm_index_ms":2.4,"sm_listing_cold_ms":259.1,"sm_listing_warm_ms":260.5,"sm_nav_ms":300.4,"sm_palette_ms":0.3,"sm_render_ms":258.3,"sm_search_ms":42.7,"fails":0}
Diffstat (limited to 'idatui')
| -rw-r--r-- | idatui/domain.py | 27 |
1 files changed, 16 insertions, 11 deletions
diff --git a/idatui/domain.py b/idatui/domain.py index e107786..b5081cf 100644 --- a/idatui/domain.py +++ b/idatui/domain.py @@ -27,6 +27,7 @@ import re import threading import urllib.request from concurrent.futures import ThreadPoolExecutor +from collections.abc import Sequence from dataclasses import dataclass, field, replace from typing import NamedTuple from typing import Callable, TYPE_CHECKING @@ -115,12 +116,18 @@ class Head(NamedTuple): #: [(kind, text)] from IDA's own colour tags — mnem/reg/num/name/str/punct/… #: None when the worker didn't provide them (older worker, or the spans #: disagreed with the plain text, in which case the text wins). - spans: tuple[tuple[str, str], ...] | None = None + #: + #: Held exactly as it came off the wire, and **read-only**. The worker + #: memoises its per-line render, so one list is shared by every row that + #: says the same thing — pickle preserves that, and 228 000 rows of bash + #: reference about 53 000 lists. Copying each row's into a fresh tuple threw + #: the sharing away and cost 0.9 µs a row for nothing. + spans: Sequence | None = None #: [(start, end, n)] — where each operand sits in ``text``, from IDA's own #: COLOR_OPND markers. Lets the view show which operand the cursor is on, #: and is the same information the worker maps a column through, so the - #: highlight and the edit can't disagree. - ops: tuple[tuple[int, int, int], ...] | None = None + #: highlight and the edit can't disagree. Read-only, as ``spans`` is. + ops: Sequence | None = None @property def label(self) -> str | None: # Line-compatible alias @@ -135,12 +142,10 @@ class Head(NamedTuple): @classmethod def from_raw(cls, d: dict, raw: bytes | None = None) -> "Head": - sp = d.get("spans") - ops = d.get("ops") - # ``tuple(map(tuple, ...))`` rather than a per-item genexpr with str()/ - # int() coercion: this runs once per listing row (hundreds of thousands - # on a real binary) and the worker's own tool already emits [str, str] - # and [int, int, int]. The coercion was re-proving that on every row. + # Spans and operand extents are stored as they arrive: the worker's own + # tool emits [str, str] and [int, int, int], so re-coercing them was + # re-proving that once per listing row -- and copying them into tuples + # destroyed the sharing the worker's line cache had just created. return cls( ea=_as_int(d["ea"]), kind=d.get("kind", "unknown"), @@ -148,8 +153,8 @@ class Head(NamedTuple): text=d.get("text", ""), name=d.get("name"), raw=raw, - spans=tuple(map(tuple, sp)) if sp else None, - ops=tuple(map(tuple, ops)) if ops else None, + spans=d.get("spans") or None, + ops=d.get("ops") or None, ) |
