Skip to content

Instantly share code, notes, and snippets.

@ztellman
Last active May 26, 2019 17:08
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ztellman/5603216 to your computer and use it in GitHub Desktop.
Save ztellman/5603216 to your computer and use it in GitHub Desktop.
an exploration of the memory implications of call-site caching for protocols
;; let's create a simple protocol that just returns a number
user> (defprotocol NumberP (number [_]))
NumberP
;; now we'll create an implementation that always returns '1'
user> (deftype One [] NumberP (number [_] 1))
user.One
;; unsurprisingly, this type only has a single static value, which wraps the '1'
> (-> One .getDeclaredFields seq)
(#<Field public static final java.lang.Object user.One.const__0>)
;; but what if we define a type in terms of another object implementing this protocol?
user> (deftype Two [one] NumberP (number [_] (+ (number one) (number one))))
user.Two
;; here we see the underlying implementation of how protocols are implemented: for each dispatch
;; to a protocol within the type, we gain three implicit per-instance fields on the object
user> (-> Two .getDeclaredFields seq)
(#<Field public static final clojure.lang.Var user.Two.const__0> #<Field public static final clojure.lang.Var user.Two.const__1> #<Field public final java.lang.Object user.Two.one> #<Field private java.lang.Class user.Two.__cached_class__0> #<Field private clojure.lang.AFunction user.Two.__cached_proto_fn__0> #<Field private clojure.lang.IFn user.Two.__cached_proto_impl__0> #<Field private java.lang.Class user.Two.__cached_class__1> #<Field private clojure.lang.AFunction user.Two.__cached_proto_fn__1> #<Field private clojure.lang.IFn user.Two.__cached_proto_impl__1>)
user> (-> Two .getDeclaredFields count)
9
;; this is unavoidable if we don't know what type the object is, but even if we typehint the member
;; so we can directly dispatch, the memory overhead (and possibly also the invocation overhead?) remains
user> (deftype Two [^One one] NumberP (number [_] (+ (number one) (number one))))
user.Two
user> (-> Two .getDeclaredFields count)
9
;; for each new callsite, we gain three additional per-instance fields, without limit
user> (deftype Three [^One one] NumberP (number [_] (+ (number one) (number one) (number one))))
user.Three
user> (-> Three .getDeclaredFields count)
12
@p-himik
Copy link

p-himik commented May 26, 2019

It seems to be better in Clojure 1.10:

(-> Two .getDeclaredFields count)
=> 4

(-> Three .getDeclaredFields count)
=> 5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment