私はちょうどVenkat Subramaniamによる "JVMでの同時処理のプログラミング"を読み終えました。その著者はその例として、ディレクトリツリーのファイルサイズを数えます。彼は並行処理を使用しない実装、キューの使用、ラッチの使用、およびスカラーアクターの使用を示しています。私のシステムでは、/ usrディレクトリ(OSX 10.6.8、Core Duo 2 Ghz、Intel G1 ssd 160GB)を繰り返して実行すると、すべての同時実行(キュー、ラッチ、スケーラアクタ)が9秒以内に実行できます。再帰的ファイルシステムアルゴリズムは、命令的なスタイルで処理する必要がありますか?
私はClojureを学んでおり、エージェントを使用してScalaのアクターバージョンをClojureに移植することに決めました。残念ながら、私は11-12秒の平均をとっていましたが、他のものよりかなり遅いです。
(defn processFile
[fileProcessor collectorAgent ^String fileName]
(let [^File file-obj (File. ^String fileName)
fileTotals (transient {:files 0, :bytes 0})]
(cond
(.isDirectory file-obj)
(do
(doseq [^File dir (.listFiles file-obj) :when (.isDirectory dir)]
(send collectorAgent addFileToProcess (.getPath dir)))
(send collectorAgent tallyResult *agent*)
(reduce (fn [currentTotal newItem] (assoc! currentTotal :files (inc (:files currentTotal))
:bytes (+ (:bytes currentTotal) newItem)))
fileTotals
(map #(.length ^File %) (filter #(.isFile ^File %) (.listFiles file-obj))))
(persistent! fileTotals))
(.isFile file-obj) (do (send collectorAgent tallyResult *agent*) {:files 1, :bytes (.length file-obj)}))))
あなたは、私が使用してみました気づくでしょう:私の髪を引っ張っ日過ごした後、私は次のコードビット(processFileは、私はファイル処理剤(単数または複数)に送る機能である犯人であることを発見しました。タイプ-ヒントとパフォーマンスを向上させるための過渡的な、無駄にすべてが私は次で上記のコードを置き換える:
(defn processChildren
[children]
(loop [entries children files 0 bytes 0 dirs '()]
(let [^File child (first entries)]
(cond
(not (seq entries)) {:files files, :bytes bytes, :dirs dirs}
(.isFile child) (recur (rest entries) (inc files) (+ bytes (.length child)) dirs)
(.isDirectory child) (recur (rest entries) files bytes (conj dirs child))
:else (recur (rest entries) files bytes dirs)))))
(defn processFile
[fileProcessor collectorAgent ^String fileName]
(let [{files :files, bytes :bytes, dirs :dirs} (processChildren (.listFiles (File. fileName)))]
(doseq [^File dir dirs]
(send collectorAgent addFileToProcess (.getPath dir)))
(send collectorAgent tallyResult *agent*)
{:files files, :bytes bytes}))
をこのバージョンでは、Scalaのバージョンよりも速くない場合は額面上で実行し、アルゴリズムとほぼ同じですScalaバージョンで使用されていました。アルゴリズムの機能的なアプローチも同様に機能すると私は単純に仮定しました。
だからこそ、この長い質問は次のようなものです。2番目のバージョンはなぜ高速ですか?
私の仮説は、ディレクトリーの内容についてmap/filter/reduceを使用する最初のバージョンは、2番目のバージョンのかなり命令的なディレクトリー処理よりも「機能的」ですが、ディレクトリーの内容複数回繰り返されています。ファイルシステムのI/Oが遅いため、プログラム全体が苦しんでいます。
私が正しいと仮定して、は、再帰的なファイルシステムアルゴリズムがパフォーマンスのための命令的アプローチを好まなければならないと言っても過言ではありませんか?
私はClojureの初心者ですので、私が何かばかげた、または非慣用的なことをしている場合は、私のコードを細断してください。