DEV Community

Jupiter Soft
Jupiter Soft

Posted on

My CPU Was Correct, But It Was Wasting Millions of Instructions

Summer Bug Smash: Clear the Lineup 🐛🛹

This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.

Project Overview

Memora8 is an experimental multi-core processor architecture and system simulator.

The project includes a complete execution environment: a custom CPU model, memory subsystem, runtime, and development tools. The goal is not only to execute programs, but to explore how software behavior and processor architecture interact.

As a real workload, I implemented parallel BLAKE3 hashing on an 8-CPU configuration.

In this setup, CPU0 works as an orchestrator:

  • distributes work between worker CPUs;
  • manages PageMover operations;
  • processes events;
  • collects intermediate results from worker FIFOs.

The BLAKE3 implementation was functionally correct and produced valid digests.

Performance analysis revealed a hidden inefficiency in the orchestrator CPU idle path: the processor was correct, but it was spending execution time checking for work that was not available.

Bug Fix or Performance Improvement

During BLAKE3 benchmarking, I found a hidden performance issue in the CPU orchestration path.

The BLAKE3 pipeline used an event-driven design: worker CPUs performed hashing, while CPU0 coordinated the system. However, when no worker result, PageMover transition, or reduction progress was available, CPU0 continued executing the orchestration loop:

check events
check PageMover
check worker FIFOs

no progress

repeat
Enter fullscreen mode Exit fullscreen mode

The system was correct, but CPU0 was spending instructions only to discover that nothing had changed.

The first step was to track whether an orchestration pass produced any progress:

let progress = service_page_movers();
Enter fullscreen mode Exit fullscreen mode

Every useful operation now reports activity. If the complete pass finishes without progress:

if (progress == 0)
    hp.cpu_sleep_ticks(64);
Enter fullscreen mode Exit fullscreen mode

CPU0 enters a bounded sleep state instead of immediately polling again.

This required changes not only in the BLAKE3 runtime code, but also in the CPU simulator. Memora8 already supported CPU sleep, but deferred sleep commands did not preserve timed sleep semantics. A request for:

sleep for N ticks
Enter fullscreen mode Exit fullscreen mode

could become:

sleep until external wake
Enter fullscreen mode Exit fullscreen mode

after command retirement.

The fix introduced:

  • preservation of deferred sleep mode and timeout values;
  • cpu_sleep_ticks() and cpu_sleep_kticks() runtime operations;
  • correct timed wake behavior in the simulator;
  • updated scheduler handling so timed sleepers remain future runnable work instead of being treated as fully idle.

The result was a CPU that can efficiently wait for future work instead of spending execution time repeatedly checking for it.

Code

The source code is maintained in a private repository.

The implementation is available in commit:

b4ac2bb Add timed CPU sleep for BLAKE3 orchestration
Date: July 25, 2026
Enter fullscreen mode Exit fullscreen mode

Main changes:

  • examples/blake3/amod.sjs

    • added orchestration progress tracking;
    • replaced empty polling cycles with bounded CPU sleep when no progress is made.
  • examples/blake3/bmod.sjs

    • removed unnecessary explicit wake calls from worker CPUs.
  • examples/blake3/hash_pipeline_common.sjs

    • added cpu_sleep_ticks() and cpu_sleep_kticks() runtime helpers.
  • mr8sim/iomem.h

    • added deferred timed sleep state handling;
    • preserved sleep mode and timeout information across deferred CPU commands;
    • moved CPU sleep operations into the device-based sleep path.
  • mr8sim/system.cpp

    • updated simulator scheduling so CPUs sleeping for a timeout are treated as future runnable work and simulated time continues advancing.
  • tests/mr8sim_shared_sram_test.cpp

    • added regression coverage for timed sleep, timeout wake-up, external wake interruption, and invalid sleep commands.

The change spans the BLAKE3 orchestration code, runtime API, CPU sleep model, simulator scheduling, and regression tests.

My Improvements

The main challenge was not making BLAKE3 compute faster, but making the CPU orchestration model more efficient.

CPU0 is responsible for coordinating the parallel workload. It must react quickly when workers, FIFOs, or PageMover operations make progress, but it should not spend execution cycles repeatedly checking the same state when nothing has changed.

The first change was introducing progress tracking in the orchestration loop.

Previously, every iteration performed the same checks regardless of whether anything useful happened. The new approach makes each subsystem report progress:

let progress = service_page_movers();
Enter fullscreen mode Exit fullscreen mode

Worker result collection and reduction steps also update this state.

After a complete orchestration pass:

if (progress == 0)
    hp.cpu_sleep_ticks(64);
Enter fullscreen mode Exit fullscreen mode

the CPU enters a bounded sleep period instead of immediately starting another polling cycle.

The important design decision was to avoid a permanent sleep. CPU0 cannot simply wait for one external event because progress may come from different sources. A timed sleep provides a middle ground:

check system
      |
      v
progress available?
      |
   yes -> continue
      |
   no
      |
 sleep N ticks
      |
 wake and check again
Enter fullscreen mode Exit fullscreen mode

The second part of the change was making this behavior possible in the processor simulator itself.

Memora8 already had CPU sleep support, but deferred sleep commands did not preserve the requested sleep mode. A timed sleep request could lose its timeout information during command retirement and become an external-wake-only sleep.

The simulator was updated to preserve:

  • sleep mode;
  • timeout value;
  • deferred sleep state.

Timed sleepers are also no longer treated as completely idle CPUs. The simulator continues advancing system time until the timeout expires or an external wake occurs.

This change required coordination between multiple layers:

BLAKE3 orchestration
        ↓
Sekura JS runtime helpers
        ↓
CPU sleep device interface
        ↓
CPU state management
        ↓
system simulator scheduling
Enter fullscreen mode Exit fullscreen mode

The result is a CPU model where idle waiting is represented as an architectural state instead of repeated instruction execution.

Results

The optimization was tested using parallel BLAKE3 workloads from 8 KB to 8 MB on an 8-CPU Memora8 configuration.

The goal was not to change the BLAKE3 algorithm or reduce the amount of useful computation. The goal was to reduce unnecessary orchestration overhead while keeping the execution result identical.

The final benchmark measures the complete optimized orchestration path.

Across the tested workloads, the optimized version showed:

  • up to 11.6% reduction in CPU0 instructions;
  • up to 9.5% reduction in branch bubbles;
  • up to 4.9% reduction in total executed instructions;
  • up to 24.5% wall-clock improvement on smaller workloads where orchestration overhead dominates;
  • approximately 5% wall-clock improvement on the largest 8 MB workload.

Example: 8 MB BLAKE3 workload.

Before:

Elapsed:        510.005 s
Total cycles:   534,106,223
Total insns:    2,483,144,031
Enter fullscreen mode Exit fullscreen mode

After:

Elapsed:        482.985 s
Total cycles:   514,918,425
Total insns:    2,415,378,301
Enter fullscreen mode Exit fullscreen mode

The largest reduction was in orchestrator activity:

CPU0 instructions:

Before:
452,195,508

After:
399,811,800
Enter fullscreen mode Exit fullscreen mode

This shows that the optimization reduced the amount of work performed by the coordinating CPU rather than changing the hashing workload itself.

Correctness was verified across all tested input sizes.

For every workload from 8 KB to 8 MB, the BLAKE3 digest remained identical between the original and optimized versions.

The final result is a processor model where idle waiting is represented explicitly instead of being simulated as repeated polling instructions. The CPU still reacts to new work, but no longer spends execution time repeatedly proving that no work is available.

Why This Was Interesting

This was not a bug that produced an incorrect result.

The CPU executed correctly and every BLAKE3 digest matched.

The problem was that the processor model represented waiting as execution. The CPU spent instructions proving that no work was available.

The fix changed waiting from active computation into an explicit architectural state.

This is the key difference between optimizing an application and optimizing a processor architecture: the cost of waiting itself becomes part of the design.

Top comments (0)