# Using new parallel ExecutionContext

**URL:** <https://forum.crystal-lang.org/t/using-new-parallel-executioncontext/9069>\
**Category:** Help & Support\
**Created:** [July 22, 2026, 4:13pm UTC](https://forum.crystal-lang.org/t/using-new-parallel-executioncontext/9069 "2026-07-22T16:13:38Z")\
**Posts on this page:** 1\
**Showing post:** 27

<div class="post-metadata">

**Author:** ![jzakiya](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/jzakiya/32/278_2.png) [@jzakiya](https://forum.crystal-lang.org/u/jzakiya)\
**Post date:** [September 3, 2026, 9:56pm UTC](https://forum.crystal-lang.org/t/using-new-parallel-executioncontext/9069/27 "2026-09-03T21:56:00Z")

</div>

From @ralsina’s parallel processing code explanation, I did a similar technique for mine.

> [@A small experiment on Crystal performance](https://forum.crystal-lang.org/t/a-small-experiment-on-crystal-performance/9114):
>
> Yesterday I ran into smallpt, a tiny raytracer in 100LOC in C++. It turns out it works as a nice cross-lang CPU benchmark! So I ported it to crystal and ran some benchmarks comparing with other implementations and … well, the result is [README.md](https://ralsina.github.io/smallpt-cr/) Tl;DR: crystal gives you readable code with practically the same performance as C++ (as expected) and multithreaded performance scales linearly (this is an ideal workload TBH)

All results were achieved using `BI` (brain intelligence), not `AI` (artificial intelligence).

```auto
  threads = System.cpu_count # number of system threads
  next_pair = Atomic.new(0) # index for next twinpair residue
  pairs_done = Atomic.new(0) # count of finished twinpair residues
  wg = WaitGroup.new(threads) # create as many WGs as threads
  Fiber::ExecutionContext.default.resize(threads)

  threads.times do
    spawn do
      while (i = next_pair.add(1)) < pairscnt
        lastwins[i], cnts[i] = twins_sieve(restwins[i], ...)
        print "\r#{pairs_done.add(1)} of #{pairscnt} twinpairs done"
      end
      wg.done
  end end
  wg.wait

```

Here each thread is first `spawned` in parallel, and only started once. Within each the `while` loop `atomically` increments the index value for the `residue pairs` array, thus they are all processed in order without contention. It now uses minimum memory, and no longer hangs for any input values. It also eliminates the need for an external data `producer`.

However again, it’s still suboptimal for performance because each thread still must manage its input data generation before data processing can begin. So for optimum performance, distributing data within a `thread pool` is faster. Conceptually I want to be able to do something like the following:

```auto
  threads = System.cpu_count # number of system threads
  pairs_done = Atomic.new(0) # count of finished twinpair residues
  wc = WaitCount.new(pairscnt) # create as many WCs as residue pairs
  Fiber::ExecutionContext.default.resize(maximum: threads)

  restwins.each_with_index do |rhi, i|
    spawn do
      lastwins[i], cnts[i] = twins_sieve(rhi, ...)
      print "\r#{pairs_done.add(1)} of #{pairscnt} twinpairs done"
      wc.done
  end end
  wc.wait

```

Here `WaitCount.new(pairscnt)` replaces `WaitGroup`s and indicates the number of times the `thread pool` is accessed. Then `wc.done` decrements the count, like `wg.done` decrements the `fiber` count, while `wc.wait` monitors them until it reaches `0`.

Since the number of threads is small|finite, each `spawned` worker in the pool doesn’t need to be assigned a unique fiber, so you can have a `fiber pool` that cycles thru each available thread. Thus, the growing memory issue for large `wg` counts can be eliminated, as now only one count value is used with a small `fiber|threads pool`.

Well, this is one conceptualization of how it could be done using `Crystal`’s existing structure.

---

_[View the full topic](https://forum.crystal-lang.org/t/using-new-parallel-executioncontext/9069)._
