Finn Völkel / Aug 22 2020
Clojure object size estimation
Some part of this post were adapted from a discussion on the clojure mailing list.
In a language such as Clojure that handles memory sharing of data structures transparently, it is a bit difficult to find a good measure of how much memory an object occupies. How does one count some substructure that is referenced by multiple objects?
A first option is to use java.io.ObjectOutputStream
(defn total-memory [obj]
(let [baos (java.io.ByteArrayOutputStream.)]
(with-open [oos (java.io.ObjectOutputStream. baos)]
(.writeObject oos obj))
(count (.toByteArray baos))))
0.1s
Clojure
'user/total-memory
(let [a [1 2 [1 2]]
b [2 [1 2]]
c (next a)]
[a b c])
0.1s
Clojure
Vector(3) [Vector(3), Vector(2), List(2)]
(total-memory *1)
0.2s
Clojure
891
(spit "results/ds.edn" (pr-str *2))
0.0s
Clojure
nil
empty
A second option is to use the JVM's build-in memory tools to get a size estimate. As we will see below, this is I think a better estimate of how much an object actually increases the memory footprint.
(defn gc []
(dotimes [_ 4] (System/gc)))
(defn used-memory []
(let [runtime (Runtime/getRuntime)]
(gc)
(- (.totalMemory runtime) (.freeMemory runtime))))
0.1s
Clojure
'user/used-memory
(defn measure [f]
(let [before (used-memory)
_ (def foo (binding [*in* (java.io.PushbackReader.
(clojure.java.io/reader f))]
(read)))
after (used-memory)]
(- after before)))
0.1s
Clojure
'user/measure
One can clearly see the structural sharing if one runs the following cell twice.
(measure ds.edn)
1.0s
Clojure
792
(measure ds.edn)
1.0s
Clojure
144
{:deps {com.clojure-goes-fast/clj-memory-meter {:mvn/version "0.1.3"}
compliment {:mvn/version "0.3.10"}}}
deps.edn
Extensible Data Notation