Skip to content

Replace O(N^2) transistor deduplication with a hash set - #20

Open
Roxxik wants to merge 1 commit into
mist64:masterfrom
Roxxik:setup-dedup
Open

Replace O(N^2) transistor deduplication with a hash set#20
Roxxik wants to merge 1 commit into
mist64:masterfrom
Roxxik:setup-dedup

Conversation

@Roxxik

@Roxxik Roxxik commented Jul 28, 2026

Copy link
Copy Markdown

setupNodesAndTransistors removes duplicate transistors by scanning the list of
survivors for each input transistor. At 3288 transistors against a list growing
to 3239, that is roughly 5.3M three-array comparisons per call.

The comment on it says:

only done once at initialization, not a significant time sink

which holds for cbmbasic, and does not hold for measure. measure calls
resetChip_test from inside its per-opcode loops and so rebuilds chip state
22832 times per run. Setup was 51% of that profile, with this scan at the top.

This is setup cost only. It does not touch the solver, and no simulation result
changes.

What it does instead

Detects duplicates with an open-addressed hash set, keyed on the canonical triple
(gate, min(c1,c2), max(c1,c2)) packed into one 64-bit word. c1 and c2 are
interchangeable, so ordering them folds the reversed-c1c2 case into a plain key
comparison. The table is sized to a power of two at least twice the transistor
count, keeping the load factor at or below 0.5 so linear probing always
terminates.

The first occurrence is still the one kept, and input order is unchanged.
Later initialisation indexes transistors_c1/c2 by a prefix sum over gate counts,
so the arrays have to stay in the netlist's own gate-sorted order. A sort-based fix
would have had to sort by gate for the same reason.

Verification

Both algorithms keep the same 3239 transistors in the same order, element for
element, on the shipped netlist. Checked directly rather than inferred.
measure's 256-opcode output is byte-identical, same md5 over three runs each
side. cbmbasic --benchmark output is identical apart from its own timing lines.

Performance

workload master this PR
measure, whole run 106.7 s 62.9 s -41%
cbmbasic, setup plus startup 9.5 ms 7.7 ms -1.8 ms
cbmbasic --benchmark, whole run 0.855 s 0.855 s unchanged

measure is where this shows an effect: 41% off the whole run.
That follows from what the change is. measure runs initAndResetChip 22832
times, so the saving per setup is collected 22832 times; 1.8 ms times 22832 is 41 s,
against a measured difference of 44 s.

cbmbasic sets the chip up once and so collects the saving once. Its run total
does not change. The setup row is the split at the benchmark's own internal
clock, which starts after initAndResetChip.

Machine

AMD Ryzen 5 7640U (Zen 4), 6C/12T, SMT on. Frequency boost disabled, governor
performance, amd_pstate=active. Kernel 7.1.4-arch1-1, gcc 16.1.1. All runs
pinned with taskset -c 8, both sides built up front and never rebuilt between
runs, interleaved round-robin with the sweep direction alternating each pass. N=3
for measure, N=42 for the cbmbasic split, medians reported.

I used an LLM to help me out in this work, but I manually reviewed all changes made.

setupNodesAndTransistors removed duplicates by scanning the survivors for
each input transistor, roughly 5.3M comparisons per call. That is a
one-off for cbmbasic, as the comment says, but not for measure, which
rebuilds chip state 22832 times per run and spent 51% of its profile in
setup.

Use an open-addressed hash set instead, keyed on the canonical triple
(gate, min(c1,c2), max(c1,c2)) packed into one word.

The first occurrence is still kept and input order unchanged: later
initialization indexes transistors_c1/c2 by a prefix sum over gate
counts, so the arrays must stay in the netlist's own gate-sorted order.

measure drops from 106.7 s to 62.9 s, output byte-identical.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant