デカルト積を処理するために使用しているロジックが本質的にシーケンシャルでない場合は、入力を半分に分割し(おそらく、各入力シーケンスを2つに分割し)、8つの別々のデカルト積を計算します(前半x最初) -半分x前半、前半x前半x後半、...)、それらを処理してから、結果を結合します。私はこれがあなたにすでにかなりの後押しを与えることを期待します。デカルト積の構築自体のパフォーマンスを微調整することに関しては、私は専門家ではありませんが、いくつかのアイデアと観察があります(プロジェクトオイラーの外積を計算する必要がある場合があります)ので、以下に要約してみました。
まずc.c.combinatorics
、パフォーマンス部門の機能が少しおかしいと思います。コメントは、Knuthから取得したものだと私は信じているので、おそらく次のいずれかが得られます。(1)ベクトルに対して非常にパフォーマンスが高いが、入力シーケンスをベクトル化するコストは、他のシーケンスタイプのパフォーマンスを低下させます。(2)このスタイルのプログラミングは、Clojureでは必ずしもうまく機能するとは限りません。(3)何らかの設計上の選択(そのローカル機能を持つなど)によって発生する累積オーバーヘッドが大きい。(4)本当に重要なものが欠けています。したがって、いくつかのユースケース(関連するシーケンスの総数、各シーケンスの要素の数などによって決定される)に使用するのに優れた関数である可能性を否定したくはありませんが、すべての私の(非科学的)測定は単純ですfor
うまくいくようです。
次に、私の2つの機能があります。そのうちの1つは、for
(より興味深いテストではやや遅いと思いますが、他のテストでは実際にはやや速いようです...完全に作成する準備ができているとは言えません)に匹敵します。知識に基づいた比較)、もう1つは、最初のバージョンの機能が制限された並列バージョンであるため、最初の入力シーケンスが長く、明らかに高速です。(詳細は以下のとおりです。)したがって、最初にタイミングを設定します((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
より多くのシーケンスを処理するように拡張したり、ワークロードを分割する方法をより洗練したりすることもできますが、それが最初の概算として思いついたものです...これをプログラムでテストする場合(おそらくニーズに合わせて調整する場合)もちろん)、結果を知りたいと思います。