Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Native Clojure

Jan Stępień
September 14, 2018

Native Clojure

GraalVM challenges the status quo on the JVM. This newly-released just-in-time compiler brings substantial speed improvements and support for polyglot applications. It also allows us to translate our JVM bytecode into small self-contained native binaries. In this session we’ll explore Graal’s impact on Clojure.

We’ll use GraalVM to build native binaries with simple command-line tools. We’ll discuss the method’s limitations and their impact. Finally, we’ll build complete Clojure web applications weighing a fraction of their traditional JVM incarnations.

As seen at ClojuTRE 2018 in Helsinki.

Jan Stępień

September 14, 2018
Tweet

More Decks by Jan Stępień

Other Decks in Programming

Transcript

  1. (ns pprint.main (:require [clojure.pprint :refer [pprint]]) (:gen-class)) (defn -main [&

    path] (-> (read *in*) (get-in (mapv read-string path)) pprint))
  2. $ echo {:a [5 3]} \ | time java -jar

    pprint.jar :a 1 3 1.51 real 112MB maximum resident set size
  3. $ echo {:a [5 3]} \ | java -XX:TieredStopAtLevel=1 \

    -jar pprint.jar :a 1 3 1.06 real 96MB maximum resident set size
  4. $ echo {:a [5 3]} \ | time lumo pprint.cljs

    :a 1 3 0.56 real 128MB maximum resident set size
  5. $ native-image --no-server \ --static -jar pprint.jar classlist: 3,596.11 ms

    (...) image: 4,671.96 ms write: 14,763.97 ms [total]: 122,614.21 ms
  6. $ echo {:a [5 3]} \ | time ./pprint :a

    1 3 0.01 real 12MB maximum resident set size
  7. Time Memory JVM 1.10 s 100 MB Lumo 0.60 s

    130 MB Native 0.01 s 12 MB
  8. (ns json2edn.core (:require [clojure.data.json :as json])) (defn -main [] (->>

    (repeatedly #(json/read *in* :key-fn keyword :eof-error? false :eof-value ::eof)) (take-while #(not= ::eof %)) (run! prn))) ;; See also Taylor Wood’s piece at ;; blog.taylorwood.io/2018/05/02/graal-clojure
  9. $ cat package.json | time json2edn {:description "Check if a

    value is an object", :engines {:node ">=0.10.0"}, :license "MIT", :repository "sindresorhus/is-obj", :name "is-obj", :scripts {:test "xo && ava"}, :keywords ["obj" "object" "is" "check" "test" "type"], :author {:name "Sindre Sorhus", :email "[email protected]", :url "sindresorhus.com"}, :files ["index.js"], :version “1.0.1"} 0.01 real 10MB maximum resident set size
  10. user=> (require '[spec-provider.provider :as sp]) nil user=> (def inferred-specs #_=>

    (sp/infer-specs #_=> [{:a 8 :b "foo" :c :k} #_=> {:a 10 :b "bar" :c "k"} #_=> {:a 1 :b "baz" :c "k"}] #_=> :toy/small-map)) #'user/inferred-specs user=>
  11. user=> (require '[spec-provider.provider :as sp]) nil user=> (def inferred-specs #_=>

    (sp/infer-specs #_=> [{:a 8 :b "foo" :c :k} #_=> {:a 10 :b "bar" :c "k"} #_=> {:a 1 :b "baz" :c "k"}] #_=> :toy/small-map)) #'user/inferred-specs user=> (sp/pprint-specs inferred-specs 'toy 's) (s/def ::c (s/or :keyword keyword? :string string?)) (s/def ::b string?) (s/def ::a integer?) (s/def ::small-map (s/keys :req-un [::a ::b ::c]))
  12. (ns spec-provider.main (:require [spec-provider.provider :as sp] [clojure.string :as str] [clojure.edn

    :as edn]) (:gen-class)) (defn -main [spec-name] (let [kw (edn/read-string spec-name) read-one #(edn/read {:eof ::eof} *in*) samples (sequence (take-while (complement #{::eof})) (repeatedly read-one)) specs (sp/infer-specs samples kw)] (sp/pprint-specs specs (namespace kw) 's))))
  13. $ cat node_modules/*/package.json \ | json2edn | spec-provider :node/package |

    zprint ;; ... (s/def ::repository (s/or :map (s/keys :req-un [::type ::url]) :simple string?)) (s/def ::engines (s/keys :req-un [::node] :opt-un [::iojs ::npm])) (s/def ::description string?) (s/def ::package (s/keys :req-un [::description ::name ::repository ::version] :opt-un [::author ::bin ::browser ::bugs ::contributors ::dependencies ::devDependencies ::engines ::files ::homepage ::icon ::keywords ::license ::main ::maintainers ::optionalDependencies ::peerDependencies ::scripts ::verb ::xo])) 0.29 real 103MB maximum resident set size
  14. $ curl localhost:8080/kv/city -i -X PUT -d val=Helsinki HTTP/1.1 201

    Created Content-Length: 9 Date: Fri, 14 Sep 2018 10:04:22 GMT Helsinki $ curl localhost:8080/kv/city -i HTTP/1.1 200 OK Content-Length: 9 Date: Fri, 14 Sep 2018 10:04:27 GMT Helsinki
  15. (ns webkv.main (:require [org.httpkit.server :as http] [ring.middleware.defaults :refer [wrap-defaults api-defaults]]

    [bidi.ring :refer [make-handler]]) (:gen-class)) ;; We want to be sure none of our calls relies ;; on reflection. (set! *warn-on-reflection* 1) ;; This is where we store our data. (def ^String tmpdir (System/getProperty "java.io.tmpdir")) ;; That's how we find a file given a key. ;; Keys must match the given pattern. (defn file [^String key] {:pre [(re-matches #"^[A-Za-z-]+$" key)]} (java.io.File. tmpdir key)) ;; Here we handle GET requests. We just ;; read from a file. (defn get-handler [{:keys [params]}] {:body (str (slurp (file (params :key))) "\n")}) ;; This is our PUT request handler. Given ;; a key and a value we write to a file. (defn put-handler [{:keys [params]}] (let [val (params :val)] (spit (file (params :key)) val) {:body (str val "\n"), :status 201})) ;; Here's the routing tree of our application. ;; We pick the handler depending on the HTTP ;; verb. On top of that we add an extra middle- ;; ware to parse data sent in requests. (def handler (-> ["/kv/" {[:key] {:get #'get-handler :put #'put-handler}}] (make-handler) (wrap-defaults api-defaults))) ;; Finally, we've got all we need to expose ;; our handler over HTTP. (defn -main [] (http/run-server handler {:port 8080}) (println " http://localhost:8080")) 7 M B
  16. FROM ubuntu AS BASE RUN apt-get update RUN apt-get install

    -yy curl leiningen build-essential zlib1g-dev RUN cd /opt && curl -sL https://github.com/.../graalvm.tar.gz \ | tar -xzvf - ADD project.clj . RUN lein deps ADD src src RUN lein uberjar RUN /opt/graalvm-ce-1.0.0-rc6/bin/native-image \ -H:EnableURLProtocols=http --static --no-server \ -cp target/webkv-0.0.0-standalone.jar webkv.main FROM scratch COPY --from=BASE /webkv.main / CMD ["/webkv.main"] 13 M B
  17. Time Memory JVM 1.10 s 100 MB Lumo 0.60 s

    130 MB Native 0.01 s 12 MB