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.