Clojure中的并发笛卡尔积算法

时间:2010-04-02 22:40:29

标签: algorithm concurrency clojure parallel-processing cartesian-product

有一个很好的算法可以在Clojure中同时计算三个seq s的笛卡尔积吗?

我正在Clojure开展一个小型业余爱好项目,主要是作为学习语言及其并发功能的一种手段。在我的项目中,我需要计算三个seq的笛卡尔积(并对结果做一些事情)。

我在cartesian-product中找到clojure.contrib.combinatorics函数,效果非常好。然而,笛卡尔积的计算结果证明是该程序的瓶颈。因此,我想同时进行计算。

现在,对于map函数,有一个方便的pmap替代方案,可以神奇地使事物并发。哪个很酷:)。不幸的是,cartesian-product不存在这样的事情。我查看了源代码,但我找不到一种简单的方法让它自己并发。

另外,我尝试使用map自己实现一个算法,但我想我的算法技能不像以前那样。我设法为两个seq提出了一些丑陋的东西,但是三个绝对是一座太过分的桥梁。

那么,有没有人知道一个已经并发的算法,或者我可以自己并行化的算法?


修改

换句话说,我真正想要实现的目标是实现与此Java代码类似的东西:

for (ClassA a : someExpensiveComputation()) {
    for (ClassB b : someOtherExpensiveComputation()) {
        for (ClassC c : andAnotherOne()) {
            // Do something interesting with a, b and c
        }
    }
}

2 个答案:

答案 0 :(得分:5)

如果您用来处理笛卡尔积的逻辑不是以某种方式固有顺序,那么也许您可以将输入分成两半(可能将每个输入seq分成两部分),计算8个独立的笛卡尔积(首先 - 半个x上半部分x上半部分,上半部分x上半部分x后半部分......),处理它们然后合并结果。我希望这会给你带来很大的推动力。至于调整笛卡尔产品建筑本身的性能,我不是专家,但我确实有一些想法&观察(有时需要计算Euler项目的交叉产品),所以我试着在下面总结它们。

首先,我发现c.c.combinatorics功能在演奏部门有点奇怪。评论说它取自Knuth,我相信,所以也许下面的一个得到:(1)它对矢量非常有效,但是输入序列的矢量化成本会影响其他序列类型的性能; (2)这种编程风格一般不一定在Clojure中表现良好; (3)由于某些设计选择(如具有本地功能)而产生的累积开销很大; (4)我遗漏了一些非常重要的东西。所以,虽然我不想忽略它可能是一个很好的功能,用于一些用例(由所涉及的seqs的总数,每个seq中的元素数等等),在我的所有(不科学的)测量一个简单的for似乎更好。

然后我有两个函数,其中一个与for相当(在更有趣的测试中稍微慢一些,我认为,虽然在其他人看来实际上有点快...但不能说我准备做一个完全受过教育的比较),另一个显然更快,有一个很长的初始输入序列,因为它是第一个的限制功能并行版本。 (详情如下。)所以,首先是时间(如果你想重复它们,偶尔会抛出(System/gc)):

;; a couple warm-up runs ellided
user> (time (last (doall (pcross (range 100) (range 100) (range 100)))))
"Elapsed time: 1130.751258 msecs"
(99 99 99)
user> (time (last (doall (cross (range 100) (range 100) (range 100)))))
"Elapsed time: 2428.642741 msecs"
(99 99 99)
user> (require '[clojure.contrib.combinatorics :as comb])
nil
user> (time (last (doall (comb/cartesian-product (range 100) (range 100) (range 100)))))
"Elapsed time: 7423.131008 msecs"
(99 99 99)
;; a second time, as no warm-up was performed earlier...
user> (time (last (doall (comb/cartesian-product (range 100) (range 100) (range 100)))))
"Elapsed time: 6596.631127 msecs"
(99 99 99)
;; umm... is syntax-quote that expensive?
user> (time (last (doall (for [x (range 100)
                               y (range 100)
                               z (range 100)]
                           `(~x ~x ~x)))))
"Elapsed time: 11029.038047 msecs"
(99 99 99)
user> (time (last (doall (for [x (range 100)
                               y (range 100)
                               z (range 100)]
                           (list x y z)))))
"Elapsed time: 2597.533138 msecs"
(99 99 99)
;; one more time...
user> (time (last (doall (for [x (range 100)
                               y (range 100)
                               z (range 100)]
                           (list x y z)))))
"Elapsed time: 2179.69127 msecs"
(99 99 99)

现在功能定义:

(defn cross [& seqs]
  (when seqs
    (if-let [s (first seqs)]
      (if-let [ss (next seqs)]
        (for [x  s
              ys (apply cross ss)]
          (cons x ys))
        (map list s)))))

(defn pcross [s1 s2 s3]
  (when (and (first s1)
             (first s2)
             (first s3))
    (let [l1 (count s1)
          [half1 half2] (split-at (quot l1 2) s1)
          s2xs3 (cross s2 s3)
          f1 (future (for [x half1 yz s2xs3] (cons x yz)))
          f2 (future (for [x half2 yz s2xs3] (cons x yz)))]
      (concat @f1 @f2))))

我相信所有版本都会产生相同的结果。 pcross可以扩展到处理更多序列,或者在分割其工作负载的方式上更加复杂,但这就是我作为第一个近似值得出的结果......如果你用你的程序测试它(可能适应)当然,这对你的需求而言,我很想知道结果。

答案 1 :(得分:-3)

'clojure.contrib.combinatorics有一个笛卡尔积函数。 它返回一个惰性序列,可以跨越任意数量的序列。