Top Banner
PREDICTABLY FAST CLOJURE Zach Tellman @ztellman
82
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Predictably

PREDICTABLY FAST CLOJURE

Zach Tellman @ztellman

Page 2: Predictably

(nth s 2)

Page 3: Predictably

(nth s 2)

(.nth ^clojure.lang.Indexed s 2)

Page 4: Predictably

(nth s 2)

(java.lang.reflect.Array/get s 2)

(.nth ^clojure.lang.Indexed s 2)

Page 5: Predictably

(nth s 2)

(java.lang.reflect.Array/get s 2)

(.get ^java.util.RandomAccess s 2)

(.nth ^clojure.lang.Indexed s 2)

Page 6: Predictably

(nth s 2)

(java.lang.reflect.Array/get s 2)

(.get ^java.util.RandomAccess s 2)

(.nth ^clojure.lang.Indexed s 2)

(Character. (.charAt ^CharSequence s 2))

Page 7: Predictably

(nth s 2)

(java.lang.reflect.Array/get s 2)

(.get ^java.util.RandomAccess s 2)

(.nth ^clojure.lang.Indexed s 2)

(Character. (.charAt ^CharSequence s 2))

(first (next (next s)))

Page 8: Predictably

REFERENTIAL TRANSPARENCY

(+ 1 1)

2

~

Page 9: Predictably

REFERENTIAL TRANSPARENCY

(f x)

((memoize f) x)~

Page 10: Predictably

REFERENTIAL TRANSPARENCY

(map f s)

(doall (map f s))

~

Page 11: Predictably

REFERENTIAL TRANSPARENCY

(map f s)

(pmap f s)~

Page 12: Predictably

(map f s)

(map (fn [x] (Thread/sleep 1000) (f x)) s)

?

Page 13: Predictably

REFERENTIAL TRANSPARENCY

ASSUMES INFINITE RESOURCES

Page 14: Predictably

REFERENTIAL TRANSPARENCY

IS CONTEXTUAL

Page 15: Predictably

THE UTILITY OF ANY ABSTRACTION

IS CONTEXTUAL

Page 16: Predictably

CORRECTNESS IS

CONTEXTUAL

Page 17: Predictably

PREDICTABLY FAST CLOJURE

Zach Tellman @ztellman

Page 18: Predictably

LOOKING DEEP INTO THE ABYSS

Zach Tellman @ztellman

Page 19: Predictably

LOOKING DEEP INTO THE ABYSS,

USING CLOJURE

Zach Tellman @ztellman

Page 20: Predictably

WHAT TO DO WHEN YOUR ASSUMPTIONS

COME CRASHING DOWN AROUND YOU

Zach Tellman @ztellman

Page 21: Predictably
Page 22: Predictably
Page 23: Predictably

MY RESPONSIBILITIES

• 100k requests/sec, at peak

• intake of terabytes of compressed data per day

• eight hours of uninterrupted sleep

Page 24: Predictably

MY RESPONSIBILITIES

• looking deep into the abyss, using Clojure

• waiting for my assumptions to come crashing down around me

Page 25: Predictably

(count s)

Page 26: Predictably

(defn count {:inline (fn [x] `(. clojure.lang.RT (count ~x)))} [coll] (clojure.lang.RT/count coll))

Page 27: Predictably

public static int count(Object o) { if(o instanceof Counted) return ((Counted) o).count(); return countFrom(Util.ret1(o, o = null)); }

Page 28: Predictably

(let [v [1 2 3]] (quick-bench (count v)))

(let [v [1 2 3]] (quick-bench (.count ^Counted v)))

~10 ns

~5 ns

Page 29: Predictably

(defn matching-index [offset ks prefix] (let [cnt-ks (- (count ks) offset) cnt-prefix (Array/getLength prefix) cnt (Math/min cnt-ks cnt-prefix)] (loop [idx 0] (if (== cnt idx) (+ offset idx) (if (= (nth ks (+ offset idx)) (aget prefix idx)) (recur (inc idx)) (+ offset idx))))))

~200 ns

Page 30: Predictably

(defn matching-index [offset ks prefix] (let [cnt-ks (- (.count ^Counted ks) offset) cnt-prefix (Array/getLength prefix) cnt (Math/min cnt-ks cnt-prefix)] (loop [idx 0] (if (== cnt idx) (+ offset idx) (if (= (nth ks (+ offset idx)) (aget prefix idx)) (recur (inc idx)) (+ offset idx))))))

~100 ns

Page 31: Predictably

PERFORMANCE IS

ALMOST NEVER THE

SUM OF ITS PARTS

Page 32: Predictably

WHY IS IT SLOWER THAN JAVA?

THE ETERNAL QUESTION:

Page 33: Predictably

(+ 2 1) !

(+ (Long. 2) 1) !

(+ (/ 3 2) (/ 3 2)) !

(+ 2 (BigInteger. 1))

Page 34: Predictably

NO.DISASSEMBLE

Page 35: Predictably

> (use 'no.disassemble) nil !

> (defn log [x] (Math/log x)) #’log !

> (println (disassemble log))

Page 36: Predictably

public final class user$log extends clojure.lang.AFunction { public static {}; ... public user$log(); ... public java.lang.Object invoke(java.lang.Object x); ... }

Page 37: Predictably

public java.lang.Object invoke(java.lang.Object x); 0 aload_1 [x] 1 aconst_null 2 astore_1 [x] 3 checkcast java.lang.Number 6 invokestatic clojure.lang.RT.doubleCast(java.lang.Object) : double 9 invokestatic java.lang.Math.log(double) : double 12 invokestatic java.lang.Double.valueOf(double) : java.lang.Double 15 areturn

(fn [x] (Math/log x))

Page 38: Predictably

public java.lang.Object invoke(java.lang.Object x); 0 aload_1 [x] 1 aconst_null 2 astore_1 [x] 3 checkcast java.lang.Number 6 invokestatic clojure.lang.RT.doubleCast(java.lang.Object) : double 9 invokestatic java.lang.Math.log(double) : double 12 invokestatic java.lang.Double.valueOf(double) : java.lang.Double 15 areturn

(fn [x] (Math/log x))

Page 39: Predictably

public java.lang.Object invoke(java.lang.Object x); 0 aload_1 [x] 1 aconst_null 2 astore_1 [x] 3 checkcast java.lang.Number 6 invokestatic clojure.lang.RT.doubleCast(java.lang.Object) : double 9 invokestatic java.lang.Math.log(double) : double 12 invokestatic java.lang.Double.valueOf(double) : java.lang.Double 15 areturn

(fn [x] (Math/log x))

Page 40: Predictably

public java.lang.Object invoke(java.lang.Object x); 0 aload_1 [x] 1 aconst_null 2 astore_1 [x] 3 checkcast java.lang.Number 6 invokestatic clojure.lang.RT.doubleCast(java.lang.Object) : double 9 invokestatic java.lang.Math.log(double) : double 12 invokestatic java.lang.Double.valueOf(double) : java.lang.Double 15 areturn

(fn [x] (Math/log x))

Page 41: Predictably

public java.lang.Object invoke(java.lang.Object x); 0 aload_1 [x] 1 aconst_null 2 astore_1 [x] 3 checkcast java.lang.Number 6 invokestatic clojure.lang.RT.doubleCast(java.lang.Object) : double 9 invokestatic java.lang.Math.log(double) : double 12 invokestatic java.lang.Double.valueOf(double) : java.lang.Double 15 areturn

(fn [x] (Math/log x))

Page 42: Predictably

(fn ^double [^double x] (Math/log x))

public java.lang.Object invoke(java.lang.Object arg0); 0 aload_0 1 aload_1 2 checkcast java.lang.Number 5 invokestatic clojure.lang.RT.doubleCast(java.lang.Object) : double 8 invokeinterface clojure.lang.IFn$DD.invokePrim(double) : double 13 new java.lang.Double 16 dup_x2 17 dup_x2 18 pop 19 invokespecial java.lang.Double(double) 22 areturn

Page 43: Predictably

(fn ^double [^double x] (Math/log x))

public final double invokePrim(double x); 0 dload_1 [x] 1 invokestatic java.lang.Math.log(double) : double 4 dreturn

Page 44: Predictably

(fn [x y] (Math/min x y))

public java.lang.Object invoke(java.lang.Object x, java.lang.Object y); 0 ldc <String "java.lang.Math"> 2 invokestatic java.lang.Class.forName(java.lang.String) : java.lang.Class 5 ldc <String "min"> 7 iconst_2 8 anewarray java.lang.Object 11 dup 12 iconst_0 13 aload_1 [x] 14 aconst_null 15 astore_1 [x] 16 aastore 17 dup 18 iconst_1 19 aload_2 [y] 20 aconst_null 21 astore_2 22 aastore 23 invokestatic clojure.lang.Reflector.invokeStaticMethod … 26 areturn

Page 45: Predictably

(fn [x y] (+ x y))

public java.lang.Object invoke(java.lang.Object x, java.lang.Object y); 0 aload_1 [x] 1 aconst_null 2 astore_1 [x] 3 aload_2 [y] 4 aconst_null 5 astore_2 [y] 6 invokestatic clojure.lang.Numbers.add(java.lang.Object, java.lang.Object) … 9 areturn

Page 46: Predictably

static public Number add(Object x, Object y){ return ops(x).combine(ops(y)).add((Number)x, (Number)y); }   class LongOps { final public Number add(Number x, Number y){ return num(Numbers.add(x.longValue(),y.longValue())); } }   static public long add(long x, long y){ long ret = x + y; if ((ret ^ x) < 0 && (ret ^ y) < 0) return throwIntOverflow(); return ret; }

(fn [x y] (+ x y))

Page 47: Predictably

(fn [^long x ^long y] (+ x y))

public final java.lang.Object invokePrim(long x, long arg1); 0 lload_1 [x] 1 lload_3 2 invokestatic clojure.lang.Numbers.add(long, long) : long 5 invokestatic clojure.lang.Numbers.num(long) : java.lang.Number 8 areturn

Page 48: Predictably

(fn ^long [^long x ^long x] (+ x y))

public final long invokePrim(long x, long arg1); 0 lload_1 [x] 1 lload_3 2 invokestatic clojure.lang.Numbers.add(long, long) : long 5 lreturn  

Page 49: Predictably

(fn ^long [^long x ^long y] (unchecked-add x y))

public final long invokePrim(long x, long arg1); 0 lload_1 [x] 1 lload_3 2 ladd 5 lreturn  

Page 50: Predictably

(fn ^long [^long x ^long y] (unchecked-add x y))

AS FAST AS JAVA!

public final long invokePrim(long x, long arg1); 0 lload_1 [x] 1 lload_3 2 ladd 5 lreturn  

Page 51: Predictably

(fn [x y] (unchecked-add x y))

public java.lang.Object invoke(java.lang.Object x, java.lang.Object y); 0 aload_1 [x] 1 aconst_null 2 astore_1 [x] 3 aload_2 [y] 4 aconst_null 5 astore_2 [y] 6 invokestatic clojure.lang.Numbers.unchecked_add(java.lang.Object, … 9 areturn

Page 52: Predictably

AND SO…

Page 53: Predictably

public class Primitives { ! … ! public static long add(long a, long b) { return a + b; }   public static double add(double a, double b) { return a + b; } ! … !}

Page 54: Predictably

> (require '[primitive-math :as p]) nil !

> (macroexpand '(p/+ x y)) (. primitive_math.Primitives add x y)

Page 55: Predictably

(fn ^long [^long x ^long y] (p/+ x y))

public final long invokePrim(long x, long arg1); 0 lload_1 [x] 1 lload_3 2 invokestatic primitive_math.Primitives.add(long, long) : long 5 lreturn

Page 56: Predictably

> (set! *warn-on-reflection* true) true !> (fn [x y] (unchecked-add x y)) #<...> !> (fn [x y] (p/+ x y)) Reflection warning - call to add can't be resolved. #<...>

Page 57: Predictably

• does not supplant Clojure’s numerics

• invariants via feedback when they aren’t satisfied

• using Java isn’t cheating

Page 58: Predictably

IN THE BEGINNING, THERE WAS THE INPUT STREAM

Page 59: Predictably

AND SO…

Page 60: Predictably

(quick-bench (.getBytes "a reasonably long string"))

(let [f (memoize identity)] (quick-bench (f 1)))

~60 ns

~100 ns

Page 61: Predictably

(defn memoize [f] (let [mem (atom {})] (fn [& args] (if-let [e (find @mem args)] (val e) (let [ret (apply f args)] (swap! mem assoc args ret) ret)))))

Page 62: Predictably

{} or (array-map)

(hash-map)

• up to eight calls to .equiv()

A LOOKUP

• one call to .hasheq(), approx. one call to .equiv()

Page 63: Predictably

if(obj instanceof IPersistentVector) { Collection ma = (Collection) obj; if (ma.size() != v.count()) return false; for(Iterator i1 = ((List) v).iterator(), i2 = ma.iterator(); i1.hasNext();) { if (!Util.equiv(i1.next(), i2.next())) return false; } return true; }

Page 64: Predictably

AND SO…

Page 65: Predictably

(deftype Tuple0 []) !

(deftype Tuple1 [a]) !

(deftype Tuple2 [a b]) !

(deftype Tuple3 [a b c]) !

Page 66: Predictably

(if (instance? ~name x##) ~(if (zero? cardinality) true `(and ~@(map (fn [f] `(Util/equiv ~f (. ~other ~f))) fields))) …)

Page 67: Predictably

(let [v [1 2 3]] (quick-bench (nth v 0)))

(let [v [1 2 3]] (quick-bench (first v)))

~5 ns

~50 ns

Page 68: Predictably

(let [t (tuple 1 2 3)] (quick-bench (nth t 0)))

(let [t (tuple 1 2 3)] (quick-bench (first t)))

~5 ns

~5 ns

Page 69: Predictably

• if you can, do the hard work once

• if you don’t control the context, assume every bit matters

Page 70: Predictably

• if you can, do the hard work at compile-time

• the tools for library-sized macros are a bit lacking right now

Page 71: Predictably

I WORK WITH THIS GUY

(he is programming)

Page 72: Predictably

AND SO…

Page 73: Predictably

MUTABILITY!

Page 74: Predictably

(let-mutable [x 0] (dotimes [_ 100] (set! x (inc x))) x)

Page 75: Predictably

(let [x (LongContainer. 0)] (dotimes [_ 100] (.set x (inc (.get x)))) (.get x))

Page 76: Predictably

(let-mutable [x 1] (fn [] x))

(let [x (LongContainer. 1)] (let [x (.get x)] (fn [] x)))

Page 77: Predictably

• Clojure’s general solutions are fairly pessimistic, and assume few invariants

• try creating a more optimistic context

Page 78: Predictably

YOU DON’T HAVE TO DO WHAT HE DID

Page 79: Predictably

AND SO…

Page 80: Predictably

SOME FINAL THOUGHTS

• be curious about the tools you use

• if you’re going for a bounded solution, describe the boundaries fully

• if you’re going for a general solution, make sure it’s actually general

Page 81: Predictably
Page 82: Predictably

QUESTIONS?