JetBrains-Research/bioinf-commons

Parallelism approach seems inconsistent

dievsky opened this issue · 7 comments

Some parts of bioinf-commons use the common ForkJoinPool, while other parts create a newFixedThreadPool.

For example, MoreStreams.kt uses ForkJoinPool in its chunked and forking extension methods, but ExecutorExtensions.kt methods (like await) prefer a separate pool. Span uses both approaches: FJP is used for the HMM EM expect and refill steps, and GenomeMap parallel operations (e.g. read coverage or compute peaks) work through await and use a special thread pool.

It might be better to use just one approach (common FJP, most likely). This will at least prevent an inadvertent creating of several thread pools in parallel. (And while we're at it, we might want to switch to coroutines.)

Discussion is most welcome.

olegs commented

Did you get any problems with this in real use cases? FJP is know to perform well when single operation is 100-150ms long.

Currently, the FJP code and the newFixedThreadPool code are separate and don't call each other. This might change in the future, though.

Are you saying that there's a reason why we use the two approaches here? One is better for the fast operations, and the other is better for the slow ones?

olegs commented

Yes, that's one of the reasons.

I've read some opinions on this issue, and here's what can pass for the general consensus:
"One last thing to remember is that the choosing a ForkJoinPool is only useful if the tasks create subtasks. Otherwise, it will function the same as a ThreadPoolExecutor, but with extra overhead." (https://stackify.com/java-thread-pools/)
However, expect and refill methods (which use FJP) don't seem to create any subtasks.

Update: my bad. Actually, IntRange.chunked(used in HMMIterationContext#expect and several other places) involves a ChunkSpliterator, and this approach does occasionally fork subtasks. However, this seems like a technical decision, since cutting the range into small chunks and feeding them to a fixed-thread executor all at once would likely work as efficiently.

olegs commented

@dievsky Looks like we can close the issue?

I don't see any changes, to be honest, but we can certainly close the issue by ignoring it ("don't fix what ain't broken" and all that).

Well, the one change that I see is that we no longer use newFixedThreadPool (just one usage); instead, newWorkStealingPool is used, so all our parallelism is now work-stealing (both the common FJP and the hand-made newWorkStealingPool).

olegs commented

Closing as obsolete. Lately most of parallel code shares executor in ExecutorExtensions.kt