Skip to content

Commit

Permalink
[#161] Clojure-side Transit optimizations
Browse files Browse the repository at this point in the history
Specifically:

  1. Now cache read-handlers and write-handlers

     Can't tell if this actually realistically helps perf since I've
     got no handler maps to test against; feedback welcome.

  2. Now cache (thread-local) writer (allows baos reuse)

     Doesn't seem to actually realistically help perf from what I can
     tell. Might nix this later since this does add some complexity
     to the impl.
  • Loading branch information
ptaoussanis committed May 15, 2016
1 parent 84ca62f commit 6207a2b
Showing 1 changed file with 66 additions and 26 deletions.
92 changes: 66 additions & 26 deletions src/taoensso/sente/packers/transit.cljx
Original file line number Diff line number Diff line change
Expand Up @@ -22,46 +22,86 @@
[cognitect.transit :as transit]
[taoensso.sente.interfaces :as interfaces :refer (pack unpack)]))

;;;; TODO
;; * Note that Transit-cljs doesn't seem to actually have msgpack support atm?
;; * Invesigate the actual cost of cljs+clj side writer/reader construction -
;; is it worth caching these?
;; * Is it worth considering a cljs-side baos/bais pool?

#+clj
(defn- get-charset [transit-fmt]
;; :msgpack appears to need ISO-8859-1 to retain binary data correctly when
;; string-encoded, all other (non-binary) formats can get UTF-8:
(if (enc/kw-identical? transit-fmt :msgpack) "ISO-8859-1" "UTF-8"))

(def ^:private -transit-writer
#+cljs (encore/memoize_ (fn [ fmt opts] (transit/writer fmt opts)))
#+clj (fn [baos fmt opts] (transit/writer baos fmt opts)))
#+clj
(def ^:private cache-read-handlers
"reader-opts -> reader-opts with cached read handler map"
(let [cache (enc/memoize_ (fn [m] (transit/read-handler-map m)))]
(fn [reader-opts]
(if-let [m (:handlers reader-opts)]
(assoc reader-opts :handlers (cache m))
reader-opts))))

#+clj
(def ^:private cache-write-handlers
"writer-opts -> writer-opts with cached write handler map"
(let [cache (enc/memoize_ (fn [m] (transit/write-handler-map m)))]
(fn [writer-opts]
(if-let [m (:handlers writer-opts)]
(assoc writer-opts :handlers (cache m))
writer-opts))))

#+clj
(def ^:private transit-writer-fn-proxy
(enc/thread-local-proxy
(fn [fmt opts]
(let [^String charset (get-charset fmt)
opts (cache-write-handlers opts)
^ByteArrayOutputStream baos (ByteArrayOutputStream. 512)
writer (transit/writer baos fmt opts)]
(fn [x]
(transit/write writer x)
(let [result (.toString baos charset)]
(.reset baos)
result))))))

(def ^:private -transit-reader
#+cljs (encore/memoize_ (fn [ fmt opts] (transit/reader fmt opts)))
#+clj (fn [bais fmt opts] (transit/reader bais fmt opts)))
(def ^:private get-transit-writer-fn
"Returns thread-safe (fn [x-to-write])"
#+cljs
(enc/memoize_
(fn [fmt opts]
(let [writer (transit/writer fmt opts)]
(fn [x] (transit/write writer x)))))

#+clj
(fn [fmt opts]
(let [thread-local-transit-writer-fn (.get ^ThreadLocal transit-writer-fn-proxy)]
(thread-local-transit-writer-fn fmt opts))))

(def ^:private get-transit-reader-fn
"Returns thread-safe (fn [str-to-read])"
#+cljs
(enc/memoize_
(fn [fmt opts]
(let [reader (transit/reader fmt opts)]
(fn [s] (transit/read reader s)))))

#+clj
(fn [fmt opts]
(let [^String charset (get-charset fmt)
opts (cache-read-handlers opts)]
(fn [s]
(let [ba (.getBytes ^String s ^String charset)
^ByteArrayInputStream bais (ByteArrayInputStream. ba)
reader (transit/reader bais fmt opts)]
(transit/read reader))))))

(deftype TransitPacker [transit-fmt writer-opts reader-opts]
taoensso.sente.interfaces/IPacker
(pack [_ x]
#+cljs (transit/write (-transit-writer transit-fmt writer-opts) x)
#+clj (let [charset (get-charset transit-fmt)
^ByteArrayOutputStream baos (ByteArrayOutputStream. 512)]
(transit/write (-transit-writer baos transit-fmt writer-opts) x)
(.toString baos ^String charset)))

(unpack [_ s]
#+cljs (transit/read (-transit-reader transit-fmt reader-opts) s)
#+clj (let [charset (get-charset transit-fmt)
ba (.getBytes ^String s ^String charset)
^ByteArrayInputStream bais (ByteArrayInputStream. ba)]
(transit/read (-transit-reader bais transit-fmt reader-opts)))))
(pack [_ x] ((get-transit-writer-fn transit-fmt writer-opts) x))
(unpack [_ s] ((get-transit-reader-fn transit-fmt reader-opts) s)))

(defn get-transit-packer "Returns a new TransitPacker"
([ ] (get-transit-packer :json {} {}))
([transit-fmt] (get-transit-packer transit-fmt {} {}))
([transit-fmt writer-opts reader-opts]
(have? [:el #{:json :msgpack}] transit-fmt)
;; No transit-cljs support for msgpack atm
(have? [:el #{:json #_:msgpack}] transit-fmt)
(have? map? writer-opts reader-opts)
(TransitPacker. transit-fmt writer-opts reader-opts)))

Expand Down

0 comments on commit 6207a2b

Please sign in to comment.