2013-04-27 16 views
8

Ich schreibe eine einfache Desktop-Suchmaschine in Clojure als eine Möglichkeit, mehr über die Sprache zu lernen. Bis jetzt ist die Leistung während der Textverarbeitungsphase meines Programms wirklich schlecht.Wie verbessert man die Textverarbeitungsleistung in Clojure?

Während der Textverarbeitung Ich habe zu:

  • Abgleich unerwünschte Zeichen;
  • Konvertieren Sie die Zeichenfolge in Kleinbuchstaben;
  • Teilen Sie das Dokument auf, um eine Liste von Wörtern zu erhalten.
  • Erstellen Sie eine Zuordnung, die jedes Wort seinen Vorkommen im Dokument zuordnet.

Hier ist der Code:

(ns txt-processing.core 
    (:require [clojure.java.io :as cjio]) 
    (:require [clojure.string :as cjstr]) 
    (:gen-class)) 

(defn all-files [path] 
    (let [entries (file-seq (cjio/file path))] 
    (filter (memfn isFile) entries))) 

(def char-val 
    (let [value #(Character/getNumericValue %)] 
    {:a (value \a) :z (value \z) 
    :A (value \A) :Z (value \Z) 
    :0 (value \0) :9 (value \9)})) 

(defn is-ascii-alpha-num [c] 
    (let [n (Character/getNumericValue c)] 
    (or (and (>= n (char-val :a)) (<= n (char-val :z))) 
     (and (>= n (char-val :A)) (<= n (char-val :Z))) 
     (and (>= n (char-val :0)) (<= n (char-val :9)))))) 

(defn is-valid [c] 
    (or (is-ascii-alpha-num c) 
     (Character/isSpaceChar c) 
     (.equals (str \newline) (str c)))) 

(defn lower-and-replace [c] 
    (if (.equals (str \newline) (str c)) \space (Character/toLowerCase c))) 

(defn tokenize [content] 
    (let [filtered (filter is-valid content) 
     lowered (map lower-and-replace filtered)] 
    (cjstr/split (apply str lowered) #"\s+"))) 

(defn process-content [content] 
    (let [words (tokenize content)] 
    (loop [ws words i 0 hmap (hash-map)] 
     (if (empty? ws) 
     hmap 
     (recur (rest ws) (+ i 1) (update-in hmap [(first ws)] #(conj % i))))))) 

(defn -main [& args] 
    (doseq [file (all-files (first args))] 
    (let [content (slurp file) 
      oc-list (process-content content)] 
     (println "File:" (.getPath file) 
       "| Words to be indexed:" (count oc-list))))) 

Wie ich another implementation dieses Problem in Haskell, ich verglichen beide, wie Sie in den folgenden Ausgaben zu sehen.

Clojure Version:

$ lein uberjar 
Compiling txt-processing.core 
Created /home/luisgabriel/projects/txt-processing/clojure/target/txt-processing-0.1.0-SNAPSHOT.jar 
Including txt-processing-0.1.0-SNAPSHOT.jar 
Including clojure-1.5.1.jar 
Created /home/luisgabriel/projects/txt-processing/clojure/target/txt-processing-0.1.0-SNAPSHOT-standalone.jar 
$ time java -jar target/txt-processing-0.1.0-SNAPSHOT-standalone.jar ../data 
File: ../data/The.Rat.Racket.by.David.Henry.Keller.txt | Words to be indexed: 2033 
File: ../data/Beyond.Pandora.by.Robert.J.Martin.txt | Words to be indexed: 1028 
File: ../data/Bat.Wing.by.Sax.Rohmer.txt | Words to be indexed: 7562 
File: ../data/Operation.Outer.Space.by.Murray.Leinster.txt | Words to be indexed: 7754 
File: ../data/The.Reign.of.Mary.Tudor.by.James.Anthony.Froude.txt | Words to be indexed: 15418 
File: ../data/.directory | Words to be indexed: 3 
File: ../data/Home.Life.in.Colonial.Days.by.Alice.Morse.Earle.txt | Words to be indexed: 12191 
File: ../data/The.Dark.Door.by.Alan.Edward.Nourse.txt | Words to be indexed: 2378 
File: ../data/Storm.Over.Warlock.by.Andre.Norton.txt | Words to be indexed: 7451 
File: ../data/A.Brief.History.of.the.United.States.by.John.Bach.McMaster.txt | Words to be indexed: 11049 
File: ../data/The.Jesuits.in.North.America.in.the.Seventeenth.Century.by.Francis.Parkman.txt | Words to be indexed: 14721 
File: ../data/Queen.Victoria.by.Lytton.Strachey.txt | Words to be indexed: 10494 
File: ../data/Crime.and.Punishment.by.Fyodor.Dostoyevsky.txt | Words to be indexed: 10642 

real 2m2.164s 
user 2m3.868s 
sys  0m0.978s 

Haskell Version:

$ ghc -rtsopts --make txt-processing.hs 
[1 of 1] Compiling Main    (txt-processing.hs, txt-processing.o) 
Linking txt-processing ... 
$ time ./txt-processing ../data/ +RTS -K12m 
File: ../data/The.Rat.Racket.by.David.Henry.Keller.txt | Words to be indexed: 2033 
File: ../data/Beyond.Pandora.by.Robert.J.Martin.txt | Words to be indexed: 1028 
File: ../data/Bat.Wing.by.Sax.Rohmer.txt | Words to be indexed: 7562 
File: ../data/Operation.Outer.Space.by.Murray.Leinster.txt | Words to be indexed: 7754 
File: ../data/The.Reign.of.Mary.Tudor.by.James.Anthony.Froude.txt | Words to be indexed: 15418 
File: ../data/.directory | Words to be indexed: 3 
File: ../data/Home.Life.in.Colonial.Days.by.Alice.Morse.Earle.txt | Words to be indexed: 12191 
File: ../data/The.Dark.Door.by.Alan.Edward.Nourse.txt | Words to be indexed: 2378 
File: ../data/Storm.Over.Warlock.by.Andre.Norton.txt | Words to be indexed: 7451 
File: ../data/A.Brief.History.of.the.United.States.by.John.Bach.McMaster.txt | Words to be indexed: 11049 
File: ../data/The.Jesuits.in.North.America.in.the.Seventeenth.Century.by.Francis.Parkman.txt | Words to be indexed: 14721 
File: ../data/Queen.Victoria.by.Lytton.Strachey.txt | Words to be indexed: 10494 
File: ../data/Crime.and.Punishment.by.Fyodor.Dostoyevsky.txt | Words to be indexed: 10642 

real 0m9.086s 
user 0m8.591s 
sys  0m0.463s 

Ich denke, das (String ->faul Sequenz) Umwandlung in der Clojure Implementierung die Leistung tötet. Wie kann ich es verbessern?

P. S: Der gesamte Code und Daten in diesen Tests verwendet werden, können here heruntergeladen werden.

+1

was ist die JVM Startzeit für das Glas? Hat Haskell ähnliche Kosten? – georgek

+0

Die JVM-Startzeit in meiner Maschine beträgt etwa eine Sekunde. Ich denke, Haskell hat wegen des Laufzeitsystems (RTS) etwas Overhead, aber ich sollte deutlich niedriger sein als die JVM. – luisgabriel

+0

s/ich sollte/es sollte/ – luisgabriel

Antwort

4

Einige Dinge, die Sie wahrscheinlich diesen Code tun würde, könnte beschleunigen:

1) Statt Ihre chars zu char-val abzubilden, tun nur direkte Wertvergleiche zwischen den Charakteren. Dies ist aus dem gleichen Grund schneller, in Java wäre es schneller.

2) Sie verwenden wiederholt str Einzelzeichenwerte zu vollwertigen Strings zu konvertieren. Erwägen Sie wiederum, die Zeichenwerte direkt zu verwenden. Auch hier ist die Erstellung von Objekten langsam, genau wie in Java.

3) Sie sollten process-content mit clojure.core/frequencies ersetzen. Vielleicht inspizieren Sie frequencies Quelle, um zu sehen, wie es schneller ist.

4) Wenn Sie eine (hash-map) in einer Schleife aktualisieren müssen, verwenden Sie transient. Siehe: http://clojuredocs.org/clojure_core/clojure.core/transient

Beachten Sie auch, dass (hash-map) eine PersistentArrayMap zurückgibt, so dass Sie schaffen neue Instanzen mit jedem Aufruf update-in - daher langsam und warum sollten Sie Transienten verwenden.

5) Dies ist dein Freund: (set! *warn-on-reflection* true) - Sie haben ziemlich viel Reflexion, die von type hints

Reflection warning, scratch.clj:10:13 - call to isFile can't be resolved. 
Reflection warning, scratch.clj:13:16 - call to getNumericValue can't be resolved. 
Reflection warning, scratch.clj:19:11 - call to getNumericValue can't be resolved. 
Reflection warning, scratch.clj:26:9 - call to isSpaceChar can't be resolved. 
Reflection warning, scratch.clj:30:47 - call to toLowerCase can't be resolved. 
Reflection warning, scratch.clj:48:24 - reference to field getPath can't be resolved. 
Reflection warning, scratch.clj:48:24 - reference to field getPath can't be resolved. 
+0

perfekt! nur die typenhinweise setzen die gesamte zeit auf 7s reduziert! ;) – luisgabriel

+0

Schön! 8-) Freut mich zu helfen! – noahlz

+0

Ich kann 'clojure.core/frequencies' nicht verwenden, weil ich die Wortpositionen für weitere Phasen wie Indexierung und Abfrage benötige. – luisgabriel

0

Nur zum Vergleich willen profitieren könnten, ist hier ein regexp basierte Clojure Version

(defn re-index 
    "Returns lazy sequence of vectors of regexp matches and their start index" 
    [^java.util.regex.Pattern re s] 
    (let [m (re-matcher re s)] 
     ((fn step [] 
      (when (. m (find)) 
      (cons (vector (re-groups m)(.start m)) (lazy-seq (step)))))))) 

(defn group-by-keep 
    "Returns a map of the elements of coll keyed by the result of 
    f on each element. The value at each key will be a vector of the 
    results of r on the corresponding elements." 
    [f r coll] 
    (persistent! 
    (reduce 
     (fn [ret x] 
     (let [k (f x)] 
      (assoc! ret k (conj (get ret k []) (r x))))) 
     (transient {}) coll))) 

(defn word-indexed 
    [s] 
    (group-by-keep 
    (comp clojure.string/lower-case first) 
    second 
    (re-index #"\w+" s))) 
Verwandte Themen