all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Robert Boyer <robertstephenboyer@gmail.com>
To: emacs-devel@gnu.org
Subject: Some thoughts about Emacs performance
Date: Wed, 7 Feb 2024 23:44:38 -0600	[thread overview]
Message-ID: <CAP9n0TP6OyFjvouPmpJ8qcg9v34GH1dq=_fJeNbYJEJghSTacA@mail.gmail.com> (raw)


[-- Attachment #1.1: Type: text/plain, Size: 1115 bytes --]

Emacs 27.1 has a 'sort' function that takes longer than stable-sort of
SBCL. Maybe
by a factor of 2. See also my attached file 'ms.lisp'.

There may be a lot that can be improved in Emacs'
handling of cl-loop, setf, elt, or cl-random.

;; First some Emacs, with times on my $100 Chromebook.

(setq n 6)
(defun make-random-array (n)
  (let ((a (make-vector n 0)))
    (cl-loop for i below n do
             (setf (elt a i) (cl-random 1000000)))
    a))
(byte-compile 'make-random-array)
(benchmark '(setq foo (make-random-array (expt 10 n))) 1) -- 2.3 seconds
(benchmark '(sort foo '<) 1) -- 1 second

;; Second some Common Lisp, with times for SBCL on my $100 Chromebook.

(defparameter n 6)
(defun make-random-array (n)
  (declare (fixnum n))
  (let ((a (make-array n)))
    (declare (type array a))
    (loop for i fixnum below n do
          (setf (aref a i) (random most-positive-fixnum)))
    a))
(time (defparameter foo (make-random-array (expt 10 n))))  -- .041 seconds
(time (progn (stable-sort foo '<) nil)) -- .45 seconds

Thanks so much for Emacs, which is so great that I cannot put it
into words.

Bob

[-- Attachment #1.2: Type: text/html, Size: 1735 bytes --]

[-- Attachment #2: ms.lisp --]
[-- Type: application/octet-stream, Size: 13979 bytes --]

; -*- coding: utf-8 -*-

;;; ms.lisp was coded by Robert S. Boyer, roberstephenboyer@gmail.com, in
;;; 2024. ms.lisp is public domain. It runs in SBCL.  The main function
;;; in ms.lisp is msa, which is simply good old merge sort.

;;; ms.lisp is
;;; https://drive.google.com/file/d/1OEYqc7wmGjUtj7Hhy_3QmOS23yJ4Izaf/view?usp=sharing

;;; Consider sorting an array of 10 million random rationals and floats.

;;; msa         takes 37.082  real seconds.
;;; stable-sort takes 69.321  real seconds.
;;; sort        takes 110.047 real seconds.

;;; The above three times were obtained using SBCL on my $100 Lenovo Chromebook.

;;; The following three times were obtained on an M3 machine by Grant Passmore.

;;; msa         takes 5.73    seconds.
;;; stable-sort takes 7.241   seconds.
;;; sort        takes 15.811  seconds.

;;; After (load "ms.lisp") one may run this form to confirm my timing results.

#|
(let* ((a (make-random-array-mixed (expt 10 7))) 
       (ac1 (copy-seq a))
       (ac2 (copy-seq a)))
  (gc :full t) 
  (format t "~%Timing msa.") 
  (time (msa a))
  (gc :full t)
  (format t "~%Timing stable-sort.") 
  (time (stable-sort ac1 '<))
  (gc :full t)
  (format t "~%Timing sort.") 
  (time (sort ac2 '<))
  (cond ((or (not (equalp a ac1)) (not (equalp a ac2)))
         (error "failed")))
  t)
|#

;;; I use --dynamic-space-size 32000 in my SBCL startup file.
;;; you can simply invoke
;;;   sbcl --dynamic-space-size 32000
;;; to start sbcl.

;;; (long-test-msa) will run tests of msa vs. stable-sort and sort on mixed,
;;; fixnum, and float random arrays of sizes starting at one million and
;;; going up by a million. I cannot get past 19 million on my Chromebook due
;;; to space problems.

;;; On arrays of mixed rationals and floats, msa does seem generally a good
;;; bit faster. On arrays that are all fixnums, msa is roughly as good as
;;; stable-sort. On arrays that are all floats, msa is about 25% worse than
;;; stable-sort, and I have no idea why.

(in-package "COMMON-LISP-USER")

(declaim (optimize (safety 0) (speed 3) (debug 0)))

(defun print-date-and-time ()
  (multiple-value-bind
   (second minute hour day month year day-of-week dst-p tz)
   (get-decoded-time)
   (declare (ignore dst-p tz))
   (format *standard-output*
           "~2,'0d:~2,'0d:~2,'0d, ~a, ~d/~2,'0d/~d~%"
           hour minute second
           (nth day-of-week '("Monday" "Tuesday" "Wednesday" "Thursday"
                              "Friday" "Saturday" "Sunday"))
           month day year)))

;;; We turn on reporting of garbage collection. The header asks Emacs to
;;; enter auto-revert-tail mode.

(let ((stream (open "gc-logfile.txt" :direction :output
                    :if-exists :supersede
                    :if-does-not-exist :create)))
  ;; (setf (gc-logfile) nil) turns gc reporting off.
  (format stream "-*- Mode: auto-revert-tail -*-~%")
  (let ((*standard-output* stream))
    (terpri)
    (print-date-and-time)
    (terpri))
  (close stream)
  (setf (gc-logfile) "gc-logfile.txt")
  (gc :full t))

(defparameter msa-input (make-array 1))

(defparameter msa-scratch (make-array 1))

(declaim (type simple-vector msa-input msa-scratch))

(deftype rational-or-float () '(or rational float))

(defmacro my-< (x y)
  (let ((gx (gensym)) (gy (gensym)))
    `(let ((,gx ,x) (,gy ,y))
       (cond ((and (typep ,gx 'fixnum) (typep ,gy 'fixnum))
              (< (the fixnum ,gx) (the fixnum ,gy)))
             ((and (typep ,gx 'float) (typep ,gy 'float))
              (< (the float ,gx) (the float ,gy)))
             (t (< ,gx ,gy))))))

(defun msa-1 (start end)
  (declare (fixnum start end))
  (let ((start+1 (the fixnum (1+ start)))
        (end-start (the fixnum (- end start))))
    (declare (fixnum start+1 end-start))
    (cond ((or (= start end) (= start+1 end)))
          ((= end-start 2)
           (let ((as-start (svref msa-input start))
                 (as-start+1 (svref msa-input start+1)))
             (declare (rational-or-float as-start as-start+1))
             (cond ((my-< as-start+1 as-start)
                    (setf (svref msa-input start) as-start+1)
                    (setf (svref msa-input start+1) as-start)))))
          (t (let ((mid (the fixnum (+ start (the fixnum (floor end-start 2))))))
               (declare (fixnum mid))
               (cond ((= start+1 mid))
                     (t (msa-1 start mid)))
               (msa-1 mid end)
               (let ((i0 start) (i1 mid) (end0 mid) (end1 end))
                 (declare (fixnum i0 i1 end0 end1))
                 (loop for si fixnum below end-start do
                       (cond ((= i0 end0)
                              (setf (svref msa-scratch si) (svref msa-input i1))
                              (incf i1))
                             ((= i1 end1)
                              (setf (svref msa-scratch si) (svref msa-input i0))
                              (incf i0))
                             ((my-< (the rational-or-float (svref msa-input i0))
                                    (the rational-or-float (svref msa-input i1)))
                              (setf (svref msa-scratch si) (svref msa-input i0))
                              (incf i0))
                             (t (setf (svref msa-scratch si) (svref msa-input i1))
                                (incf i1)))))
               (loop for i fixnum below end-start as j fixnum from start do
                     (setf (svref msa-input j) (svref msa-scratch i))))))
    nil))

(defun msa-1-fixnum (start end)
  (declare (fixnum start end))
  (let ((start+1 (the fixnum (1+ start)))
        (end-start (the fixnum (- end start))))
    (declare (fixnum start+1 end-start))
    (cond ((or (= start end) (= start+1 end)))
          ((= end-start 2)
           (let ((as-start (svref msa-input start))
                 (as-start+1 (svref msa-input start+1)))
             (declare (fixnum as-start as-start+1))
             (cond ((< as-start+1 as-start)
                    (setf (svref msa-input start) as-start+1)
                    (setf (svref msa-input start+1) as-start)))))
          (t (let ((mid (the fixnum (+ start (the fixnum (floor end-start 2))))))
               (declare (fixnum mid))
               (cond ((= start+1 mid))
                     (t (msa-1-fixnum start mid)))
               (msa-1-fixnum mid end)
               (let ((i0 start) (i1 mid) (end0 mid) (end1 end))
                 (declare (fixnum i0 i1 end0 end1))
                 (loop for si fixnum below end-start do
                       (cond ((= i0 end0)
                              (setf (svref msa-scratch si) (svref msa-input i1))
                              (incf i1))
                             ((= i1 end1)
                              (setf (svref msa-scratch si) (svref msa-input i0))
                              (incf i0))
                             ((< (the fixnum (svref msa-input i0))
                                 (the fixnum (svref msa-input i1)))
                              (setf (svref msa-scratch si) (svref msa-input i0))
                              (incf i0))
                             (t (setf (svref msa-scratch si) (svref msa-input i1))
                                (incf i1)))))
               (loop for i fixnum below end-start as j fixnum from start do
                     (setf (svref msa-input j) (svref msa-scratch i))))))
    nil))

(defun msa-1-float (start end)
  (declare (fixnum start end))
  (let ((start+1 (the fixnum (1+ start)))
        (end-start (the fixnum (- end start))))
    (declare (fixnum start+1 end-start))
    (cond ((or (= start end) (= start+1 end)))
          ((= end-start 2)
           (let ((as-start (svref msa-input start))
                 (as-start+1 (svref msa-input start+1)))
             (declare (float as-start as-start+1))
             (cond ((< as-start+1 as-start)
                    (setf (svref msa-input start) as-start+1)
                    (setf (svref msa-input start+1) as-start)))))
          (t (let ((mid (the fixnum (+ start (the fixnum (floor end-start 2))))))
               (declare (fixnum mid))
               (cond ((= start+1 mid))
                     (t (msa-1-float start mid)))
               (msa-1-float mid end)
               (let ((i0 start) (i1 mid) (end0 mid) (end1 end))
                 (declare (fixnum i0 i1 end0 end1))
                 (loop for si fixnum below end-start do
                       (cond ((= i0 end0)
                              (setf (svref msa-scratch si) (svref msa-input i1))
                              (incf i1))
                             ((= i1 end1)
                              (setf (svref msa-scratch si) (svref msa-input i0))
                              (incf i0))
                             ((< (the float (svref msa-input i0))
                                 (the float (svref msa-input i1)))
                              (setf (svref msa-scratch si) (svref msa-input i0))
                              (incf i0))
                             (t (setf (svref msa-scratch si) (svref msa-input i1))
                                (incf i1)))))
               (loop for i fixnum below end-start as j fixnum from start do
                     (setf (svref msa-input j) (svref msa-scratch i))))))
    nil))

(defun msa (ar)
  "(msa ar) takes a one dimensional array ar of rationals or floats,
   sorts ar by <, and returns ar. msa creates a scratch array the length of
   ar. If ar is not a simple-vector, then ar also creates another array the
   length of ar."
  (cond ((not (typep ar '(array t (*))))
         (error "msa input not an array of one dimension")))
  (let ((len (length ar)) (ar ar) (all-fixnum t) (all-float t))
    (declare (fixnum len) (type simple-vector ar))
    (loop for i fixnum below len do
          (let ((x (aref ar i)))
            (cond ((not (typep x 'rational-or-float))
                   (error "ar takes an array of rationals or floats."))
                  ((not (typep x 'fixnum)) (setq all-fixnum nil))
                  ((not (typep x 'float)) (setq all-float nil)))))
    (cond ((typep ar 'simple-vector)
           (setq msa-input ar))
          (t (setq msa-input (copy-seq msa-input))))
    (setq msa-scratch (make-array len))
    (cond (all-fixnum
           ;; (format t "~%msa-1-fixnum.")
           (msa-1-fixnum 0 len))
          (all-float
           ;; (format t "~%msa-1-float.")
           (msa-1-float 0 len))
          (t (msa-1 0 len)))
    (cond ((typep ar 'simple-vector))
          (t (loop for i below len do
                   (setf (aref ar i) (svref msa-input i)))))
    (setq msa-input (make-array 1))
    (setq msa-scratch (make-array 1))
    ;; (gc :full t)
    ar))

;;; The remainder of this file concerns testing.

(defparameter test-msa-ar1 (make-array 1))

(defparameter test-msa-ar2 (make-array 1))

(defun make-random-array-fixed (n)
  (declare (fixnum n))
  (let ((a (make-array n)))
    (declare (type (array t (*)) a))
    (loop for i fixnum below n do
          (setf (aref a i) (random most-positive-fixnum)))
    a))

(defun make-random-array-float (n)
  (declare (fixnum n))
  (let ((a (make-array n)))
    (declare (type (array t (*)) a))
    (loop for i fixnum below n do
          (setf (aref a i) (float (random most-positive-fixnum))))
    a))

(defun random-mixed ()
  (let* ((c (random 3))
         (x (* 2 most-positive-fixnum))
         (n (random x))
         (d (random x)))
    (cond ((= d 0) (setq d 1)))
    (cond ((= c 0) n)
          ((= c 1) (/ n d))
          ((= c 2) (float (/ n d))))))

(defun make-random-array-mixed (n)
  (declare (fixnum n))
  (let ((a (make-array n)))
    (declare (type (array t (*)) a))
    (loop for i fixnum below n do
          (setf (aref a i) (random-mixed)))
    a))

(defun test-msa-mixed (n)
  "(test-msa n) creates an array ar1 of n random rationals and floats,
   creates a copy ar2 of ar1, calls (msa ar1), calls (stable-sort ar2 '<),
   and returns (equalp ar1 ar2)."
  (declare (fixnum n))
  (format t "~%running (test-msa-mixed ~:d)." n)
  (setq test-msa-ar1 (make-random-array-mixed n))
  (setq test-msa-ar2 (copy-seq test-msa-ar1))
  (gc :full t)
  (format t "~%Timing of (msa test-msa-ar1).")
  (time (msa test-msa-ar1))
  (gc :full t)
  (format t "~%Timing of (stable-sort test-msa-ar2 '<).")
  (time (stable-sort test-msa-ar2 '<))
  (equalp test-msa-ar1 test-msa-ar2))

(defun test-msa-fixed (n)
  "(test-msa n) creates an array ar1 of n random fixnums,
   creates a copy ar2 of ar1, calls (msa ar1), calls (stable-sort ar2 '<),
   and returns (equalp ar1 ar2)."
  (declare (fixnum n))
  (format t "~%running (test-msa-fixed ~:d)." n)
  (setq test-msa-ar1 (make-random-array-fixed n))
  (setq test-msa-ar2 (copy-seq test-msa-ar1))
  (gc :full t)
  (format t "~%Timing of (msa test-msa-ar1).")
  (time (msa test-msa-ar1))
  (gc :full t)
  (format t "~%Timing of (stable-sort test-msa-ar2 '<).")
  (time (stable-sort test-msa-ar2 '<))
  (equalp test-msa-ar1 test-msa-ar2))

(defun test-msa-float (n)
  "(test-msa n) creates an array ar1 of n random floats, creates a copy ar2
   of ar1, calls (msa ar1), calls (stable-sort ar2 '<), and returns (equalp
   ar1 ar2)."
  (declare (fixnum n))
  (format t "~%running (test-msa-float ~:d)." n)
  (setq test-msa-ar1 (make-random-array-float n))
  (setq test-msa-ar2 (copy-seq test-msa-ar1))
  (gc :full t)
  (format t "~%Timing of (msa test-msa-ar1).")
  (time (msa test-msa-ar1))
  (gc :full t)
  (format t "~%Timing of (stable-sort test-msa-ar2 '<).")
  (time (stable-sort test-msa-ar2 '<))
  (equalp test-msa-ar1 test-msa-ar2))

(defun long-test-msa ()
  (loop for i from 1 do 
        (let ((n (* i (expt 10 6))))
          (cond ((not (test-msa-mixed n))
                 (error "long-test failed mixed at ~:d." n)))
          (cond ((not (test-msa-fixed n))
                 (error "long-test failed fixed at ~:d." n)))
          (cond ((not (test-msa-float n))
                 (error "long-test failed float at ~:d." n))))))

             reply	other threads:[~2024-02-08  5:44 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-02-08  5:44 Robert Boyer [this message]
2024-02-08 14:47 ` Some thoughts about Emacs performance Ihor Radchenko
2024-02-16 14:08 ` Simon Leinen
2024-02-16 16:15   ` Robert Boyer
2024-02-19 12:29     ` Ihor Radchenko
2024-02-19 19:05       ` Robert Boyer
2024-02-16 16:31   ` Robert Boyer
2024-02-16 18:06   ` Robert Boyer
2024-02-16 20:28     ` [External] : " Drew Adams
2024-02-16 18:34   ` Robert Boyer
  -- strict thread matches above, loose matches on Subject: below --
2024-02-08 23:53 Arthur Miller
     [not found] <DU2PR02MB10109962DC3D994DCE1BCC11896442@DU2PR02MB10109.eurprd02.prod.outlook.com>
2024-02-09  0:23 ` Ihor Radchenko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CAP9n0TP6OyFjvouPmpJ8qcg9v34GH1dq=_fJeNbYJEJghSTacA@mail.gmail.com' \
    --to=robertstephenboyer@gmail.com \
    --cc=emacs-devel@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

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

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.