You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Cpp: make DFAState edge lookups lock-free to speed up lexing
The C++ runtime stored each DFA state's outgoing edges in a
FlatHashMap<size_t, DFAState*> and guarded every access with a single
shared ATN-wide mutex (ATN::_edgeMutex). Because the lexer consults the
edge table once per input character, the hottest loop in the runtime paid
a shared-lock acquire plus a hash-map probe on every codepoint. The Java
reference runtime has neither cost: DFAState.edges is a plain array indexed
by symbol, and reads are lock-free (a benign miss simply recomputes the
edge, which is idempotent), while writes are serialized per state.
This change brings the C++ representation in line with Java:
- DFAState::edges becomes a lazily allocated, fixed-size array of
std::atomic<DFAState*>. getEdge() reads a slot with acquire ordering
and needs no lock; setEdge() publishes slots with release ordering and
is still serialized by the caller via ATN::_edgeMutex.
- Lexer and parser getExistingTargetState() now read edges lock-free,
removing the per-symbol shared-lock acquire from the hot path.
- The lexer and parser edge tables are allocated once at their natural
full size (the char range and maxTokenType+1 respectively) and never
reallocated, so a concurrent lock-free reader can never observe a moved
table. The only table that grows is the precedence DFA's start-state
table, which is read exclusively under ATN::_edgeMutex and never via
the lock-free path, so resizing it cannot race a reader.
The concurrency contract is therefore identical to Java's: lock-free reads
tolerating a benign recompute-on-miss, with writes serialized. There is no
public API change.
Measured on an 84 MiB JSON input (Apple M5 Max, single-threaded interpreted
parse), the lexer stage goes from 26.7 to 47.9 MiB/s (~1.8x) and total
native parse from 15.0 to 20.9 MiB/s (~1.4x); a 54 MiB netlist input shows
1.7x / 1.3x. Output is byte-for-byte identical to the previous runtime
across all test inputs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Christopher Barber <analog.cbarber@gmail.com>
0 commit comments