unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* Compiler macro for apply-partially
@ 2021-08-24  6:37 Adam Porter
  2021-08-24  6:51 ` Adam Porter
  2021-08-24 22:32 ` Stefan Monnier
  0 siblings, 2 replies; 8+ messages in thread
From: Adam Porter @ 2021-08-24  6:37 UTC (permalink / raw)
  To: emacs-devel

Hi,

I wondered if using apply-partially had a significant runtime cost, so I
did a little benchmark using my bench-multi-lexical macro.[0]  I found
that it seems to be much slower than writing a lambda form directly,
which the byte-compiler can optimize more effectively.  It also conses,
so it can contribute to earlier GC.  Whether it has a noticable effect
depends on the application, I suppose.

Anyway, I came up with a simple compiler-macro declaration that seems to
help.  In these benchmarks, the version with the compiler-macro is named
`apply-partially*': it's roughly twice as fast as `apply-partially' and
produces about half as much garbage.

I was curious to see if it could be further optimized for cases in which
the partially applied function is called with only one argument, which
is the most common case for me, so I also made a version that does that,
here named `apply-partially**', and it's virtually identical in
performance to writing a lambda directly.

Please see the benchmark code and results below.  If any of these
enhancements would be useful, I'd be glad to submit a patch.  Please let
me know.

Thanks,
Adam

0: https://github.com/alphapapa/emacs-package-dev-handbook#bench-multi-lexical

#+BEGIN_SRC elisp :exports code :results silent
  (defun apply-partially* (fun &rest args)
    "Return a function that is a partial application of FUN to ARGS.
  ARGS is a list of the first N arguments to pass to FUN.  The
  result is a new function which does the same as FUN, except that
  the first N arguments are fixed at the values with which this
  function was called."
    (declare (compiler-macro (lambda (exp)
                               `(lambda (&rest args2)
                                  (apply ,fun ,@args args2)))))
    (lambda (&rest args2)
      (apply fun (append args args2))))

  (defun apply-partially** (fun &rest args)
    "Return a function that is a partial application of FUN to ARGS.
  ARGS is a list of the first N arguments to pass to FUN.  The
  result is a new function which does the same as FUN, except that
  the first N arguments are fixed at the values with which this
  function was called, and it only accepts one additional
  argument."
    (declare (compiler-macro (lambda (exp)
                               `(lambda (arg2)
                                  ,(pcase fun
                                     (`(function ,fun)
                                      `(,fun ,@args arg2))
                                     (_ `(,fun ,@args arg2)))))))
    (lambda (arg2)
      (apply fun (append args (list arg2)))))
#+END_SRC

* With one applied argument

#+BEGIN_SRC elisp :exports both
  (bench-multi-lexical :times 100000 :ensure-equal t
    :forms (("lambda" (let* ((foo 'FOO)
                             (fn (lambda (arg)
                                   (eq arg foo))))
                        (cl-loop for i below 100
                                 do (funcall fn foo))))
            ("apply-partially" (let* ((foo 'FOO)
                                      (fn (apply-partially #'eq foo)))
                                 (cl-loop for i below 100
                                          do (funcall fn foo))))
            ("apply-partially*" (let* ((foo 'FOO)
                                       (fn (apply-partially* #'eq foo)))
                                  (cl-loop for i below 100
                                           do (funcall fn foo))))
            ("apply-partially**" (let* ((foo 'FOO)
                                        (fn (apply-partially** #'eq foo)))
                                   (cl-loop for i below 100
                                            do (funcall fn foo))))))
#+END_SRC

#+RESULTS:
| Form              | x faster than next | Total runtime | # of GCs | Total GC runtime |
|-------------------+--------------------+---------------+----------+------------------|
| lambda            |               1.02 |      0.446226 |        0 |                0 |
| apply-partially** |               5.11 |      0.456534 |        0 |                0 |
| apply-partially*  |               1.99 |      2.331980 |        5 |         1.476272 |
| apply-partially   |            slowest |      4.629640 |       11 |         3.257624 |

* With two applied arguments

#+BEGIN_SRC elisp :exports both
  (bench-multi-lexical :times 100000 :ensure-equal t
    :forms (("lambda" (let* ((n 0)
                             (m 1)
                             (fn (lambda (x y z)
                                   (< x y z))))
                        (cl-loop for i below 100
                                 do (funcall fn n m i))))
            ("apply-partially" (let* ((n 0)
                                      (m 1)
                                      (fn (apply-partially #'< n m)))
                                 (cl-loop for i below 100
                                          do (funcall fn i))))
            ("apply-partially*" (let* ((n 0)
                                       (m 1)
                                       (fn (apply-partially* #'< n m)))
                                  (cl-loop for i below 100
                                           do (funcall fn i))))
            ("apply-partially**" (let* ((n 0)
                                        (m 1)
                                        (fn (apply-partially** #'< n m)))
                                   (cl-loop for i below 100
                                            do (funcall fn i))))))
#+END_SRC

#+RESULTS:
| Form              | x faster than next | Total runtime | # of GCs | Total GC runtime |
|-------------------+--------------------+---------------+----------+------------------|
| apply-partially** |               1.00 |      0.551158 |        0 |                0 |
| lambda            |               4.38 |      0.551878 |        0 |                0 |
| apply-partially*  |               2.75 |      2.415262 |        5 |         1.485259 |
| apply-partially   |            slowest |      6.640960 |       17 |         5.059900 |

* With two applied arguments and two called arguments

#+BEGIN_SRC elisp :exports both
  (bench-multi-lexical :times 100000 :ensure-equal t
    :forms (("lambda" (let* ((n 0)
                             (m 1)
                             (fn (lambda (x y z a)
                                   (< x y z a))))
                        (cl-loop for i below 100
                                 do (funcall fn n m i i))))
            ("apply-partially" (let* ((n 0)
                                      (m 1)
                                      (fn (apply-partially #'< n m)))
                                 (cl-loop for i below 100
                                          do (funcall fn i i))))
            ("apply-partially*" (let* ((n 0)
                                       (m 1)
                                       (fn (apply-partially* #'< n m)))
                                  (cl-loop for i below 100
                                           do (funcall fn i i))))))
#+END_SRC

#+RESULTS:
| Form             | x faster than next | Total runtime | # of GCs | Total GC runtime |
|------------------+--------------------+---------------+----------+------------------|
| lambda           |               7.06 |      0.629179 |        0 |                0 |
| apply-partially* |               1.87 |      4.441770 |       11 |         3.310380 |
| apply-partially  |            slowest |      8.323220 |       22 |         6.596604 |





^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2021-08-25 18:44 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2021-08-24  6:37 Compiler macro for apply-partially Adam Porter
2021-08-24  6:51 ` Adam Porter
2021-08-24  7:22   ` Andreas Schwab
2021-08-24  7:38     ` Adam Porter
2021-08-24 22:32 ` Stefan Monnier
2021-08-25 17:45   ` Eric Abrahamsen
2021-08-25 18:27     ` Stefan Monnier
2021-08-25 18:44       ` Eric Abrahamsen

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).