[英]Strategies for constructing tree in parallel in Haskell
我有一個項目,我在Haskell中構建一個決策樹 。 生成的樹將具有多個彼此獨立的分支,因此我認為它們可以並行構建。
DecisionTree
數據類型的定義如下:
data DecisionTree =
Question Filter DecisionTree DecisionTree |
Answer DecisionTreeResult
instance NFData DecisionTree where
rnf (Answer dtr) = rnf dtr
rnf (Question fil dt1 dt2) = rnf fil `seq` rnf dt1 `seq` rnf dt2
這是構造樹的算法的一部分
constructTree :: TrainingParameters -> [Map String Value] -> Filter -> Either String DecisionTree
constructTree trainingParameters trainingData fil =
if informationGain trainingData (parseFilter fil) < entropyLimit trainingParameters
then constructAnswer (targetVariable trainingParameters) trainingData
else
Question fil <$> affirmativeTree <*> negativeTree `using` evalTraversable parEvalTree
where affirmativeTree = trainModel trainingParameters passedTData
negativeTree = trainModel trainingParameters failedTData
passedTData = filter (parseFilter fil) trainingData
failedTData = filter (not . parseFilter fil) trainingData
parEvalTree :: Strategy DecisionTree
parEvalTree (Question f dt1 dt2) = do
dt1' <- rparWith rdeepseq dt1
dt2' <- rparWith rdeepseq dt2
return $ Question f dt1' dt2'
parEvalTree ans = return ans
trainModel
遞歸調用constructTree
。 並行的相關路線是
Question fil <$> affirmativeTree <*> negativeTree `using` evalTraversable parEvalTree
我正在用GHC標志-threaded -O2 -rtsopts -eventlog
它並使用stack exec -- performance-test +RTS -A200M -N -s -l
運行它stack exec -- performance-test +RTS -A200M -N -s -l
(我在2核機器上)。
但它似乎並沒有並行運行
SPARKS: 164 (60 converted, 0 overflowed, 0 dud, 0 GC'd, 104 fizzled)
INIT time 0.000s ( 0.009s elapsed)
MUT time 29.041s ( 29.249s elapsed)
GC time 0.048s ( 0.015s elapsed)
EXIT time 0.001s ( 0.006s elapsed)
Total time 29.091s ( 29.279s elapsed)
我懷疑使用rdeepseq
和並行策略進行遞歸調用可能存在一些問題。 如果一些經驗豐富的Haskeller會發出聲響,那真的會讓我的一天成真:)
我不是Haskell性能/並行性方面的專家,但我認為這里有一些事情正在發生。
首先,確實有這條線:
Question fil <$> affirmativeTree <*> negativeTree `using` evalTraversable parEvalTree
據推測,人們可能會期望該行的第一部分構建一個看起來像的數據結構
+-------+
| Right |
+-------+
|
+----------+
| Question |
+----------+
| | |
+-----------------+ | +-----------+
| +----+ |
| | |
+-----+ +-------------------+ +----------------+
| fil | | THUNK | | THUNK |
+-----+ | (affirmativeTree) | | (negativeTree) |
+-------------------+ +----------------+
然后evalTraversable
將看到Right
並在Question
上運行parEvalTree
,導致兩個thunk被激發以進行並行的深度評估。
不幸的是,這不是發生的事情,我認為問題是由於額外的Either String
。 為了評估Question
行(即使只是對WHNF),如evalTraversable
必須,我們必須弄清楚結果是一個Right decisonTree
還是Left _
。 這意味着在parEvalTree
可以發揮作用之前,必須向WHNF評估affirmativeTree
和negativeTree
。 不幸的是,由於你的代碼的結構,以這種方式評估任何一個樹到WHNF幾乎所有東西---必須強制過濾器選擇,以便看到遞歸constructTree
調用采用哪個分支,然后是自己的遞歸調用to trainModel
被迫以同樣的方式進入WHNF。
這可以通過首先單獨激發affirmativeTree
和negativeTree
來避免,然后只有在他們有時間完全計算之后才以WHNF形式查看結果,通過這樣做:
uncurry (Question fil) <$> bisequence ((affirmativeTree, negativeTree) `using` parTuple2 rdeepseq rdeepseq)
如果使用此行替換原始代碼並將其加載到ThreadScope中運行代碼,您將看到並行性顯然有所增加:活動圖在幾個地方短暫地超過1,並且在幾個地方的HEC之間執行跳轉。 不幸的是,程序的絕大部分時間仍然花在順序執行上。
我試着稍微研究一下,我認為你的樹形結構代碼中的某些東西可能有點偏向右邊。 我添加了一些traceMarker
和traceEvent
,看起來過濾器的正負兩側之間經常存在相當大的不平衡,這使得並行執行不能很好地工作:正子樹傾向於非常快速地完成,而負面子樹需要很長時間,創建看起來基本上順序執行的東西。 在一些情況下,正子樹非常小,以至於引發計算的核心完成它,然后在另一個核心醒來之前開始負面子樹以竊取工作。 這是ThreadScope中單個核心的長距離運行的地方。 您可以在圖表開頭看到的具有相當並行性的短時間段是第一個過濾器的負子樹正在執行的時間,因為這是主要過濾器,其負數子樹大到足以真正貢獻並行性。 稍后在跟蹤中還會發生一些相似(但小得多)的事件,其中會創建合理大小的負樹。
我希望如果您進行上述更改並嘗試查找更均勻地對數據集進行分區的過濾器,您應該會看到此代碼的可並行性有相當大的增加。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.