Skip to main content
All articles

Where Did the Gains from Parallel Execution Go?

· 11 min read
Bankware Global Engineering

Running independent transactions together and dispatching dependent work as soon as its inputs are ready can reduce computation time and unnecessary waiting. Nigo explored several ways to schedule that work, each with a different way of handling dependencies, while preserving the same results as sequential execution.

But a correct parallel engine did not reduce the node's total processing time by the same proportion. In one experiment on August 7, 2026, block execution for 1,000 transactions took about 18% less time, while the full local processing path measured in the experiment improved by only about 2.4%.

Where did the execution gains go? Revisiting the records requires separating three questions: what was measured, what work remained in the execution stage, and how the execution methods were compared. This article looks back at that experiment. It is not a new measurement of Nigo's current throughput.

Work takes time before execution begins

The experiment used 1,000 Direct transactions, each consuming one existing record and creating one new record. Each transaction used a different input, so none had to wait for another transaction's result. The measurement covered the node accepting already signed transactions and producing block execution results. It repeatedly processed the same prepared dataset; it did not model a live ledger continually receiving real transfers.

Accepting a transaction involves more than adding it to a list. The node checks its signature and whether the requested operation and state satisfy the rules, then places it in the TxPool, the store of pending transactions. This acceptance and validation stage is called admission. The node then retrieves a batch of transactions, prepares a block execution request, executes it, and cleans up the pending list.

The comparison changed block execution from sequential processing to parallel processing with four workers. Signature verification during admission used four workers in both cases. “Sequential” in the table below therefore does not mean that every operation in the node ran one at a time.

Values are mean times in milliseconds for processing one batch of 1,000 transactions.

Measured stageSequentialParallel, 4 workers
Admission37.86237.885
Block execution6.3135.176
Other processing0.4810.511
Total44.65643.572

“Other processing” is the total minus the first two stages. It includes batch retrieval, execution request preparation, pending-list cleanup, and the remaining measured time. All state was held in memory.

The “total” here runs from transaction admission through execution and pending-list cleanup. It excludes transaction creation and signing, HTTP request parsing, network delivery, consensus, state commitment computation, and database commits. These numbers therefore cannot be read as the time from a user's submission to finality, or converted into production TPS.

Why an 18% improvement became 2.4%

On the sequential path, block execution accounted for 6.313 ms of the 44.656 ms total, or about 14%. Reducing that part by about 18% reduces the total by roughly 18% of 14%, or about 2.5%. That is close to the observed overall reduction of 2.4%. The other stages also changed slightly, so the figures do not match exactly.

Execution saved 1.137 ms. The total fell by 1.084 ms. Nearly all the time saved in execution appeared in the total, but it was a small fraction of that total. The gains from parallelism had not all disappeared somewhere else.

An extreme assumption makes the scope clearer. Even if the 6.313 ms of block execution could be reduced to zero while all other costs stayed the same, about 38.3 ms would remain. Under these conditions, changing execution alone could remove at most about 14% of the total. This is a way to understand the size of the optimization target, not a prediction that execution can be made free.

The largest stage was admission, at about 85% of the total. It included several checks, including recovering public keys from signatures and verifying them. The full 37.862 ms cannot be attributed to signature computation alone, but adding block execution workers clearly does not directly reduce the cost of admission.

Repeating verification changes the experiment

Another boundary became important: does block execution verify transaction signatures again?

Transactions retrieved from the node's own TxPool have already passed signature verification during admission. Nigo passes that result along as evidence valid only within the same process, allowing eligible transactions to avoid repeating the signature computation. This is called an admission proof. It checks that the transaction content and verification criteria match; it does not skip all checks that depend on current state.

A block received from another node does not carry this local proof. The receiving node must verify the signatures for itself. The proof is not consensus evidence that other nodes simply trust, and it does not eliminate the cost of the initial verification.

The first table includes both the cost of verifying signatures and creating proofs during admission, and block execution that reuses those proofs. By contrast, an experiment that measures block execution alone can prepare proofs in advance and leave their creation outside the measurement. The two measurements answer different questions: the cost of accepting and processing transactions for the first time, and the work left in block execution after verification has already finished.

When signatures must be verified again, each transaction requires substantial computation that can be spread across workers. Reusing the verification result makes execution of the same transaction much lighter. Dependency analysis, work dispatch, and result collection then account for a larger share of the cost. Successfully removing duplicate computation can therefore make parallelism less beneficial in the stage that follows.

Dependencies change the comparison again

The first 1,000 transactions were all independent. When one transaction consumes an output created by an earlier transaction, it must wait for that result. Adding workers cannot remove the dependency itself.

A separate experiment arranged 250 of 1,000 Direct transactions into a single chain, with each consuming the preceding transaction's output. The remaining 750 were independent. That is what “25% dependency” means here. It does not mean that transactions had a random 25% chance of conflicting.

The next table compares mean times for block execution alone, in milliseconds. Its transaction structure and measurement conditions differ from those of the full local path above, so the absolute values in the two tables should not be compared directly. The parallel path in this table used WAVE, which groups transactions into batches for concurrent execution, with four workers.

Signature handlingSequentialParallel, 4 workers
Verify again121.01664.351
Reuse verification4.7635.759

The two rows use the same dependency structure with different signature verification conditions. The second row excludes prior verification and proof creation.

The parallel path was faster when signatures were verified again, but the sequential path was faster when verification results were reused. This also differs from the first experiment, where parallel execution made the independent transactions faster. The records do not support a single conclusion that “Direct transactions run faster in parallel.”

Does less waiting mean faster execution?

The same dependency experiment also compared READY_QUEUE, which dispatches transactions as they become ready, and WORK_FIRST_CONTINUATION, which lets a worker that has finished a transaction continue with a ready successor. These choices aimed to reduce waiting or the cost of handing off work, but their actual benefit depended on how much computation remained per transaction.

With four workers, 25% dependency, and signature revalidation, the mean time was 45.455 ms for READY_QUEUE and 45.079 ms for WORK_FIRST_CONTINUATION. Both were shorter than WAVE's 64.351 ms. When signature verification results were reused, however, both alternatives were slower than WAVE and sequential execution under the same conditions. Across the reuse cases with 25% and 50% dependency and two and four workers, all three parallel strategies were slower than sequential execution.

With signature revalidation and 50% dependency, WORK_FIRST_CONTINUATION had the lowest mean in both the two-worker and four-worker comparisons. But at each worker count, READY_QUEUE had a lower p99, a measure of the slower executions. The p99 is the time within which 99% of the measured samples completed. The strategy that reduced the mean most did not also reduce the slower executions most.

This does not prove that sequential execution will always beat every scheduling strategy. One interpretation is that, once transaction execution became lighter, the gains were too small to offset dispatch and result-collection costs. These were workloads consisting of Direct transactions. They do not establish how the three strategies compare for Native Program transactions or mixed blocks. At the time, no strategy won under every condition, so neither alternative was promoted to the general default.

Dividing computation also takes computation

Parallel execution involves work beyond executing the transactions themselves. The engine must analyze dependencies, prepare temporary state for each task, validate execution results, and collect them in the prescribed order. Dispatching work sooner does not remove those costs.

The improvements at the time focused on reducing this overhead. Where the engine had submitted one task per transaction, it grouped multiple transactions into a bounded number of tasks. It stored results in their assigned transaction positions from the start instead of sorting them afterward.

These changes did not remove validation or alter transaction order. They reduced task submission, intermediate data construction, and result-collection costs while checking the same conditions. The first table was measured after these adjustments. However, it compares sequential and parallel execution in the adjusted implementation, not the implementation before and after optimization. It cannot isolate the contribution of each change.

Worker count and ideas for reducing waiting are therefore not enough to select an execution method. The comparison must include dispatch costs under conditions that match the remaining computation per transaction and the dependency structure.

Reversing measurement order reversed the result

The measurement method also needed correction before these conclusions could be drawn. When the full local path for 1,000 transactions was measured in a fixed order with sequential execution first, the parallel path was about 3.6% slower. Reversing that order and measuring parallel execution first made it about 3.3% faster. Numbers intended to compare the code pointed in opposite directions depending on measurement order.

The report attributed this behavior to the long signature verification stage being affected by device temperature, clock frequency, and execution order. The preserved records do not establish the separate contribution of temperature or frequency. What was directly observed was that the faster path changed when the fixed measurement order changed.

The revised experiment put both paths into a single benchmark invocation and reversed their order in the next invocation. Each path used separate in-memory state, a separate pending list, and its own execution engine. This paired measurement with alternating order produced the first table. Admission times, which had differed substantially, became nearly equal across the two paths, and the parallel path's total time was about 2.4% shorter.

Alternating the order does not eliminate every measurement error. Still, when the comparison between two paths with the same admission stage changes substantially with experiment order, there is good reason to examine the comparison method before changing the engine.

What these records can tell us

Both tables in this article come from local experiment results recorded on August 7, 2026.

Measurement conditions and mean calculation

The experiments used Java HotSpot 21.0.7 and JMH 1.37, with one benchmark harness thread. That thread count is separate from the internal workers used for signature verification or block execution.

The first experiment used three separate JVM forks, each with five one-second warmup iterations and eight one-second measurement iterations. Each value in the table is the sum of the time spent on that path divided by the sum of its execution counts across all 24 measurement iterations. The score for the full invocation containing both sequential and parallel processing was not used as the time for either individual path. The second, dependency experiment used three separate JVM forks, two one-second warmup iterations, and four one-second sample-time measurement iterations. The parallel figures in the second table are for WAVE; the READY_QUEUE and WORK_FIRST_CONTINUATION results discussed in the text also came from this dependency experiment. Unlike the first experiment's alternating comparison, these methods were measured in separate runs.

Neither results document directly records the CPU model, memory, or operating system. Hardware details from a different experiment on a nearby date have not been substituted as confirmed conditions for these measurements. These records are therefore used to explain what the comparisons taught us, not to predict performance in another environment or guarantee current performance.

Broadening the measurement scope explained why the execution gains looked small in the total. Interpreting the effects of different schedulers required distinguishing signature computation already removed from the dispatch costs that remained. Even that comparison could shift when measurement order changed.

That gives us a starting point for the next optimization: find the largest stage, reduce repeated work within it, and parallelize computation that does not need to wait on other computation. Then check the full path again under the same conditions. Node performance including database commits and consensus requires a separate measurement that covers those stages. To judge the benefit of parallelism, examine both the time saved and the total that contains it.