Skip to content

Latest commit

 

History

History
518 lines (434 loc) · 19.2 KB

benchmarks.org

File metadata and controls

518 lines (434 loc) · 19.2 KB

require

Load benchmarking code

(load-file "multi-benchmarks.el")

multi-patterns

Load multi-patterns

(load-file "multi-patterns.el")

relative performance

How does pattern-matching compare to the existing semantically similar solutions. E.g. manual destructuring, seq-* functions, dash, etc. Idea is to have baseline perf we should strive for and to give users some idea about perf so they can decide if its good enough for them.

basic mu-case vs pcase

(mu-bench*
  :compare t
  (mu-bench "multi-case"
    (let* ((test-cases '(() (1) (1 2) (1 2 3) (1 2 3 4)))
           (len (length test-cases))
           (test-case (lambda () (nth (random len) test-cases))))
      (mu-case (funcall test-case)
        ((lst) 0)
        ((lst _) 1)
        ((lst _ _) 2)
        ((lst _ _ _) 3)
        (otherwise 'many))))
  (mu-bench "pcase"
    (let* ((test-cases '(() (1) (1 2) (1 2 3) (1 2 3 4)))
           (len (length test-cases))
           (test-case (lambda () (nth (random len) test-cases))))
      (pcase (funcall test-case)
        (`nil 0)
        (`(,_) 1)
        (`(,_ ,_) 2)
        (`(,_ ,_ ,_) 3)
        (otherwise 'many)))))
Formx slowerTotal timeGCsGC timeTimestamp
multi-case1.000.00201000.000000Sun Dec 30 11:34:41 2018
pcase1.060.00213400.000000Sun Dec 30 11:34:41 2018
Formx slowerTotal timeGCsGC timeTimestamp
pcase1.000.00132500.0Thu Dec 27 08:41:01 2018
multi-case1.560.00206900.0Thu Dec 27 08:41:01 2018

mu-let

mu-let is terribly slow and triggers GC

(mu-bench*
  :compare t
  (mu-bench "seq-let"
    (mapcar
     (lambda (arg)
       (seq-let (s1 &rest d) arg
         (seq-let (a &rest t2) s1
           (seq-let (b c) (seq-elt t2 0)
             (list a b c d)))))
     (list '((1 (2 3)) 4) [[1 [2 3]] 4])))

  (mu-bench "mu-let"
    (mapcar
     (lambda (arg)
       (mu-let (([[a &rest [[b c]]] &rest d] arg))
         (list a b c d)))
     (list '((1 (2 3)) 4) [[1 [2 3]] 4])))

  (mu-bench "mu-case"
    (mapcar
     (lambda (arg)
       (mu-case arg
         ([[a &rest [[b c]]] &rest d] (list a b c d))))
     (list '((1 (2 3)) 4) [[1 [2 3]] 4]))))
Formx slowerTotal timeGCsGC timeTimestamp
seq-let1.000.03203200.000000Sun Dec 30 11:45:50 2018
mu-case6.940.22218100.000000Sun Dec 30 11:45:50 2018
mu-let15.790.50591810.046986Sun Dec 30 11:45:50 2018
Formx slowerTotal timeGCsGC timeTimestamp
seq-let1.000.03104300.000000Thu Dec 27 08:44:16 2018
mu-case8.040.24953700.000000Thu Dec 27 08:44:16 2018
mu-let20.460.63514310.099905Thu Dec 27 08:44:16 2018
(mu-bench*
  :compare t
  ("seq-let" (let* ((a 1))
               (seq-let (b) '(2 3)
                 (seq-let (c d e) '(3 4)
                   (list a b c d e)))))

  ("mu-let" (mu-let ((a 1)
                     ([b] '(2 3))
                     ([c d e] '(3 4)))
              (list a b c d e))))
Formx slowerTotal timeGCsGC timeTimestamp
seq-let1.000.00522100.000000Thu Dec 27 08:45:38 2018
mu-let44.470.23219800.000000Thu Dec 27 08:45:38 2018

ht-pattern vs ht|alist-get

(mu-bench*
 :compare t
 (mu-bench "ht-get"
   (let* ((table (ht (:a 1) ('b 2) (:c 3) ('d 4)))
          (a (ht-get table :a))
          (b (ht-get table 'b))
          (c (ht-get table :c))
          (D (ht-get table 'd)))
     (list a b c D)))

 (mu-bench "ht-pattern"
   (mu-case (ht (:a 1) ('b 2) (:c 3) ('d 4))
     ;; NOTE this one is richer than the ht-get version cause
     ;; it tries different keys :a 'a "a" in order
     ((ht :a b 'c ('d D)) (list a b c D)))))
Formx slowerTotal timeGCsGC timeTimestamp
ht-pattern1.000.07915610.049057Sun Dec 30 11:47:27 2018
ht-get1.050.08279910.046360Sun Dec 30 11:47:27 2018
Formx slowerTotal timeGCsGC timeTimestamp
ht-pattern1.000.13053610.095632Thu Dec 27 08:46:47 2018
ht-get1.050.13743310.101954Thu Dec 27 08:46:47 2018
(mu-bench*
 :compare t
 (mu-bench "ht-get"
   (let* ((l (list (ht (:a 1)) '((:b . 2))))
          (a (ht-get (car l) :a))
          (b (alist-get :b (cadr l))))
     (list a b)))

 (mu-bench "ht-pattern"
   (mu-case (list (ht (:a 1)) '((:b . 2)))
     ((l (ht :a) (ht b)) (list a b)))))
Formx slowerTotal timeGCsGC timeTimestamp
ht-pattern1.000.06409610.046210Sun Dec 30 11:47:52 2018
ht-get1.120.07172010.047829Sun Dec 30 11:47:52 2018
Formx slowerTotal timeGCsGC timeTimestamp
ht-get1.000.11820710.096840Thu Dec 27 08:47:22 2018
ht-pattern1.070.12685310.102074Thu Dec 27 08:47:22 2018

mu-lambda vs lambda

(mu-bench*
 :compare t
 (mu-bench "lambda"
   (let ((fun (lambda () t)))
     (funcall fun)))

 (mu-bench "mu-lambda"
   (let ((fun (mu [] t)))
     (funcall fun))))
Formx slowerTotal timeGCsGC timeTimestamp
mu-lambda1.000.00061300.000000Sun Dec 30 11:48:29 2018
lambda1.490.00091500.000000Sun Dec 30 11:48:29 2018
Formx slowerTotal timeGCsGC timeTimestamp
lambda1.000.00049900.000000Thu Dec 27 08:48:50 2018
mu-lambda1.220.00060900.000000Thu Dec 27 08:48:50 2018

I don’t think this is apples to apples comparison, need a better bench.

(mu-bench*
 :compare t
 (mu-bench "lambda" (funcall (lambda (a b &rest args) (list* a b args)) 1 2 3 4))
 (mu-bench "mu-lambda" (funcall (mu [a b | args] (list* a b args)) 1 2 3 4)))
Formx slowerTotal timeGCsGC timeTimestamp
lambda1.000.00062700.000000Sun Dec 30 11:49:01 2018
mu-lambda151.090.09473400.000000Sun Dec 30 11:49:01 2018
Formx slowerTotal timeGCsGC timeTimestamp
lambda1.000.00071800.000000Thu Dec 27 08:49:00 2018
mu-lambda161.320.11582800.000000Thu Dec 27 08:49:00 2018
(mu-bench*
 :compare t
 (mu-bench "lambda"
   (let ((fun (lambda (&rest args)
                (pcase args
                  ((or `(,a ,b) `[,a ,b]) (list a b))
                  ((or `(,a ,b ,c) `[,a ,b ,c]) (list a b c))))))
     (list (funcall fun 1 2)
           (funcall fun 1 2 3))))

 (mu-bench "mu-lambda"
   (let ((fun (mu _
                ([a b] (list a b))
                ([a b c] (list a b c)))))
     (list (funcall fun 1 2)
           (funcall fun 1 2 3)))))
Formx slowerTotal timeGCsGC timeTimestamp
mu-lambda1.000.00320600.000000Sun Dec 30 11:49:27 2018
lambda1.190.00380700.000000Sun Dec 30 11:49:27 2018
Formx slowerTotal timeGCsGC timeTimestamp
mu-lambda1.000.00344300.000000Thu Dec 27 08:50:05 2018
lambda1.010.00348500.000000Thu Dec 27 08:50:05 2018

absolute performance

Benchmarks to track perf improvements and spot regressions. Ideally we should cover a wide variaty of patterns in every API bell-n-whistle we expose.

basic patterns

(mu-bench
  (mapcar
   (lambda (arg)
     (mu-case arg
       ((lst) 0)
       ((lst _) 1)
       ((lst _ _) 2)
       ((lst _ _ _) 3)
       (otherwise 'many)))
   '(() (1) (1 2) (1 2 3) (1 2 3 4))))
Total timeGCsGC timeTimestamp
0.00703300.0Thu Dec 27 08:50:55 2018

deeply nested []-pattern

(mu-bench
  (mapcar
   (lambda (arg)
     (mu-case arg
       ([[a &rest [[b c]]] &rest d] (list a b c d))))
   (list '((1 (2 3)) 4) [[1 [2 3]] 4])))
Total timeGCsGC timeTimestamp
0.25585500.0Thu Dec 27 08:51:25 2018

multi-methods

Load multi-methods

(load-file "multi-methods.el")

As a running example we’ll be using the following global hierachy:

:dot  ->  :square  ->  :rect   *-> :shape
          |                    ^
          |                    |
          *->  :parallelogram  *-> :multiangle

captured in the following function:

(defsubst mu--bench-reset-hierachy ()
  ;; reset global hierarchy
  (setq mu-global-hierarchy (make-mu-hierarchy))
  ;; install new relations
  (mu-rel :dot isa :square)
  (mu-rel :rect isa :shape)
  (mu-rel :square isa :rect)
  (mu-rel :square isa :parallelogram)
  (mu-rel :parallelogram isa :multiangle)
  (mu-rel :parallelogram isa :shape))

relative performance

TODO I think I’m testing the interpreted code here. I need the “dispatch” be byte-compiled and running byte-code. Both the user-install foo-dispatcher and my mu-method lookup. I guess this means I want to (byte-compile #’foo-test)?

multimethods vs generic dispatch

(mu-bench/context

    ;; benchmark
    (mu-bench*/let ((s0 (make-foo-struct-0))
                    (s1 (make-foo-struct-1))
                    (s2 (make-foo-struct-2))
                    (s3 (make-foo-struct-3))
                    (s4 (make-foo-struct-4))
                    (s5 (make-foo-struct-5))
                    (s6 (make-foo-struct-6)))
      :times 1000
      :compare t
      (mu-bench "generic"
        (foo-struct-test s0)
        (foo-struct-test s1)
        (foo-struct-test s2)
        (foo-struct-test s3)
        (foo-struct-test s4)
        (foo-struct-test s5)
        (foo-struct-test s6))

      (mu-bench "multi"
        (foo-test s0)
        (foo-test s1)
        (foo-test s2)
        (foo-test s3)
        (foo-test s4)
        (foo-test s5)
        (foo-test s6)))

  ;; context

  (cl-defstruct foo-struct-0)
  (cl-defstruct foo-struct-1)
  (cl-defstruct foo-struct-2)
  (cl-defstruct foo-struct-3)
  (cl-defstruct foo-struct-4)
  (cl-defstruct foo-struct-5)
  (cl-defstruct foo-struct-6)

  ;; multi
  (mu-defmulti foo-test #'type-of)
  (mu-defmethod foo-test (x) :when 'foo-struct-1 1)
  (mu-defmethod foo-test (x) :when 'foo-struct-2 2)
  (mu-defmethod foo-test (x) :when 'foo-struct-3 3)
  (mu-defmethod foo-test (x) :when 'foo-struct-4 4)
  (mu-defmethod foo-test (x) :when 'foo-struct-5 5)
  (mu-defmethod foo-test (x) :when 'foo-struct-6 6)
  (mu-defmethod foo-test (x) :when :default 0)

  ;; generic
  (cl-defgeneric foo-struct-test (s) 0)
  (cl-defmethod foo-struct-test ((s foo-struct-1)) 1)
  (cl-defmethod foo-struct-test ((s foo-struct-2)) 2)
  (cl-defmethod foo-struct-test ((s foo-struct-3)) 3)
  (cl-defmethod foo-struct-test ((s foo-struct-4)) 4)
  (cl-defmethod foo-struct-test ((s foo-struct-5)) 5)
  (cl-defmethod foo-struct-test ((s foo-struct-6)) 6))
Formx slowerTotal timeGCsGC timeTimestamp
generic1.000.00168000.000000Sun Dec 30 11:12:15 2018
multi244.410.41061320.210106Sun Dec 30 11:12:15 2018
Formx slowerTotal timeGCsGC timeTimestamp
:generic1.000.00165200.000000Thu Dec 27 16:36:18 2018
:mu-method237.570.39246020.194683Thu Dec 27 16:36:18 2018

absolute performance

basic isa lookup

(mu--bench-reset-hierachy)

(mu-bench*
  :times 1000
  :compare t
  (mu-bench "equal"    (mu-isa? 42 42))
  (mu-bench "direct"   (mu-isa? :rect   :shape))
  (mu-bench "indirect" (mu-isa? :square :shape))
  (mu-bench "seq1"     (mu-isa? [:square :rect]  [:rect :shape]))
  (mu-bench "seq2"     (mu-isa? [:square :shape] [:rect :shape]))
  (mu-bench "nested"   (mu-isa? [[:dot :parallelogram] :square] [[:shape :multiangle] :rect])))
Formx slowerTotal timeGCsGC timeTimestamp
equal1.000.00060300.000000Sun Dec 30 11:13:09 2018
indirect6.820.00411400.000000Sun Dec 30 11:13:09 2018
direct6.890.00415400.000000Sun Dec 30 11:13:09 2018
seq212.860.00775200.000000Sun Dec 30 11:13:09 2018
seq118.040.01087600.000000Sun Dec 30 11:13:09 2018
nested28.420.01713900.000000Sun Dec 30 11:13:09 2018
Formx slowerTotal timeGCsGC timeTimestamp
:equal1.000.00047700.000000Thu Dec 27 11:45:38 2018
:direct7.610.00363200.000000Thu Dec 27 11:45:38 2018
:indirect8.750.00417500.000000Thu Dec 27 11:45:38 2018
:seq215.370.00733200.000000Thu Dec 27 11:45:38 2018
:seq122.340.01065700.000000Thu Dec 27 11:45:38 2018
:nested42.180.02012200.000000Thu Dec 27 11:45:38 2018

dispatch on equality - no deep isa hierarchy traversal

(mu-bench/context

    ;; benchmark
    (mu-bench*
      :times 1000
      :compare t
      (mu-bench "a" (foo :a))
      (mu-bench "b" (foo :b))
      (mu-bench "a-a" (foo :a :a))
      (mu-bench "b-b" (foo :a :b)))

  ;; context
  (mu-defmulti foo (lambda (&rest args) (apply #'vector args)))
  (mu-defmethod foo (&rest x) :when [:a] :a)
  (mu-defmethod foo (&rest x) :when [:b] :b)
  (mu-defmethod foo (&rest x) :when [:a :a] :a)
  (mu-defmethod foo (&rest x) :when [:a :b] :b))
Formx slowerTotal timeGCsGC timeTimestamp
a1.000.01709800.000000Sun Dec 30 11:14:02 2018
b1.100.01874300.000000Sun Dec 30 11:14:02 2018
b-b1.100.01884800.000000Sun Dec 30 11:14:02 2018
a-a1.120.01909800.000000Sun Dec 30 11:14:02 2018
Formx slowerTotal timeGCsGC timeTimestamp
:b1.000.01763800.000000Thu Dec 27 16:38:10 2018
:a1.020.01796600.000000Thu Dec 27 16:38:10 2018
:b-b1.040.01834800.000000Thu Dec 27 16:38:10 2018
:a-a1.050.01857600.000000Thu Dec 27 16:38:10 2018

Hierarchies are rarely used, so most cache benefit comes from avoiding to isa with every registered method (0-20 in this example). The more methods we register the more overhead choosing the one becomes. Keep in mind: cache may overflow if most of the time you go to the :default method, so need to think of cache excision eventually.

(mu-defmulti foo #'identity)
(mu-defmethod foo (&rest args) :when 0  0)
(mu-defmethod foo (&rest args) :when 1  1)
(mu-defmethod foo (&rest args) :when 2  2)
(mu-defmethod foo (&rest args) :when 3  3)
(mu-defmethod foo (&rest args) :when 4  4)
(mu-defmethod foo (&rest args) :when 5  5)
(mu-defmethod foo (&rest args) :when 6  6)
(mu-defmethod foo (&rest args) :when 7  7)
(mu-defmethod foo (&rest args) :when 8  8)
(mu-defmethod foo (&rest args) :when 9  9)
(mu-defmethod foo (&rest args) :when 10 10)
(mu-defmethod foo (&rest args) :when 11 11)
(mu-defmethod foo (&rest args) :when 12 12)
(mu-defmethod foo (&rest args) :when 13 13)
(mu-defmethod foo (&rest args) :when 14 14)
(mu-defmethod foo (&rest args) :when 15 15)
(mu-defmethod foo (&rest args) :when 16 16)
(mu-defmethod foo (&rest args) :when 17 17)
(mu-defmethod foo (&rest args) :when 18 18)
(mu-defmethod foo (&rest args) :when 19 19)
(mu-defmethod foo (&rest args) :when 20 20)

(mu-bench/let ((random20 (byte-compile (lambda () (random 21)))))
  (foo (funcall random20)))
FormTotal timeGCsGC timeTimestamp
_1.44204570.7474569999999403Sun Dec 30 11:30:35 2018