簡體   English   中英

在Haskell中並行構造樹的策略

[英]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)

threadscope輸出

我懷疑使用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評估affirmativeTreenegativeTree 不幸的是,由於你的代碼的結構,以這種方式評估任何一個樹到WHNF幾乎所有東西---必須強制過濾器選擇,以便看到遞歸constructTree調用采用哪個分支,然后是自己的遞歸調用to trainModel被迫以同樣的方式進入WHNF。

這可以通過首先單獨激發affirmativeTreenegativeTree來避免,然后只有在他們有時間完全計算之后才以WHNF形式查看結果,通過這樣做:

uncurry (Question fil) <$> bisequence ((affirmativeTree, negativeTree) `using` parTuple2 rdeepseq rdeepseq)

如果使用此行替換原始代碼並將其加載到ThreadScope中運行代碼,您將看到並行性顯然有所增加:活動圖在幾個地方短暫地超過1,並且在幾個地方的HEC之間執行跳轉。 不幸的是,程序的絕大部分時間仍然花在順序執行上。

我試着稍微研究一下,我認為你的樹形結構代碼中的某些東西可能有點偏向右邊。 我添加了一些traceMarkertraceEvent ,看起來過濾器的正負兩側之間經常存在相當大的不平衡,這使得並行執行不能很好地工作:正子樹傾向於非常快速地完成,而負面子樹需要很長時間,創建看起來基本上順序執行的東西。 在一些情況下,正子樹非常小,以至於引發計算的核心完成它,然后在另一個核心醒來之前開始負面子樹以竊取工作。 這是ThreadScope中單個核心的長距離運行的地方。 您可以在圖表開頭看到的具有相當並行性的短時間段是第一個過濾器的負子樹正在執行的時間,因為這是主要過濾器,其負數子樹大到足以真正貢獻並行性。 稍后在跟蹤中還會發生一些相似(但小得多)的事件,其中會創建合理大小的負樹。

我希望如果您進行上述更改並嘗試查找更均勻地對數據集進行分區的過濾器,您應該會看到此代碼的可並行性有相當大的增加。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM