From fe1e7094a7b76ba6bf282c5c4409a32b36827d56 Mon Sep 17 00:00:00 2001 Message-Id: From: Ihor Radchenko Date: Tue, 4 May 2021 22:55:14 +0800 Subject: [PATCH] Use cache in org-up-heading-safe * lisp/org.el (org-up-heading-safe): Use buffer-local cache to store positions of parent headings. The cache is invalidated when buffer text is changed, according to `buffer-chars-modified-tick'. (org--up-heading-cache): Buffer-local hash-table storing the cache. (org--up-heading-cache-tick): The buffer modification state for currently active `org--up-heading-cache'. The optimisation is critical when running agenda or org-entry-get queries using property/tag inheritance. In such scenarios `org-up-heading-safe' can be called thousands of times. For example, building todo agenda on large number of headings lead to the following benchmark results: Benchmark: 1. (elp-instrument-function #'org-up-heading-safe) 2. Run agenda 3. (elp-results) ;; function, # calls, total time, avg time Without cache: org-up-heading-safe 27555 8.4234025759 0.0003056941 With cache, first run: org-up-heading-safe 23227 0.5300747539 2.282...e-05 With cache, second run on unchanged buffer: org-up-heading-safe 23227 0.1447754880 6.233...e-06 The final speedup is 1-2 orders of magnitude (~15-56 times). --- lisp/org.el | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/lisp/org.el b/lisp/org.el index b8678359f..4385903ee 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -19434,6 +19434,10 @@ (defun org-up-heading-all (arg) With argument, move up ARG levels." (outline-up-heading arg t)) +(defvar-local org--up-heading-cache (make-hash-table) + "Buffer-local `org-up-heading-safe' cache.") +(defvar-local org--up-heading-cache-tick nil + "Buffer `buffer-chars-modified-tick' in `org--up-heading-cache'.") (defun org-up-heading-safe () "Move to the heading line of which the present line is a subheading. This version will not throw an error. It will return the level of the @@ -19443,10 +19447,41 @@ (defun org-up-heading-safe () because it relies on stars being the outline starters. This can really make a significant difference in outlines with very many siblings." (when (ignore-errors (org-back-to-heading t)) - (let ((level-up (1- (funcall outline-level)))) - (and (> level-up 0) - (re-search-backward (format "^\\*\\{1,%d\\} " level-up) nil t) - (funcall outline-level))))) + (let ((level-cache (gethash (point) org--up-heading-cache))) + (if (and level-cache + (eq (buffer-chars-modified-tick) org--up-heading-cache-tick)) + (when (<= (point-min) level-cache (point-max)) + ;; Parent is inside accessible part of the buffer. + (progn (goto-char level-cache) + (funcall outline-level))) + ;; Buffer modified. Invalidate cache. + (when level-cache + (clrhash org--up-heading-cache) + (setq-local org--up-heading-cache-tick + (buffer-chars-modified-tick))) + (let ((level-up (1- (funcall outline-level))) + (pos (point))) + (let (result) + (setq result + (and (> level-up 0) + (re-search-backward + (format "^\\*\\{1,%d\\} " level-up) nil t) + (funcall outline-level))) + (when result + (puthash pos (point) org--up-heading-cache)))))))) +;; (defun org-up-heading-safe () +;; "Move to the heading line of which the present line is a subheading. +;; This version will not throw an error. It will return the level of the +;; headline found, or nil if no higher level is found. + +;; Also, this function will be a lot faster than `outline-up-heading', +;; because it relies on stars being the outline starters. This can really +;; make a significant difference in outlines with very many siblings." +;; (when (ignore-errors (org-back-to-heading t)) +;; (let ((level-up (1- (funcall outline-level)))) +;; (and (> level-up 0) +;; (re-search-backward (format "^\\*\\{1,%d\\} " level-up) nil t) +;; (funcall outline-level))))) (defun org-up-heading-or-point-min () "Move to the heading line of which the present is a subheading, or point-min. -- 2.26.3