【问题标题】:clojure pmap vs mapclojure pmap vs map
【发布时间】:2013-11-26 00:14:51
【问题描述】:

我在 cojure REPL 中测试了 clojure 函数映射和 pmap,如下所示。这让我很困惑:为什么并行 pmap 比 map 慢?

user=> (def lg (range 1 10000000))
user=> (time (def rs (doall (pmap #(* % %) lg))))

"Elapsed time: **125739.056** msecs"

# -------------------------------------------------------
user=> (def lg (range 1 10000000))
user=> (time (def rs (doall (map #(* % %) lg))))

"Elapsed time: **5804.485** msecs"

**PS: the machine has 8 cores**

【问题讨论】:

    标签: dictionary clojure pmap


    【解决方案1】:

    对于每个并行处理任务,由于任务协调,都会产生一些开销。 pmap 将映射函数分别应用于不同线程中的每个元素。由于pmap 返回的惰性序列被消耗,消费者线程必须与生产者线程协调。 pmap 的定义方式,产生的每个元素都会产生这种开销。

    考虑到这一点,当您使用pmap 来计算一个简单的函数(例如平方数,如您的示例中所示)时,线程协调其活动所需的时间会超过实际计算价值。正如文档字符串所说,pmap“仅对 f 的时间支配协调开销的 计算密集型 函数有用”(添加了 empasis)。在这些情况下,pmap 将比map 花费更长的时间,无论您拥有多少内核。

    要真正看到pmap 的好处,您必须选择一个“更难”的问题。在某些情况下,这可能就像将输入序列划分为块一样简单。然后可以用pmap处理这个chunk序列,然后运行concat得到最终输出。

    例如:

    (defn chunked-pmap [f partition-size coll]
      (->> coll                           ; Start with original collection.
    
           (partition-all partition-size) ; Partition it into chunks.
    
           (pmap (comp doall              ; Map f over each chunk,
                       (partial map f)))  ; and use doall to force it to be
                                          ; realized in the worker thread.
    
           (apply concat)))               ; Concatenate the chunked results
                                          ; to form the return value.
    

    但是,对序列进行分区并在最后连接块也有开销。例如,至少在我的机器上,chunked-pmap 在您的示例中仍然大大低于map。不过,它可能对某些功能有效。

    另一种提高pmap 有效性的方法是将工作分区在整个算法中的不同位置。例如,假设我们对计算点对之间的欧几里得距离感兴趣。虽然并行化 square 函数已被证明是无效的,但我们可能会幸运地并行化整个距离函数。实际上,我们希望在更高的层次上划分任务,但这就是它的要点。

    简而言之,并行算法的性能对任务的划分方式很敏感,并且您选择的级别对于您的测试来说过于精细。

    【讨论】:

      【解决方案2】:

      Rörd 是正确的,使用 pmap 有很大的开销。考虑改用减速器:

      (def l (range 10000000))
      
      (time (def a (doall (pmap #(* % %) l))))
      "Elapsed time: 14674.415781 msecs"
      
      (time (def a (doall (map #(* % %) l))))
      "Elapsed time: 1119.107447 msecs"
      
      (time (def a (doall (into [] (r/map #(* % %) l)))))
      "Elapsed time: 1049.754652 msecs"
      

      【讨论】:

      • 请务必与您系统上的 (time (def a (mapv #(* % %) l))) 和 Clojure 版本进行比较
      【解决方案3】:

      创建线程、在它们之间拆分工作负载和重新组装结果会产生一些开销。您将需要一个运行时间明显长于#(* % %) 的函数才能看到pmap 的速度提升(当然它还取决于您在问题中未指定的CPU 核心数)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-07-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-16
        • 2016-08-18
        • 2016-05-14
        相关资源
        最近更新 更多