Більш швидкий спосіб отримати "рядок номер-на-пози" у великих буферах


19

Ця функція line-number-at-pos(коли повторюється приблизно 50 разів) викликає помітне уповільнення в напіввеликих буферах - наприклад, 50000 рядків - коли точка знаходиться в кінці буфера. Під уповільненням я маю на увазі загальну суму приблизно 1,35 секунди.

Замість того, щоб використовувати 100% функціонал elispдля підрахунку рядків і перейти до верхньої частини буфера, я зацікавився би гібридним методом, який використовує вбудовані здібності C, відповідальні за номер рядка, що відображається в рядку режиму. Номер рядка, який відображається на рядку режиму, відбувається зі швидкістю світла, незалежно від розміру буфера.


Ось функція тесту:

(defmacro measure-time (&rest body)
"Measure the time it takes to evaluate BODY.
http://lists.gnu.org/archive/html/help-gnu-emacs/2008-06/msg00087.html"
  `(let ((time (current-time)))
     ,@body
     (message "%.06f" (float-time (time-since time)))))

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

Відповіді:


17

Спробуйте

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

Ви можете отримати іншу інформацію, використовуючи % -Constructs, описані в посібнику Emacs Lisp.

Caveat:

Окрім обмежень, на які вказували wasamasa та Stefan (див. Коментарі нижче), це не працює для буферів, які не відображаються.

Спробуйте це:

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

і порівняти

(with-temp-buffer
  (dotimes (i 10000)
    (insert (format "%d\n" i)))
  (line-number-at-pos))

Так, це зменшило його з 1,35 секунди до 0,003559! Дуже дякую - дуже вдячний! :)
законник

6
Будьте в курсі, що цей метод дасть вам "??" для рядків, що перевищують line-number-display-limit-widthвстановлене значення 200 за замовчуванням, як я дізнався тут .
wasamasa

3
Результат IIRC також може бути недостовірним, якщо в буфері відбулися зміни з моменту останнього повторного відображення.
Стефан

Я вважаю, що було б потрібно змінити тести у відповіді таким чином, щоб другий лист iзамінили (string-to-number (format-mode-line "%l"))на перший тест, а другий лист iзамінили (line-number-at-pos)на другий тест.
законник

5

nlinum.el використовує наступне:

(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)))
           (line-number-at-pos))))
    ;;(assert (= pos (line-number-at-pos)))
    (setq nlinum--line-number-cache (cons (point) pos))
    pos))

із наступною додатковою конфігурацією у режимі режиму:

(add-hook 'after-change-functions #'nlinum--after-change nil t)

1
А-а ... Я просто думав про вашу бібліотеку раніше цього ранку. Це line-number-at-posможе бути замінено відповіддю Костянтина, і це пришвидшило б вашу бібліотеку навіть більше, ніж вона вже є, особливо у великих буферах. count-linesтакож слід виправити за допомогою методу Костянтина. Я навіть думав надсилати подання в коробці пропозицій на гарячу лінію report-emacs-bug, щоб виправити ці функції.
законник
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.