Skip to content

Speed Comparisons

okamsn edited this page Nov 28, 2022 · 5 revisions

NOTE: As Loopy develops, these examples and results might become out of date. For the most up-to-date information, refer to this package’s documentation, which is installed with the package as the Info file loopy.

Additionally, this information can/should probably be expanded to compare to several other approaches.


This file contains benchmarks comparing cl-loop, loopy, some of the built-in C functions like mapcar and mapcan, some of CL Lib’s functions like cl-mapcar and cl-mapcan, some of Dash’s functions like -map and -mapcat, and some Seq functions like seq-reduce.

In general, for simple cases, they can be sorted from fastest to slowest as

  1. C functions like mapcar and apply
  2. loopy and cl-loop (as far as these benchmarks cover)
  3. -map (but not -count)
  4. Seq’s functions

The C functions are fastest because they are in C, and Dash’s functions are should be faster because they try to be pure and side-effect free (which allows for better optimizations). The Dash results were better when testing on Emacs 27, but both loopy and cl-loop closed the gap (and in several cases passed) when testing with Emacs 28 with native compilation.

For straightforward cases, loopy and cl-loop are overkill. They allow a lot more flexibility with branch code in loops, but they can’t be the C functions in terms of speed.

One important (for these tests) difference between loopy and cl-loop, is that, in loopy, all iteration variables are bound to nil until their respective iteration command is run. This consistency is deemed useful for predictability, but can require extra variables and assignments, making it slower and not directly comparable to what cl-loop is doing. Regardless, loopy is usually about the same speed if not faster.

Table of Contents

Benchmark Process

loopy tries to follow the same logic as cl-loop, but in being more flexible in some cases, it cannot always implement concepts as efficiently as cl-loop. However, for general cases, they should be more or less the same.

These comparisons are made using macros listed in user Alphapapa’s useful Emacs Package Dev Handbook. For completeness, they are copied below.

(require 'cl-lib)

;;;###autoload
(cl-defmacro bench (&optional (times 100000) &rest body)
  "Call `benchmark-run-compiled' on BODY with TIMES iterations, returning list suitable for Org source block evaluation.
Garbage is collected before calling `benchmark-run-compiled' to
avoid counting existing garbage which needs collection."
  (declare (indent defun))
  `(progn
     (garbage-collect)
     (list '("Total runtime" "# of GCs" "Total GC runtime")
           'hline
           (benchmark-run-compiled ,times
             (progn
               ,@body)))))

;;;###autoload
(cl-defmacro bench-multi (&key (times 1) forms ensure-equal raw)
  "Return Org table as a list with benchmark results for FORMS.
Runs FORMS with `benchmark-run-compiled' for TIMES iterations.

When ENSURE-EQUAL is non-nil, the results of FORMS are compared,
and an error is raised if they aren't `equal'. If the results are
sequences, the difference between them is shown with
`seq-difference'.

When RAW is non-nil, the raw results from
`benchmark-run-compiled' are returned instead of an Org table
list.

If the first element of a form is a string, it's used as the
form's description in the bench-multi-results; otherwise, forms
are numbered from 0.

Before each form is run, `garbage-collect' is called."
  ;; MAYBE: Since `bench-multi-lexical' byte-compiles the file, I'm not sure if
  ;; `benchmark-run-compiled' is necessary over `benchmark-run', or if it matters.
  (declare (indent defun))
  (let*((keys (gensym "keys"))
        (result-times (gensym "result-times"))
        (header '(("Form" "x faster than next" "Total runtime" "# of GCs" "Total GC runtime")
                  hline))
        ;; Copy forms so that a subsequent call of the macro will get the original forms.
        (forms (cl-copy-list forms))
        (descriptions (cl-loop for form in forms
                               for i from 0
                               collect (if (stringp (car form))
                                           (prog1 (car form)
                                             (setf (nth i forms) (cadr (nth i forms))))
                                         i))))
    `(unwind-protect
         (progn
           (defvar bench-multi-results nil)
           (let* ((bench-multi-results (make-hash-table))
                  (,result-times (sort (list ,@(cl-loop for form in forms
                                                        for i from 0
                                                        for description = (nth i descriptions)
                                                        collect `(progn
                                                                   (garbage-collect)
                                                                   (cons ,description
                                                                         (benchmark-run-compiled ,times
                                                                           ,(if ensure-equal
                                                                                `(puthash ,description ,form bench-multi-results)
                                                                              form))))))
                                       (lambda (a b)
                                         (< (cl-second a) (cl-second b))))))
             ,(when ensure-equal
                `(cl-loop with ,keys = (hash-table-keys bench-multi-results)
                          for i from 0 to (- (length ,keys) 2)
                          unless (equal (gethash (nth i ,keys) bench-multi-results)
                                        (gethash (nth (1+ i) ,keys) bench-multi-results))
                          do (if (sequencep (gethash (car (hash-table-keys bench-multi-results)) bench-multi-results))
                                 (let* ((k1) (k2)
                                        ;; If the difference in one order is nil, try in other order.
                                        (difference (or (setq k1 (nth i ,keys)
                                                              k2 (nth (1+ i) ,keys)
                                                              difference (seq-difference (gethash k1 bench-multi-results)
                                                                                         (gethash k2 bench-multi-results)))
                                                        (setq k1 (nth (1+ i) ,keys)
                                                              k2 (nth i ,keys)
                                                              difference (seq-difference (gethash k1 bench-multi-results)
                                                                                         (gethash k2 bench-multi-results))))))
                                   (user-error "Forms' bench-multi-results not equal: difference (%s - %s): %S"
                                               k1 k2 difference))
                               ;; Not a sequence
                               (user-error "Forms' bench-multi-results not equal: %s:%S %s:%S"
                                           (nth i ,keys) (nth (1+ i) ,keys)
                                           (gethash (nth i ,keys) bench-multi-results)
                                           (gethash (nth (1+ i) ,keys) bench-multi-results)))))
             ;; Add factors to times and return table
             (if ,raw
                 ,result-times
               (append ',header
                       (bench-multi-process-results ,result-times)))))
       (unintern 'bench-multi-results nil))))

(defun bench-multi-process-results (results)
  "Return sorted RESULTS with factors added."
  ;; (setq results (sort results (-on #'< #'cl-second)))
  (setq results (cl-sort results #'< :key #'cl-second))
  (cl-loop with length = (length results)
           for i from 0 to (1- length)
           for description = (car (nth i results))
           for factor = (if (< i (1- length))
                            (format "%.2f" (/ (cl-second (nth (1+ i) results))
                                              (cl-second (nth i results))))
                          "slowest")
           collect (append (list description factor)
                           (list (format "%.6f" (cl-second (nth i results)))
                                 (cl-third (nth i results))
                                 (if (> (cl-fourth (nth i results)) 0)
                                     (format "%.6f" (cl-fourth (nth i results)))
                                   0)))))

Benchmark Results

For Clauses

The following clauses don’t have exact equivalents in Loopy, but most can be mimicked by using Emacs’s built-in functions and the list loop command. See the section For Clauses under Translating … for the relevant function.

  • for VAR being the symbols
  • for VAR being the hash-keys
  • for VAR being the hash-values
  • for VAR being the key-codes
  • for VAR being the key-bindings
  • for VAR being the key-bindings
  • for VAR being the key-seqs
  • for VAR being the overlays
  • for VAR being the intervals
  • for VAR being the frames
  • for VAR being the windows
  • for VAR being the buffers
  • for VAR from EXPR1 to EXPR2 by EXPR3: When using the keywords (“CL style”), these expand to basically the same code. When using the positional arguments with variables (instead of printed numbers), loopy has to check the direction at run time, which results in slower code via funcall.

    It seems faster to be able to use the numbers directly in the code instead of creating variables. In the table below, it is almost 69% slower to use variables.

    Interestingly, even when running the same number of steps, it seems that stepping by 1 is faster than by other increments.

    ;; one thousand steps tested ten thousand times
    (bench-multi
      :times 10000
      :forms (("cl-loop" (cl-loop for i from 1 to 1000))
              ("loopy cl-style" (loopy (numbers i :from 1 :to 1000)))
              ("loopy python-style" (loopy (numbers i 1 1000)))
    
              ("cl-loop by 1" (cl-loop for i from 1 to 1000 by 1))
              ("loopy cl-style by 1" (loopy (numbers i :from 1 :to 1000 :by 1)))
              ("loopy python-style by 1" (loopy (numbers i 1 1000 1)))
    
              ;; Tests other increment but same number of steps.
              ("cl-loop by 3" (cl-loop for i from 3 to 3000 by 3))
              ("loopy cl-style by 3" (loopy (numbers i :from 3 :to 3000 :by 3)))
              ("loopy python-style by 3" (loopy (numbers i 3 3000 3)))
    
              ;; With variables.
              ("cl-loop with vars" (cl-loop with start = 1 and end = 1000 and step = 1
                                            for i from start to end by step))
              ("loopy cl-style with vars" (loopy (with (start 1) (end 1000) (step 1))
                                                 (numbers i :from start :to end :by step)))
              ("loopy python-style with vars" (loopy (with (start 1) (end 1000) (step 1))
                                                     (numbers i start end step)))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    loopy python-style 1.00 0.262468 0 0
    cl-loop by 1 1.00 0.262500 0 0
    loopy cl-style 1.00 0.262936 0 0
    loopy cl-style by 1 1.00 0.263450 0 0
    loopy python-style by 1 1.00 0.263473 0 0
    cl-loop 1.49 0.263483 0 0
    loopy python-style by 3 1.00 0.392483 0 0
    loopy cl-style by 3 1.00 0.392819 0 0
    cl-loop by 3 1.10 0.393508 0 0
    cl-loop with vars 1.00 0.431381 0 0
    loopy cl-style with vars 1.59 0.432137 0 0
    loopy python-style with vars slowest 0.688223 0 0
  • for VAR in LIST by FUNCTION:
    (let ((l (number-sequence 1 100)))
      (bench-multi
        :times 10000
        :forms (("cl-loop" (cl-loop for i in l))
                ("loopy" (loopy (list i l)))
                ;; These shouldn’t be different.
                ("cl-loop with function" (cl-loop for i in l by #'cdr))
                ("loopy with function" (loopy (list i l :by #'cdr))))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    loopy with function 1.00 0.025758 0 0
    cl-loop 1.01 0.025881 0 0
    loopy 1.02 0.026019 0 0
    cl-loop with function slowest 0.026655 0 0
  • for VAR on LIST by FUNCTION: This has slightly different behavior.

    In cl-loop, the below i is bound to the value of l immediately, before the loop body is run.

    (let ((l (number-sequence 1 100)))
      (bench-multi
        :times 10000
        :forms (("cl-loop" (cl-loop for i on l))
                ("loopy" (loopy (cons i l)))
                ;; These shouldn’t be different.
                ("cl-loop with function" (cl-loop for i on l by #'cdr))
                ("loopy with function" (loopy (cons i l :by #'cdr))))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    cl-loop 1.01 0.017197 0 0
    cl-loop with function 1.40 0.017314 0 0
    loopy 1.01 0.024299 0 0
    loopy with function slowest 0.024485 0 0
  • for VAR in-ref LIST by FUNCTION:
    (let ((l (number-sequence 1 100)))
      (bench-multi
        :times 10000
        :forms (("cl-loop" (cl-loop for i in-ref l))
                ("loopy" (loopy (list-ref i l)))
                ;; These shouldn’t be different.
                ("cl-loop with function" (cl-loop for i in-ref l by #'cdr))
                ("loopy with function" (loopy (list-ref i l :by #'cdr))))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    loopy 1.00 0.017130 0 0
    cl-loop with function 1.00 0.017137 0 0
    loopy with function 1.01 0.017157 0 0
    cl-loop slowest 0.017380 0 0
  • for VAR across ARRAY:
    (let ((a (make-vector 1000 1)))
      (bench-multi
        :times 10000
        :forms (("cl-loop" (cl-loop for i across a))
                ("loopy" (loopy (array i a))))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    loopy 1.14 0.433241 0 0
    cl-loop slowest 0.495099 0 0
  • for VAR across-ref ARRAY:
    (let ((a (make-vector 1000 1)))
      (bench-multi
        :times 10000
        :forms (("cl-loop" (cl-loop for i across-ref a))
                ("loopy" (loopy (array-ref i a))))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    loopy 1.28 0.276715 0 0
    cl-loop slowest 0.354138 0 0
  • for VAR being the elements of SEQUENCE:
    (let ((a (make-vector 1000 1))
          (l (make-list 1000 1)))
      (bench-multi
        :times 10000
        :forms (("cl-loop array" (cl-loop for i being the elements of a))
                ("loopy array" (loopy (seq i a)))
                ("cl-loop list" (cl-loop for i being the elements of l))
                ("loopy list" (loopy (seq i l))))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    loopy list 1.00 0.639563 0 0
    cl-loop list 1.25 0.640058 0 0
    loopy array 1.04 0.797108 0 0
    cl-loop array slowest 0.825234 0 0
  • for VAR being the elements of-ref SEQUENCE:
    (let ((a (make-vector 1000 1))
          (l (make-list 1000 1)))
      (bench-multi
        :times 10000
        :forms (("cl-loop array" (cl-loop for i being the elements of-ref a))
                ("loopy array" (loopy (seq-ref i a)))
                ("cl-loop list" (cl-loop for i being the elements of-ref l))
                ("loopy list" (loopy (seq-ref i l))))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    cl-loop array 1.00 0.278885 0 0
    loopy array 1.04 0.279238 0 0
    loopy list 1.00 0.289703 0 0
    cl-loop list slowest 0.290474 0 0
  • for VAR = EXPR1 then EXPR2: Differences are due to the implementation of repeat vs cycle.
    (bench-multi
      :times 10000
      :forms (("cl-loop" (cl-loop for i = 0 then (1+ i)
                                  repeat 100))
              ("loopy" (loopy (set i 0 (1+ i))
                              (cycle 100)))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    cl-loop 1.01 0.048465 0 0
    loopy slowest 0.048762 0 0

Iteration Clauses

The clauses always, never, thereis, and iter-by are currently not implemented in Loopy, but their usages can be matched with existing (though currently less convenient) loop commands.

  • repeat:
    (bench-multi
      :times 1000
      :forms (("cl-loop" (cl-loop repeat 100))   ; Uses `>=' and `1-'.
              ("loopy" (loopy (cycle 100))))) ; Uses `<' and `1+'.
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    loopy 1.04 0.002680 0 0
    cl-loop slowest 0.002779 0 0
  • while: In loopy, the while command is short for (when COND (leave)) and is evaluated in place. In cl-loop, the while clause adds a condition to the internal while loop. This is probably the cause of any difference in speed, though in practice it would only be a very small part of the loop’s execution time.
    (bench-multi
      :times 10000
      :forms (("cl-loop v1" (cl-loop while t repeat 100))
              ("loopy v1" (loopy (while t) (repeat 100)))
              ;; Try opposite order.
              ("cl-loop v2" (cl-loop repeat 100 while t))
              ("loopy v2" (loopy (repeat 100) (while t)))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    loopy v1 1.00 0.027067 0 0
    loopy v2 1.02 0.027115 0 0
    cl-loop v1 1.03 0.027617 0 0
    cl-loop v2 slowest 0.028419 0 0
  • until: See the details of while above for a possible explanation of any differences.
    (bench-multi
      :times 10000
      :forms (("cl-loop v1" (cl-loop until nil repeat 100))
              ("loopy v1" (loopy (until nil) (repeat 100)))
              ;; Try opposite order.
              ("cl-loop v2" (cl-loop repeat 100 until nil))
              ("loopy v2" (loopy (repeat 100) (until nil)))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    loopy v1 1.00 0.026887 0 0
    loopy v2 1.02 0.027005 0 0
    cl-loop v2 1.00 0.027451 0 0
    cl-loop v1 slowest 0.027484 0 0

Accumulation Clauses

cl-loop uses special considerations for append, collect, and nconc using combinations append, nconc, push, nreverse, and reverrse depending on how variables are being declared and used. loopy is currently much simpler, such as using push and nreverse for collect when no explicitly variable is given (as in (collect some-val)) and using append otherwise.

You should note that naming the variables into which you append, collect, or nconc can be much slower, since the macros can no longer use tricks with reverse and nreverse. If they did, you might try to access the accumulation variable before it was reversed. More control can be used via the accum-opt special macro argument.

  • collect:
    (let ((l (number-sequence 1 1000)))
      (bench-multi
        :times 10000
        :forms (("cl-loop" (cl-loop for i in l
                                    collect (* (1+ i) 2)))
                ("loopy" (loopy (list i l)
                                (collect (* (1+ i) 2))))
                ("mapcar" (mapcar (lambda (i) (* (1+ i) 2)) l))
                ("cl-mapcar" (cl-mapcar (lambda (i) (* (1+ i) 2)) l))
                ("dash" (-map (lambda (i) (* (1+ i) 2)) l))
                ("anaphoric dash" (--map (* (1+ it) 2) l))
                ("dolist" (let ((result))
                            (dolist (i l (nreverse result))
                              (push (* (1+ i) 2) result)))))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    dolist 1.02 0.651358 0 0
    loopy 1.01 0.663928 0 0
    cl-loop 1.44 0.667420 0 0
    dash 1.00 0.963277 0 0
    cl-mapcar 1.00 0.963896 0 0
    mapcar 1.04 0.964183 0 0
    anaphoric dash slowest 1.005722 0 0
  • append and nconc:
    (let ((l (number-sequence 1 1000)))
      (bench-multi
        :times 10000
        :forms (("cl-loop append" (cl-loop for i in l
                                           append (list (* (1+ i) 2)
                                                        (* (1+ i) 3))))
                ("cl-loop nconc" (cl-loop for i in l
                                          nconc (list (* (1+ i) 2)
                                                      (* (1+ i) 3))))
                ("loopy append" (loopy (list i l)
                                       (append (list (* (1+ i) 2)
                                                     (* (1+ i) 3)))))
                ("loopy nconc" (loopy (list i l)
                                      (nconc (list (* (1+ i) 2)
                                                   (* (1+ i) 3)))))
                ("mapcan" (mapcan (lambda (i) (list (* (1+ i) 2)
                                                    (* (1+ i) 3)))
                                  l)) ; C function.
                ("cl-mapcan" (cl-mapcan (lambda (i) (list (* (1+ i) 2)
                                                          (* (1+ i) 3)))
                                        l))
                ("dash" (-mapcat (lambda (i) (list (* (1+ i) 2)
                                                   (* (1+ i) 3)))
                                 l))
                ("anaphoric dash" (--mapcat (list (list (* (1+ it) 2)
                                                        (* (1+ it) 3)))
                                            l)))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    cl-loop nconc 1.01 1.431169 0 0
    loopy nconc 1.05 1.444169 0 0
    mapcan 1.00 1.518999 0 0
    cl-mapcan 1.16 1.521155 0 0
    cl-loop append 1.10 1.768662 0 0
    anaphoric dash 1.03 1.941612 0 0
    loopy append 1.16 1.999173 0 0
    dash slowest 2.320142 0 0
  • concat: This difference might be because cl-loop uses cl-callf, while loopy currently uses concat and setq in all cases.

    seq-mapcat and mapconcat might be much faster because they only call concat once after mapping across all values. On the other hand, loopy and cl-loop currently call concat for each iteration. On the other hand, cl-loop calls concat for each iteration, as does loopy if the accumulation variable is explicitly named.

    (let ((l (make-list 100 (make-string 100 ?a))))
      (bench-multi
        :times 1000
        :forms (("cl-loop" (cl-loop for i in l concat (capitalize i)))
                ;; Here, `loopy' calls `concat' once:
                ("loopy implicit" (loopy (list i l) (concat (capitalize i))))
                ;; Here, `loopy', calls `concat' each iteration:
                ("loopy explicit" (loopy (list i l) (concat my-str (capitalize i))))
                ("mapconcat" (mapconcat #'capitalize l "")) ; C function.
                ("concat" (apply #'concat (mapcar #'capitalize l)))
                ;; `cl-concatenate' is alias of `seq-concatenate'.
                ("cl-concatenate" (apply #'cl-concatenate 'string
                                         (mapcar #'capitalize l)))
                ("seq-macpcat" (seq-mapcat #'capitalize l 'string)))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    concat 1.01 0.228874 0 0
    cl-concatenate 1.00 0.231995 0 0
    seq-macpcat 1.03 0.232622 0 0
    mapconcat 1.02 0.238497 0 0
    loopy implicit 3.17 0.244310 0 0
    loopy explicit 1.01 0.774461 0 0
    cl-loop slowest 0.783207 0 0
  • vconcat: As with concat above, cl-loop calls concat for each iteration, while other functions create a list of sequences and then call concat once.
    (let* ((l (make-list 100 (number-sequence 1 1000))))
      (bench-multi
        :times 1000
        :forms (("cl-loop" (cl-loop for i in l
                                    vconcat (cdr i)))
                ("loopy implicit" (loopy (list i l) (vconcat (cdr i))))
                ("loopy explicit" (loopy (list i l) (vconcat my-vect (cdr i))))
                ("vconcat" (apply #'vconcat (mapcar #'cdr l)))
                ("cl-concatenate" (apply #'cl-concatenate 'vector
                                         (mapcar #'cdr l)))
                ;; This concatenates once after the `cdr' of each list is gotten.
                ("seq-macpcat" (seq-mapcat #'cdr l 'vector)))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    seq-macpcat 1.00 0.559997 0 0
    cl-concatenate 1.00 0.560870 0 0
    loopy implicit 1.01 0.561155 0 0
    vconcat 52.76 0.564348 0 0
    cl-loop 1.00 29.774690 37 5.212632
    loopy explicit slowest 29.788736 37 5.213497
  • count: Not sure why -count is so much faster than the others.
    (let* ((l (loopy (cycle 1000) (collect (cl-evenp (random 2))))))
      (bench-multi
        :times 10000
        :forms (("cl-loop" (cl-loop for i in l count i))
                ("loopy" (loopy (list i l) (count i)))
                ("cl-count" (cl-count t l))
                ("cl-count-if" (cl-count-if #'identity l))
                ("seq-count" (seq-count #'identity l))
                ("-count" (-count #'identity l)))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    -count 1.55 0.241444 0 0
    cl-count 1.01 0.373984 0 0
    loopy 1.02 0.378541 0 0
    cl-loop 1.94 0.385982 0 0
    cl-count-if 1.07 0.750654 0 0
    seq-count slowest 0.799656 0 0
  • sum:
    (let* ((l (number-sequence 1 1000)))
      (bench-multi
        :times 10000
        :forms (("cl-loop" (cl-loop for i in l sum (1+ i)))
                ("loopy" (loopy (list i l) (sum (1+ i))))
                ("mapcar and apply" (apply #'+ (mapcar #'1+ l)))
                ("mapcar and cl-reduce" (cl-reduce #'+ (mapcar #'1+ l)))
                ("mapcar and seq-reduce" (seq-reduce #'+ (mapcar #'1+ l) 0))
                ("mapcar and -reduce" (-reduce #'+ (mapcar #'1+ l))))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    mapcar and apply 1.13 0.426344 0 0
    loopy 1.01 0.483621 0 0
    cl-loop 1.33 0.487986 0 0
    mapcar and -reduce 1.79 0.647770 0 0
    mapcar and cl-reduce 1.04 1.157679 0 0
    mapcar and seq-reduce slowest 1.198302 0 0
  • maximize:
    (let* ((l (number-sequence 1 1000)))
      (bench-multi
        :times 10000
        :forms (("cl-loop" (cl-loop for i in l maximize (1- i)))
                ("loopy" (loopy (list i l) (max (1- i))))
                ("mapcar and apply" (apply #'max (mapcar #'1- l)))
                ("mapcar and cl-reduce" (cl-reduce #'max (mapcar #'1- l)))
                ("mapcar and seq-reduce" (seq-reduce #'max (mapcar #'1- l) -1.0e+INF))
                ("mapcar and -reduce" (-reduce #'max (mapcar #'1- l))))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    mapcar and apply 1.07 0.499009 0 0
    loopy 1.34 0.535440 0 0
    mapcar and -reduce 1.18 0.718134 0 0
    cl-loop 1.46 0.848273 0 0
    mapcar and cl-reduce 1.02 1.239439 0 0
    mapcar and seq-reduce slowest 1.261978 0 0
  • minimize:
    (let* ((l (number-sequence 1 1000)))
      (bench-multi
        :times 10000
        :forms (("cl-loop" (cl-loop for i in l minimize (1- i)))
                ("loopy" (loopy (list i l) (min (1- i))))
                ("mapcar and apply" (apply #'min (mapcar #'1- l)))
                ("mapcar and cl-reduce" (cl-reduce #'min (mapcar #'1- l)))
                ("mapcar and seq-reduce" (seq-reduce #'min (mapcar #'1- l) +1.0e+INF))
                ("mapcar and -reduce" (-reduce #'min (mapcar #'1- l))))))
        
    Form x faster than next Total runtime # of GCs Total GC runtime
    mapcar and apply 1.06 0.501541 0 0
    loopy 1.39 0.533328 0 0
    mapcar and -reduce 1.15 0.741511 0 0
    cl-loop 1.49 0.850181 0 0
    mapcar and cl-reduce 1.02 1.262570 0 0
    mapcar and seq-reduce slowest 1.282561 0 0

Destructuring

(let ((alist (loopy (numbers n 0 1000)
                    (collect (cons n (+ n 2000))))))
  (bench-multi
    :times 10000
    :forms (("cl-loop" (cl-loop for (i . j) in alist collect (+ i j)))
            ("loopy" (loopy (list (i . j) alist) (collect (+ i j))))
            ("pcase-lambda" (mapcar (pcase-lambda (`(,i . ,j)) (+ i j)) alist))
            ("cl-function" (mapcar (cl-function (lambda ((i . j)) (+ i j))) alist))
            ("-lambda" (mapcar (-lambda ((i . j)) (+ i j)) alist)))))
Form x faster than next Total runtime # of GCs Total GC runtime
cl-loop 1.01 0.814436 0 0
loopy 2.31 0.821378 0 0
-lambda 1.10 1.900049 0 0
pcase-lambda 1.01 2.082832 0 0
cl-function slowest 2.101464 0 0
Clone this wiki locally