Более быстрый метод для получения `line-number-at-pos` в больших буферах

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))
lawlist
источник

Ответы:

17

Пытаться

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

Вы можете извлечь другую информацию, используя % -Constructs, описанные в Руководстве по Emacs Lisp.

Предостережение:

В дополнение к ограничениям, указанным 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, устанавливается по умолчанию, как я узнал здесь .
Васамаса
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))

со следующей дополнительной настройкой в ​​функции mode:

(add-hook 'after-change-functions #'nlinum--after-change nil t)
Стефан
источник
1
Ах ... Я просто думал о твоей библиотеке сегодня утром. Их line-number-at-posможно заменить на ответ Константина, и это ускорит вашу библиотеку даже больше, чем она уже есть, особенно в больших буферах. count-linesследует также исправить с помощью метода Константина. Я даже думал о том, чтобы отправить предложение-предложение на горячую линию report-emacs-bug, чтобы исправить эти функции.
законник