Я не эксперт в производительности / параллелизме Haskell, но я думаю, что здесь происходит несколько вещей.
Во-первых, действительно есть эта строка:
Question fil <$> affirmativeTree <*> negativeTree `using` evalTraversable parEvalTree
Предположительно, можно ожидать, что первая часть этой строки создает структуру данных, которая выглядит как
+-------+
| Right |
+-------+
|
+----------+
| Question |
+----------+
| | |
+-----------------+ | +-----------+
| +----+ |
| | |
+-----+ +-------------------+ +----------------+
| fil | | THUNK | | THUNK |
+-----+ | (affirmativeTree) | | (negativeTree) |
+-------------------+ +----------------+
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * evalTraversable
* * * * * * * * * * * * * * * 10 * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * *
*1014* *1014* К сожалению, это не то, чтослучается, и я думаю, что проблема связана с дополнительным Either String
.Чтобы оценить линию Question
(даже просто для WHNF), как и evalTraversable
, мы должны выяснить, будет ли результат Right decisonTree
или Left _
.Это означает, что affirmativeTree
и negativeTree
должны быть оценены до WHNF, прежде чем parEvalTree
сможет вступить в игру.К сожалению, из-за структуры вашего кода, оценка любого дерева в WHNF таким способом заставляет почти все - выбор фильтра должен быть принудительным, чтобы увидеть, какую ветвь принимает рекурсивный вызов constructTree
, а затем свой собственный.рекурсивные вызовы trainModel
принудительно передаются в WHNF таким же образом.
Этого можно избежать, сначала сначала выделив affirmativeTree
и negativeTree
отдельно, а затем просматривая результаты в форме WHNF только после того, как ониу вас было время для того, чтобы полностью вычислить, выполнив что-то вроде этого:
uncurry (Question fil) <$> bisequence ((affirmativeTree, negativeTree) `using` parTuple2 rdeepseq rdeepseq)
Если вы запустите свой код с этой строкой, заменив оригинал, и загрузите его в ThreadScope, вы увидите, что явно наблюдается некоторое увеличениев параллелизме: график активности кратковременно поднимается выше 1 в нескольких местах, а выполнение выполняется между HEC в нескольких местах.К сожалению, подавляющее большинство времени программы все еще тратится на последовательное выполнение.
Я попытался немного разобраться в этом, и я думаю, что что-то в вашем коде построения дерева может быть немного смещено вправо.Я добавил несколько traceMarker
с и traceEvent
с, и похоже, что часто существует довольно большой дисбаланс между положительной и отрицательной сторонами фильтра, что делает параллельное выполнение не очень хорошим: положительное поддерево имеет тенденцию заканчиваться оченьочень быстро, в то время как отрицательное поддерево занимает много времени, создавая то, что выглядит как по существу последовательное выполнение.В некоторых случаях положительное поддерево настолько мало, что ядро, которое инициировало вычисления, завершает его, а затем начинает отрицательное поддерево, прежде чем другое ядро может проснуться, чтобы украсть работу.Вот откуда берутся длинные прогоны на одном ядре в ThreadScope.Короткий промежуток времени с достаточным параллелизмом, который вы можете увидеть в начале графика, - это время, в течение которого выполняется отрицательное поддерево первого фильтра, поскольку это основной фильтр с отрицательным поддеревом, достаточно большим, чтобы реально внести вкладк параллелизму.Есть также несколько похожих (но гораздо меньших) событий позже в трассировке, где создаются отрицательные деревья разумного размера.
Я ожидаю, что если вы сделаете указанное выше изменение и попытаетесь найти фильтры, которые более равномерно разделяютнабор данных, вы должны увидеть довольно большое увеличение параллелизации этого кода.