2012-04-02 10 views
6

Publiqué antes en un huge XML file - es un XML de 287 GB con volcado de Wikipedia que no quiero poner en el archivo CSV (autores de revisiones y marcas de tiempo). Logré hacer eso hasta cierto punto. Antes de obtener el error StackOverflow, pero ahora, después de resolver el primer problema, obtengo: java.lang.OutOfMemoryError: error de espacio en el montón de Java.Archivo enorme en Clojure y error de espacio en montón Java

Mi código (en parte tomado de Justin Kramer respuesta) se ve así:

(defn process-pages 
    [page] 
    (let [title  (article-title page) 
     revisions (filter #(= :revision (:tag %)) (:content page))] 
    (for [revision revisions] 
     (let [user (revision-user revision) 
      time (revision-timestamp revision)] 
     (spit "files/data.csv" 
       (str "\"" time "\";\"" user "\";\"" title "\"\n") 
       :append true))))) 

(defn open-file 
[file-name] 
(let [rdr (BufferedReader. (FileReader. file-name))] 
    (->> (:content (data.xml/parse rdr :coalescing false)) 
     (filter #(= :page (:tag %))) 
     (map process-pages)))) 

no me presento article-title, revision-user y revision-title funciones, ya que simplemente toman datos de un lugar específico en la página o hash de revisión. Cualquiera me puede ayudar con esto: soy realmente nuevo en Clojure y no entiendo el problema.

Respuesta

4

Para que quede claro, (:content (data.xml/parse rdr :coalescing false)) es perezoso. Verifique su clase o saque el primer artículo (regresará al instante) si no está convencido.

Dicho esto, un par de cosas a tener en cuenta cuando se procesan secuencias grandes: aferrarse a la cabeza y pereza no realizada/anidada. Creo que tu código sufre de esto último.

Aquí es lo que recomiendo:

1) Añadir (dorun) hasta el final de la cadena de llamadas ->>. Esto obligará a la secuencia a realizarse completamente sin aferrarse a la cabeza.

2) Cambie for en process-page a doseq. Estás escupiendo a un archivo, que es un efecto secundario, y no quieres hacerlo perezosamente aquí.

Como recomienda Arthur, es posible que desee abrir un archivo de salida una vez y seguir escribiendo en lugar de abrir & escribiendo (spit) para cada entrada de Wikipedia.

ACTUALIZACIÓN:

Aquí está una reescritura que trata de separar las preocupaciones más claramente:

(defn filter-tag [tag xml] 
    (filter #(= tag (:tag %)) xml)) 

;; lazy 
(defn revision-seq [xml] 
    (for [page (filter-tag :page (:content xml)) 
     :let [title (article-title page)] 
     revision (filter-tag :revision (:content page)) 
     :let [user (revision-user revision) 
       time (revision-timestamp revision)]] 
    [time user title])) 

;; eager 
(defn transform [in out] 
    (with-open [r (io/input-stream in) 
       w (io/writer out)] 
    (binding [*out* out] 
     (let [xml (data.xml/parse r :coalescing false)] 
     (doseq [[time user title] (revision-seq xml)] 
      (println (str "\"" time "\";\"" user "\";\"" title "\"\n"))))))) 

(transform "dump.xml" "data.csv") 

no veo nada aquí que pueda causar el uso excesivo de memoria.

+1

El punto sobre dorun podría aclararse un poco para alguien nuevo en Clojure: la función de archivo abierto como se muestra en la pregunta devuelve la secuencia de resultados de las llamadas a las páginas de proceso, y cuando se llama a la función desde el repl, imprimiendo la secuencia hace que todos los resultados se mantengan en la memoria al mismo tiempo. Llamar a dorun sobre el resultado hace que los elementos de la secuencia se evalúen y no se devuelvan, de modo que nunca es necesario tener todos los resultados en memoria al mismo tiempo. –

+0

¡Gracias por la explicación! Entiendo (con suerte) ahora cómo funciona la pereza en este fragmento de código y cambié lo que usted propuso, pero aún 'OutOfMemoryError: Java montón de espacio '. Estoy trabajando en una muestra de 1 GB del archivo final, pero aún se inicia el error de memoria. Estaría muy agradecido por cualquier ayuda. – trzewiczek

+0

Ver mi última actualización. Si aún obtiene el error OutOfMemory, no estoy seguro de por qué. Utilicé un código muy similar a este sin problemas de memoria. –

1

Desafortunadamente data.xml/parse no es flojo, intenta leer todo el archivo en la memoria y luego analizarlo.

En su lugar use el this (lazy) xml library que contiene solo la parte en la que está trabajando actualmente en el ram. Luego tendrá que volver a estructurar el código para escribir el resultado a medida que lee la entrada en lugar de recopilar todo el xml y luego emitirlo.

su línea

(:content (data.xml/parse rdr :coalescing false) 

cargará todos el XML en la memoria y luego solicitar la clave de contenido de la misma. que explotará el montón.

un esbozo de una respuesta perezosa sería algo como esto:

(with-open [input (java.io.FileInputStream. "/tmp/foo.xml") 
      output (java.io.FileInputStream. "/tmp/foo.csv"] 
    (map #(write-to-file output %) 
     (filter is-the-tag-i-want? (parse input)))) 

tener paciencia, trabajar con (> data ram) siempre lleva tiempo :)

+0

Ya está usando 'data.xml' de contrib , que como dices, es flojo. –

Cuestiones relacionadas