2011-07-07 6 views
6

私はちょうど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の初心者ですので、私が何かばかげた、または非慣用的なことをしている場合は、私のコードを細断してください。

答えて

4

最初のバージョンを編集して読みやすくしました。私はいくつかのコメントを持っていますが、無決定的に役立つ文:

  1. あなたは物事をスローダウンされたものに何ら実際の証拠とトランジェントとtypehintsを追加しました。これらの操作を不用意に使用することで事態を大幅に遅らせることは可能です。実際に何が減速しているのかを知るためにプロフィールを作成することをお勧めします。あなたの選択は合理的だと思われますが、明らかに効果がなかったタイプヒントを削除しました(たとえば、コンパイラが(File ... ...)がFileオブジェクトを生成するというヒントを必要としないなど)。

  2. Clojure(実際には任意のlisp)は、some-agent~someAgentを強く好みます。接頭辞の構文は、-が無作為のコンパイラによる減算として解析できるという心配がないことを意味しているので、より明確な名前を付けることができます。

  3. tallyResultやaddFileToProcessのように、ここでは一切定義していない一連の関数への呼び出しを含めます。おそらく彼らは演技版でそれらを使用しているので、彼らはうまく動作しますが、それを含めないことで、他の誰かがそれをぶつけたり、スピードアップしたりするのを困難にしました。

  4. I/Oバウンド処理ではなくsend-offを考慮してください。sendは、スレッドを束縛しないように境界付きスレッドプールを使用します。ここでは、エージェントを1つしか使用せずにシリアライズしているので、これはおそらく重要ではありませんが、将来的には重要なケースを実行します。

とにかく、約束通り、あなたの最初のバージョンのより読みやすく書き直し:

(defn process-file 
    [_ collector-agent ^String file-name] 
    (let [file-obj (File. file-name) 
     file-totals (transient {:files 0, :bytes 0})] 
    (cond (.isDirectory file-obj) 
      (do 
      (doseq [^File dir (.listFiles file-obj) 
        :when (.isDirectory dir)] 
       (send collector-agent addFileToProcess 
        (.getPath dir))) 
      (send collector-agent tallyResult *agent*) 
      (reduce (fn [current-total new-item] 
         (assoc! current-total 
           :files (inc (:files current-total)) 
           :bytes (+ (:bytes current-total) new-item))) 
        file-totals 
        (map #(.length ^File %) 
         (filter #(.isFile ^File %) 
           (.listFiles file-obj)))) - 
      (persistent! file-totals)) 

      (.isFile file-obj) 
      (do (send collector-agent tallyResult *agent*) 
       {:files 1, :bytes (.length file-obj)})))) 

編集:あなたの削減の結果を捨てることで、間違った方法で、トランジェントを使用しています。 (assoc! m k v)です。mオブジェクトを変更して返すことができますが、それがより便利で効率的な場合は別のオブジェクトを返すことがあります。だからあなたはもっと同じものが必要です(persistent! (reduce ...))

関連する問題