Using new parallel ExecutionContext

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

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

  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:

  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 WaitGroups 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.