【问题标题】:Strategies for constructing tree in parallel in HaskellHaskell中并行构建树的策略
【发布时间】:2018-12-09 11:03:50
【问题描述】:

我有一个项目,我正在 Haskell 中构建一个 Decision Tree。 生成的树会有多个相互独立的分支,所以我认为它们可以并行构建。

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 (我在 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 parallel-processing tree


【解决方案1】:

我不是 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 _。这意味着affirmativeTreenegativeTree 必须先评估为WHNF,然后parEvalTree 才能发挥作用。不幸的是,由于您的代码结构,以这种方式将任一树评估为 WHNF 几乎会强制执行所有操作 --- 必须强制过滤器选择才能查看递归 constructTree 调用采用哪个分支,然后查看它自己的分支对 trainModel 的递归调用以相同的方式强制执行 WHNF。

这可以通过先分别触发 affirmativeTreenegativeTree 来避免,然后在它们有时间完全计算后才查看 WHNF 形式的结果,方法如下:

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

如果你运行你的代码,用这行替换原始代码并将其加载到 ThreadScope,你会看到并行度明显增加:活动图在一些地方短暂超过 1,并且在 HEC 之间的执行跳转几个地方。不幸的是,程序的绝大部分时间仍然花在顺序执行上。

我尝试对此进行了一些研究,并且我认为您的树构造代码中的某些内容可能有点偏右。我添加了一些traceMarkers 和traceEvents,看起来过滤器的正面和负面之间经常存在相当大的不平衡,这使得并行执行不能很好地工作:正子树往往会非常完成很快,而负子树需要很长时间,创建看起来基本上是顺序执行的东西。在某些情况下,正子树非常小,以至于引发计算的核心完成了它,然后在另一个核心唤醒以窃取工作之前开始负子树。这就是 ThreadScope 中单个内核上的长时间运行的来源。您可以在图的开头看到具有相当多并行性的短时间段是执行第一个过滤器的负子树的时间,因为这是具有足够大的负子树以真正贡献的主过滤器到并行。在跟踪的后面还有一些类似(但小得多)的事件,在这些事件中创建了合理大小的负树。

我希望,如果您进行上述更改并尝试找到更均匀地划分数据集的过滤器,您应该会看到此代码的可并行性有相当大的提高。

【讨论】:

    猜你喜欢
    • 2016-07-27
    • 1970-01-01
    • 1970-01-01
    • 2014-11-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多