2013-04-27 22 views
8

Dili hakkında daha fazla bilgi edinmek için Clojure'da basit bir masaüstü arama motoru yazıyorum. Şimdiye kadar, programımın metin işleme aşamasında performans gerçekten çok kötü. Ben ettik metin işleme sırasındaClojure'da metin işleme performansı nasıl iyileştirilir?

:

  • temizleyin istenmeyen karakterleri;
  • Dizeyi küçük harfe dönüştürün;
  • Sözcüklerin bir listesini almak için belgeyi bölün;
  • Her sözcüğü, belgedeki olaylarla ilişkilendiren bir harita oluşturun. İşte

kodudur: Aşağıdaki çıkışları görebileceğiniz gibi ben Haskell bu sorunun another implementation sahip olduğundan

(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))))) 

, ben de karşılaştırıldı.

Clojure sürümü:

$ 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 sürümü: - Clojure uygulanmasında dönüşüm performansını öldürüyor

$ 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 

I (>tembel dizisidize) düşünüyorum. Bunu nasıl geliştirebilirim?

Not: Bu testlerde kullanılan kod ve veriler here'dan indirilebilir.

1) yerine karakterler arasında sizin charschar-val için, sadece doğrudan değer karşılaştırmaları yapmak haritalama: Eğer bunu yapabilir

+1

neler kavanoz için JVM başlangıç ​​zamanı nedir? Haskell'in benzer yükü var mı? – georgek

+0

Makinemdeki JVM başlangıç ​​zamanı yaklaşık bir saniyedir. Haskell'in çalışma zamanı sistemi (RTS) nedeniyle bazı yükleri olduğunu düşünüyorum, fakat JVM'den oldukça düşük olmalıyım. – luisgabriel

+0

s/I olmalıdır/it/ – luisgabriel

cevap

4

Bazı şeyler büyük ihtimal bu kodu hızlandırmak olacaktır. Bu, aynı sebepten dolayı daha hızlıdır, Java'da daha hızlıdır.

2) Tek karakter değerlerini tam uzunlukta dizelere dönüştürmek için tekrar tekrar str kullanın. Yine, karakter değerlerini doğrudan kullanmayı düşünün. Yine, nesne oluşturma, Java'daki gibi yavaştır.

3) clojure.core/frequencies ile process-content'u değiştirmelisiniz. Belki nasıl daha hızlı olduğunu görmek için frequencies kaynağını inceleyin.

4) Bir döngüde (hash-map)'u güncelleştirmeniz gerekiyorsa, transient'u kullanın. Eğer geçişlerini kullanmak niye dolayısıyla yavaş ve -

http://clojuredocs.org/clojure_core/clojure.core/transient Ayrıca (hash-map) bir PersistentArrayMap döndüren unutmayın, bu yüzden update-in yapılan her çağrı ile yeni örneklerini oluştururken: Bkz.

5) Bu senin arkadaşın: (set! *warn-on-reflection* true) - Sen sadece karşılaştırma aşkına 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

Mükemmel! sadece türünü yazmak toplam süreyi 7 saniyeye düşürdü! ;) – luisgabriel

+0

Güzel! 8-) Yardım ettiğim için sevindim! – noahlz

+0

"clojure.core/frequencies" işlevini kullanamıyorum çünkü indeksleme ve sorgulama gibi daha ileri aşamalara kelime konumlarına ihtiyacım var. – luisgabriel

0

yararlanabilecek yansıma biraz var, burada esaslı bir regexp'in Clojure versiyonu

(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)))