execution/stagedsync: hand the trace sets to the apply result instead of copying - #23092
Conversation
… of copying The publish path allocated two empty maps and copied every entry of the tx's trace sets into them. The tracer is built per tx inside Worker.RunTxTaskNoLock and nothing else keeps a reference, so the result already owns those maps, and the consumer only ranges over them. The block-end result four hundred lines below already assigns them directly.
There was a problem hiding this comment.
Pull request overview
This PR optimizes the parallel execution publish path in execution/stagedsync by removing per-transaction copying of trace address sets (from/to) into freshly-allocated maps when building the apply result. Instead, it directly hands the existing maps from the finalized execution result into the txResult sent through the apply/commit fan-out, aligning the per-transaction path with the existing block-end path and reducing allocations and per-entry inserts on the serial apply loop.
Changes:
- Remove
maps.Copy-based cloning ofTraceFroms/TraceTosduringnextResultpublishing. - Assign
result.TraceFroms/result.TraceTosdirectly into the per-txtxResult(traceFroms/traceTos), eliminating two map allocations per published tx. - Drop the now-unused
mapsimport.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
yperbasis
left a comment
There was a problem hiding this comment.
LGTM — verified the ownership and read-only claims against the code, and ran execution/stagedsync under -race plus the rpc/jsonrpc trace tests locally on the branch.
Nit, pre-existing and fine as a follow-up: the block-finalize txResult carries traceFroms/traceTos that no consumer ever reads — isFinalize has been write-only since #20805, and no caller passes ApplyTxIndexes' skipReceiptCache although its docstring cites exactly that finalize result. So the block-end precedent cited here is safe but vacuous: the coinbase TracesToIdx entry actually comes from the block-end task's own per-tx publish. Dropping those dead fields and the stale docstring would tidy this up.
The publish path in
nextResultallocated two empty maps into the apply result, then copied every entry of the transaction's trace sets into them. The result already owns those maps.Worker.RunTxTaskNoLock, built per transaction. The parallelWorkerhas noCallTracerfield — it keepsevmandibsacross transactions, not the tracer.CallTracer.OnEnterlazily allocatesfroms/tosinside that instance andFroms()/Tos()hand them straight toresult.TraceFroms/TraceTos.TxResultis not pooled.applyLogsAndTraces4only ranges over the maps — no writes, no deletes.result.TraceFromsafter the publish block.finalizedResults[tx-1]is read again, but only for.Receipt, andtakeNextPending+markCompletepublish each transaction once.The block-end result in the same function already assigns them directly:
This makes the per-transaction path match. Costs two map allocations and one insert per distinct from/to address per transaction, on the serial apply loop.
Nil is safe:
rangeover a nil map is zero iterations, and the block-end path already passes possibly-nil maps through.If the tracer is ever made worker-held to drop its per-transaction allocation, this becomes wrong — but so would the block-end path, and the fix then is
Resetsemantics at the worker rather than a defensive copy on the apply loop.No new tests: pure refactor, no behaviour change. Covered by the existing trace-index tests.
Green:
execution/stagedsync,execution/tests,rpc/jsonrpc.