2013-05-09 3 views
4

큰 데이터 구조 인 트리가 있으며, 램에서 약 2GB를 차지합니다. 그것은 잎에 clojure 세트를 포함하고, 가지로 심판을 포함합니다. 트리는 큰 플랫 파일을 읽고 구문 분석하고 행을 트리에 삽입하여 만들어집니다. 그러나 이것은 약 30 초 걸립니다. 한 번 트리를 빌드하고 clj 파일로 내 보낸 다음 트리를 내 독립 실행 형 항아리로 컴파일하여 큰 텍스트 파일을 다시 읽지 않고 트리에서 값을 조회 할 수있는 방법이 있습니까? 나는 이것이 30 초짜리 트리 구조를 다듬을 것이라고 생각하지만, 이것은 또한 타기를 위해 텍스트 파일을 필요로하지 않고 독립형 항아리를 배치하는데 도움이 될 것입니다. 이것Clojure : 대형 파생 데이터 구조 저장 및 컴파일

내 첫 스윙 실패 : 대신 많은 값에 대한 참조 FO 나무의 단일 값을로 트리를 구성 할 수있는 경우

(def x (ref {:zebra (ref #{1 2 3 4})})) 
#<[email protected]: {:zebra #<[email protected]: #{1 2 3 4}>}> 

(def y #<[email protected]: {:zebra #<[email protected]: #{1 2 3 4}>}>) 
RuntimeException Unreadable form clojure.lang.Util.runtimeException (Util.java:219) 
+1

읽기 전용 구조로되어 있다면,'ref's를 완전히 피할 것을 제안합니다. 액세스가 느려지고 직렬화가 더 까다로워 질뿐만 아니라 전체 구조가 더 많은 메모리를 사용하게됩니다. 불변 영구 데이터 구조는 여러분의 친구입니다. – mikera

답변

6

JVM에 부과 된 크기 제한 때문에 이처럼 큰 데이터를 컴파일 된 코드에 포함하지 못할 수도 있습니다. 특히 한 가지 방법으로 64 KiB를 초과 할 수 없습니다. 아래에 더 자세히 설명하는 방식으로 데이터를 임베드하려면 클래스 파일에 많은 양의 데이터가 포함될 필요가 있습니다. 좋은 생각 같지는 않습니다.

당신이 데이터 구조를 사용하고있는 점을 감안 읽기 전용, 당신은 다음, 그 파일을 포함 (즉, edn에 대한 Clojure의 문자 표기법에 따라 직렬화 형식의)를 .clj/.edn로 방출, 한 번을 구성 할 수 있습니다 클래스 경로에 "리소스"로 포함되어 있으므로 überjar에 포함됩니다 (기본값 Leiningen 설정을 사용하는 resources/) :uberjar-exclusions에 의해 제외되지 않는 한 überjar에 포함되어 project.clj에있는 런타임에 리소스에서 읽습니다. Clojure 독자의 전속력 :

(ns foo.core 
    (:require [clojure.java.io :as io])) 

(defn get-the-huge-data-structure [] 
    (let [r (io/resource "huge.edn") 
     rdr (java.io.PushbackReader. (io/reader r))] 
    (read r))) 

;; if you then do something like this: 

(def ds (get-the-huge-data-structure)) 

;; your app will load the data as soon as this namespace is required; 
;; for your :main namespace, this means as soon as the app starts; 
;; note that if you use AOT compilation, it'll also be loaded at 
;; compile time 

당신은 또한 그것을 überja에 추가 할 수 없습니다 r을 사용하는 대신 앱을 실행할 때 classpath에 추가하십시오. 이렇게하면 überjar 자체가 거대하지 않아도됩니다.

지속적인 Clojure 데이터 이외의 처리 print-method (직렬화 할 때) 및 리더 태그 (비 직렬화 할 때)를 사용하여 수행 할 수 있습니다. Arthur는 이미 리더 태그를 사용하여 시연했습니다. print-method를 사용하여, 당신은 당신 만 직렬화 할 때 정의 된 print-method 방법이 필요

물론
(defmethod print-method clojure.lang.Ref [x writer] 
    (.write writer "#ref ") 
    (print-method @x writer)) 

;; from the REPL, after doing the above: 

user=> (pr-str {:foo (ref 1)}) 
"{:foo #ref 1}" 

뭔가를 할 것; deserialize 코드는 혼자 남겨 둘 수 있지만 적절한 데이터 판독기가 필요합니다. 데이터 구조는 기본적으로 Clojure의 (Clojure의 영속 콜렉션에 의해 처리 불변의 데이터가 포함 된 가정

임의로 중첩 플러스 :


내가 흥미있는 문제를 내장 데이터를 발견하면, 잠시 동안의 코드 크기 문제를 무시하고 같은 숫자, 문자열 (이 목적을 위해 원자), 키워드, 기호로 원자 항목, 더 참고 문헌 등), 당신은 실제로 당신의 코드에 포함 할 수 없습니다 :

(defmacro embed [x] 
    x) 

생성 된 바이트 코드는 다음과 x를 다시합니다 클래스 파일에 포함 된 상수와 clojure.lang.RT 클래스의 정적 메서드를 사용하여 모든 것을 읽습니다. RT.vectorRT.map).

위의 매크로는 아무 것도 없기 때문에 이것은 물론 리터럴을 컴파일하는 방법입니다. 우리는 비록 일을 더 재미있게 만들 수 있습니다

(ns embed-test.core 
    (:require [clojure.java.io :as io]) 
    (:gen-class)) 

(defmacro embed-resource [r] 
    (let [r (io/resource r) 
     rdr (java.io.PushbackReader. (io/reader r))] 
    (read r))) 

(defn -main [& args] 
    (println (embed-resource "foo.edn"))) 

이 컴파일시에 foo.edn를 읽고 (클래스 파일의 데이터를 재구성하기 위해 적절한 상수 및 코드 등의 의미에서) 컴파일 된 코드의 결과를 포함합니다. 런타임에는 더 이상의 판독이 수행되지 않습니다.

3

다음 트리를 인쇄하고 그것을 읽을 수있을 것입니다 . 심판은 읽을 수 없으므로 자신의 구문 분석을 수행하지 않고도 전체 트리를 읽을 수있는 것으로 처리 할 수 ​​없습니다.

을 사용하여 유형을 만들어 트리에 대한 인쇄 및 읽기 기능을 추가하는 것이 좋습니다.

먼저 각 EDN 태그/타입

user> (defn parse-map-ref [m] (ref (apply hash-map m))) 
#'user/parse-map-ref 
user> (defn parse-set-ref [s] (ref (set s))) 
#'user/parse-set-ref 

의 내용에 대한 핸들러를 정의 여기

문자열로부터 세트와지도에 대한 참조를 생성 데이터 독자를 사용하여 최소한의 예는 그런 다음 텍스트 태그 핸들러를 연결지도를 데이터 독자 바인딩 :

(def y-as-string 
    "#user/map-ref [:zebra #user/set-ref [1 2 3 4]]") 

user> (def y (binding [*data-readers* {'user/set-ref user/parse-set-ref 
             'user/map-ref user/parse-map-ref}] 
       (read-string y-as-string))) 

user> y 
#<[email protected]: {:zebra #<[email protected]: #{1 2 3 4}>}> 
,691,363 (210)

이도 더 깊이 중첩 된 나무와 함께 작동합니다 : 당신은 REF-지도의 유형을 정의하는 경우가 훨씬 쉬울 것 비록 나무에서

(def z-as-string 
    "#user/map-ref [:zebra #user/set-ref [1 2 3 4] 
        :ox #user/map-ref [:amimal #user/set-ref [42]]]") 

user> (def z (binding [*data-readers* {'user/set-ref user/parse-set-ref 
             'user/map-ref user/parse-map-ref}] 
       (read-string z-as-string))) 
#'user/z 
user> z 
#<[email protected]: {:ox #<[email protected]: {:amimal #<[email protected]: #{42}>}>, 
       :zebra #<[email protected]: #{1 2 3 4}>}> 

생산 문자열, 인쇄 방식의 multimethod을 확장하여 수행 할 수 있으며, de-type을 사용하여 ref-set을 사용하면 프린터가 어느 ref에서 어떤 문자열을 생성해야하는지 알 수 있습니다.

일반적으로 문자열로 읽는 것이 너무 느리면 프로토콜 버퍼와 같은 더 빠른 이진 직렬화 라이브러리가 있습니다.

+0

정말 가능하지 않습니까? 위의 예제에서'#

+0

clojure 리더 유형은 파일에 기록하고 다시 읽을 수 있는지에 따라 "읽기 가능"또는 "읽을 수 없음"중 하나입니다. 독자가 읽는 용어는 직렬화 및 구문 분석이 가능한지에 대한 의견이 아닙니다 그들은. –

+0

이해, 감사합니다. 지금 내가 뭘하고있는 것은 데이터 파일을 텍스트 파일에 저장하고 불행히도 내 응용 프로그램을 명령 행에서 쓸모 없게 만들고, 데몬/서버가되어야합니다 ...나는 일반적인 전략이 컴파일 된 자바 프로그램에 큰 데이터 구조를 포함시키는 것이 무엇인지 궁금해하지만 ... 코딩 방법으로 크기가 64k로 제한되어있다. –

3

이 구조는 변경되지 않습니까? 그렇지 않은 경우 Java 직렬화를 사용하여 구조를 유지하십시오. 탈 직렬화는 매번 재구성하는 것보다 훨씬 빠릅니다.

+0

예, 이것은 한 번 만들어진 다음 읽기 전용 방식으로 사용됩니다. 궁금하다면 직렬화 된 객체를 대상 항아리로 컴파일 한 다음 'java -jar treeLookup.jar arg1 arg2'를 실행할 때로드 할 수 있습니까? –

+0

예, 직렬화 된 객체를 대상 jar에 포함시키고 필요할 때 또는 클래스 초기화시 deserialize 할 수 있습니다. 나는 인생이 모든 심판 없이는 더 쉬울 것이라는 @arthurulfeldt에 동의한다. –

+0

클래스가 초기화 될 때로드하는 방법에 대한 참조를 가르쳐 주시겠습니까? –

관련 문제