The function line-number-at-pos (when repeated about 50 times) is causing a noticeable slow-down in semi-large buffers -- e.g., 50,000 lines -- when point is near the end of the buffer. By slow-down, I mean a combined total of about 1.35 seconds.

Instead of using a 100% elisp funciton to count-lines and goto the top of the buffer, I'd be interested in a hybrid method that taps into the built-in C abilities responsible for the line number appearing on the mode-line. The line-number that appears on the mode-line occurs at light speed, regardless of the size of the buffer.

Here is a test function:

(defmacro measure-time (&rest body)
"Measure the time it takes to evaluate BODY.
  `(let ((time (current-time)))
     (message "%.06f" (float-time (time-since time)))))

  (let* (
      (window-start (window-start))
      (window-end (window-end)))
      (goto-char window-end)
        (re-search-backward "\n" window-start t)
        (push (line-number-at-pos) line-numbers)))


(string-to-number (format-mode-line "%l"))

You can extract other information using %-Constructs described in the Emacs Lisp Manual.


In addition to limitations pointed out by wasamasa and Stefan (see comments below) this does not work for buffers that are not displayed.

Try this:

  (dotimes (i 10000)
    (insert (format "%d\n" i)))
  (string-to-number (format-mode-line "%l")))

and compare to

  (dotimes (i 10000)
    (insert (format "%d\n" i)))
| improve this answer | |
  • Yes, that reduced it from 1.35 seconds to 0.003559! Thank you very much -- greatly appreciated! :) – lawlist Nov 23 '14 at 8:10
  • 6
    Be aware that this method will give you "??" for lines exceeding line-number-display-limit-width which is set to a value of 200 per default as I found out here. – wasamasa Nov 23 '14 at 11:29
  • 3
    IIRC the result may also be unreliable if there have been modifications in the buffer since the last redisplay. – Stefan Nov 23 '14 at 14:51
  • I believe it would be necessary to modify the tests in the answer such that the second letter i is replaced with (string-to-number (format-mode-line "%l")) for the first test, and the second letter i is replaced with (line-number-at-pos) for the second test. – lawlist Feb 16 '16 at 21:31

nlinum.el uses the following:

(defvar nlinum--line-number-cache nil)
(make-variable-buffer-local 'nlinum--line-number-cache)

;; We could try and avoid flushing the cache at every change, e.g. with:
;;   (defun nlinum--before-change (start _end)
;;     (if (and nlinum--line-number-cache
;;              (< start (car nlinum--line-number-cache)))
;;         (save-excursion (goto-char start) (nlinum--line-number-at-pos))))
;; But it's far from clear that it's worth the trouble.  The current simplistic
;; approach seems to be good enough in practice.

(defun nlinum--after-change (&rest _args)
  (setq nlinum--line-number-cache nil))

(defun nlinum--line-number-at-pos ()
  "Like `line-number-at-pos' but sped up with a cache."
  ;; (assert (bolp))
  (let ((pos
         (if (and nlinum--line-number-cache
                  (> (- (point) (point-min))
                     (abs (- (point) (car nlinum--line-number-cache)))))
             (funcall (if (> (point) (car nlinum--line-number-cache))
                          #'+ #'-)
                      (cdr nlinum--line-number-cache)
                      (count-lines (point) (car nlinum--line-number-cache)))
    ;;(assert (= pos (line-number-at-pos)))
    (setq nlinum--line-number-cache (cons (point) pos))

with the following extra config in the mode function:

(add-hook 'after-change-functions #'nlinum--after-change nil t)
| improve this answer | |
  • 1
    Ah ... I was just thinking about your library earlier this morning. The line-number-at-pos could be replaced with the answer by Constantine, and that would speed up your library even more than it already its -- especially in large buffers. count-lines should also be fixed using the method by Constantine. I was even thinking of sending in a suggest-box submission to the report-emacs-bug hotline to fix those functions. – lawlist Nov 23 '14 at 17:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.