gpt4 book ai didi

serialization - Clojure:存储和编译大型派生数据结构

转载 作者:行者123 更新时间:2023-12-04 16:45:54 24 4
gpt4 key购买 nike

我有一个大数据结构,一棵树,在 ram 中占用大约 2GB。它包括叶子中的 clojure 集,以及作为分支的 refs。树是通过读取和解析一个大的平面文件并将行插入到树中来构建的。但是,这需要大约 30 秒。有没有一种方法可以构建树一次,将其发送到 clj 文件,然后将树编译到我的独立 jar 中,这样我就可以在树中查找值而无需重新读取大文本文件?我认为这将减少 30 秒的树构建,但这也将帮助我部署我的独立 jar,而无需附带文本文件。

我的第一次挥杆失败了:

(def x (ref {:zebra (ref #{1 2 3 4})}))
#<Ref@6781a7dc: {:zebra #<Ref@709c4f85: #{1 2 3 4}>}>

(def y #<Ref@6781a7dc: {:zebra #<Ref@709c4f85: #{1 2 3 4}>}>)
RuntimeException Unreadable form clojure.lang.Util.runtimeException (Util.java:219)

最佳答案

由于 JVM 的大小限制,在编译代码中嵌入这么大的数据可能是不可能的。特别是,任何一种方法的长度都不能超过 64 KiB。以我在下面进一步描述的方式嵌入数据还需要在它将要存在的类文件中包含大量内容;似乎不是一个好主意。

鉴于您使用的是只读数据结构,您可以构造一次,然后将其发送到 .clj/.edn (用于 edn,基于 Clojure 文字符号的序列化格式),然后将该文件作为“资源”包含在您的类路径中,以便将其包含在 überjar 中(在 resources/ 中,具有默认的 Leiningen 设置;它)然后将包含在 überjar 中,除非被 :uberjar-exclusions in 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

您也可以不将其添加到 überjar,而是在运行应用程序时将其添加到类路径中。这样您的 überjar 本身就不必很大。

可以使用 print-method 来处理持久性 Clojure 数据以外的内容。 (序列化时)和阅读器标签(反序列化时)。 Arthur 已经使用阅读器标签进行了演示;使用 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}"

当然你只需要 print-method序列化时定义的方法;您正在反序列化代码可以不理会它,但需要适当的数据读取器。

暂时忽略代码大小问题,因为我发现数据嵌入问题很有趣:

假设您的数据结构仅包含由 Clojure 本地处理的不可变数据(Clojure 持久集合,任意嵌套,加上原子项目,例如数字、字符串(用于此目的的原子)、关键字、符号;没有 Refs 等),您确实可以包含它在您的代码中:
(defmacro embed [x]
x)

然后生成的字节码将重新创建 x无需阅读任何内容,使用类文件中包含的常量和 clojure.lang.RT 的静态方法类(例如 RT.vectorRT.map )。

当然,这就是文字的编译方式,因为上面的宏是一个 noop。我们可以让事情变得更有趣:
(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在编译时并将结果嵌入到编译代码中(在包含适当的常量和代码以在类文件中重建数据的意义上)。在运行时,不会执行进一步的读取。

关于serialization - Clojure:存储和编译大型派生数据结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16468321/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com