Revisiting this old topic, (string-to-number (format-mode-line "%l") still holds up quite well against (line-number-at-position). I found it is also highly cached for "nearby" line positions. For example, operating on /usr/dict/words (236k lines here):
(let* ((buf (get-buffer "words")) (win (get-buffer-window buf)) (len (buffer-size buf)) (off 0) (cnt 20000) (step (floor (/ (float len) cnt))) line) (set-window-point win 0) (redisplay) ; we must start at the top; see note [1] (measure-time (dotimes (i cnt) (set-window-point win (cl-incf off (+ step (random 5))));(random len)) (setq line (string-to-number (format-mode-line "%l" 0 win))))) (message "Final line: %d (step %d cnt %d)" line step cnt))
This takes about 20s to run through, visiting 20,000 positions spanning the entire file in order. If instead you just look in the vicinity of a position (here ±5000 characters):
(let* ((buf (get-buffer "words")) (win (get-buffer-window buf)) (len (buffer-size buf)) (off (/ len 2)) (cnt 20000) (step (floor (/ (float len) cnt))) line) (set-window-point win off) (redisplay) ; we must start at the top (measure-time (dotimes (i cnt) (let ((pos (+ off (- (random 10000) 5000)))) (set-window-point win pos);(random len)) (setq line (string-to-number (format-mode-line "%l" 0 win)))))) (message "Final line: %d (step %d cnt %d)" line step cnt))
This only takes ~1/5s or so! So (as you'd expect) it's highly optimized for scrolling to nearby locations (vs jumping across the entire file randomly).
In contrast:
(let* ((buf (get-buffer "words")) (win (get-buffer-window buf)) (len (buffer-size buf)) (off 0) (cnt 1000) (step (floor (/ (float len) cnt))) line) (measure-time (dotimes (i cnt) (with-current-buffer buf (setq line (line-number-at-pos (cl-incf off step)))))) (message "Final line: %d" line))
takes over 10s (for 20x fewer positions) each and every time. So to summarize, format-mode-line is ~10x faster than line-number-at-pos, on a full and fast run through a long file. And for nearby positions, it gives near instantaneous results through local caching.
[1] Before I brought the point back to the top and redisplayed, subsequent full-file runs were taking << 1 second. At first I thought this was some miraculous internal caching of format-mode-line, but then I noticed that this only happened if you left point at the end of the buffer. In that case set-window-point doesn't actually move point over such long distances, and format-mode-line just quickly returns the same line, over and over.