Slide 1

Slide 1 text

Native Clojure
 with GraalVM Jan Stępień @janstepien 14.09.2018 // ClojuTRE

Slide 2

Slide 2 text

graalvm.org

Slide 3

Slide 3 text

Native Clojure

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

(ns pprint.main (:require [clojure.pprint :refer [pprint]]) (:gen-class)) (defn -main [& path] (-> (read *in*) (get-in (mapv read-string path)) pprint))

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

$ echo {:a [5 3]} \ | time lumo pprint.cljs :a 1 3 0.56 real 128MB maximum resident set size

Slide 9

Slide 9 text

$ 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

Slide 10

Slide 10 text

$ echo {:a [5 3]} \ | time ./pprint :a 1 3 0.01 real 12MB maximum resident set size

Slide 11

Slide 11 text

Time Memory JVM 1.10 s 100 MB Lumo 0.60 s 130 MB Native 0.01 s 12 MB

Slide 12

Slide 12 text

Command Line Tools

Slide 13

Slide 13 text

(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

Slide 14

Slide 14 text

$ 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

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

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=>

Slide 17

Slide 17 text

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]))

Slide 18

Slide 18 text

(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))))

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

$ cat node_modules/*/package.json \ | json2edn | spec-provider :node/package | zprint

Slide 21

Slide 21 text

$ 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

Slide 22

Slide 22 text

Web Applications

Slide 23

Slide 23 text

$ 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

Slide 24

Slide 24 text

(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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

How Is That Even Possible

Slide 27

Slide 27 text

Time Memory JVM 1.10 s 100 MB Lumo 0.60 s 130 MB Native 0.01 s 12 MB

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

static { ... } initialiser Static class

Slide 30

Slide 30 text

https:/ /blog.ndk.io/clojure-compilation

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

Limitations

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

;; Reflection (set! *warn-on-reflection* true)

Slide 35

Slide 35 text

;; Dynamic class loading (require 'example.core :reload)

Slide 36

Slide 36 text

github.com/taylorwood/lein-native-image github.com/taylorwood/clj.native-image

Slide 37

Slide 37 text

Jan Stępień @janstepien janstepien.com Native Clojure
 with GraalVM 14.09.2018 // ClojuTRE

Slide 38

Slide 38 text

JEP 243 JVM Compiler Interface JD K 9

Slide 39

Slide 39 text

Truffle TruffleRuby GraalVM JVMCI Graal JVM