branch: externals/phpinspect commit e270729e14ff20dcab728d0830c52e0cc13168e9 Author: Hugo Thunnissen <de...@hugot.nl> Commit: Hugo Thunnissen <de...@hugot.nl>
Implement splay tree for overlay storage/lookup This makes repeated overlay lookups during incremental parsing or buffer analysis more efficient. --- benchmarks/parse-file.el | 42 ++++++- phpinspect-bmap.el | 37 ++----- phpinspect-splayt.el | 281 +++++++++++++++++++++++++++++++++++++++++++++++ test/phpinspect-test.el | 1 + test/test-splayt.el | 86 +++++++++++++++ 5 files changed, 415 insertions(+), 32 deletions(-) diff --git a/benchmarks/parse-file.el b/benchmarks/parse-file.el index 9af0171e45..c2f05b2b25 100644 --- a/benchmarks/parse-file.el +++ b/benchmarks/parse-file.el @@ -1,13 +1,11 @@ (require 'phpinspect-parser) - (defun phpinspect-parse-current-buffer () (phpinspect-parse-buffer-until-point (current-buffer) (point-max))) - (let ((here (file-name-directory (or load-file-name buffer-file-name)))) (with-temp-buffer @@ -23,16 +21,23 @@ (phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :bmap bmap) (benchmark 1 '(phpinspect-parse-current-buffer))) + (garbage-collect) + (message "Incremental parse (no edits):") (phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :bmap bmap2 :previous-bmap bmap :edtrack (phpinspect-make-edtrack)) (benchmark 1 '(phpinspect-parse-current-buffer))) + (garbage-collect) + (message "Incremental parse repeat (no edits):") (phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :previous-bmap bmap2 :edtrack (phpinspect-make-edtrack)) (benchmark 1 '(phpinspect-parse-current-buffer))) + (garbage-collect) + (let ((edtrack (phpinspect-make-edtrack)) - (bmap (phpinspect-make-bmap))) + (bmap (phpinspect-make-bmap)) + (bmap-after (phpinspect-make-bmap))) ;; Fresh (phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :bmap bmap) (phpinspect-parse-current-buffer)) @@ -42,13 +47,40 @@ (goto-char 9062) (delete-backward-char 1) + (garbage-collect) + (phpinspect-edtrack-register-edit edtrack 9061 9061 1) - (phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :previous-bmap bmap :edtrack edtrack) - (benchmark 1 '(phpinspect-parse-current-buffer))))) + (phpinspect-with-parse-context (phpinspect-make-pctx :bmap bmap-after :incremental t :previous-bmap bmap :edtrack edtrack) + (benchmark 1 '(phpinspect-parse-current-buffer))) + + (phpinspect-edtrack-clear edtrack) + (insert "{") + + (phpinspect-edtrack-register-edit edtrack 9061 9062 0) + ;; Mark region as edit without length deta + (phpinspect-edtrack-register-edit edtrack 19552 19562 10) + + (garbage-collect) + + ;;(profiler-start 'cpu+mem) + (message "Incremental parse after 2 more edits:") + (phpinspect-with-parse-context (phpinspect-make-pctx :incremental t :previous-bmap bmap-after :edtrack edtrack) + (benchmark 1 '(phpinspect-parse-current-buffer))) + + ;; (save-current-buffer + ;; (profiler-stop) + ;; (profiler-report) + ;; (profiler-report-write-profile (concat here "/profile.txt"))) + ))) + + (with-temp-buffer + (insert-file-contents (concat here "/Response.php")) + (garbage-collect) (message "Bare (no token reuse) parse (warmup):") (benchmark 1 '(phpinspect-parse-current-buffer)) + (garbage-collect) (message "Bare (no token reuse) parse:") (benchmark 1 '(phpinspect-parse-current-buffer)))) diff --git a/phpinspect-bmap.el b/phpinspect-bmap.el index cc8fe8c17a..1170def7be 100644 --- a/phpinspect-bmap.el +++ b/phpinspect-bmap.el @@ -23,6 +23,8 @@ ;;; Code: +(require 'phpinspect-splayt) + (cl-defstruct (phpinspect-bmap (:constructor phpinspect-make-bmap)) (starts (make-hash-table :test #'eql :size (floor (/ (point-max) 4)) @@ -35,8 +37,8 @@ :rehash-size 1.5)) (token-stack nil :type list) - (overlays nil - :type list) + (overlays (phpinspect-make-splayt) + :type phpinspect-splayt) (last-token-start nil :type integer)) @@ -288,10 +290,9 @@ (gethash point (phpinspect-bmap-ends bmap))))) (defsubst phpinspect-bmap-overlay-at-point (bmap point) - (catch 'found - (dolist (overlay (phpinspect-bmap-overlays bmap)) - (when (phpinspect-overlay-overlaps-point overlay point) - (throw 'found overlay))))) + (let ((overlay (phpinspect-splayt-find (phpinspect-bmap-overlays bmap) point #'<= #'<= #'<=))) + (when (and overlay (phpinspect-overlay-overlaps-point overlay point)) + overlay))) (defsubst phpinspect-bmap-tokens-overlapping (bmap point) (let ((tokens)) @@ -311,7 +312,7 @@ (or (gethash token (phpinspect-bmap-meta bmap)) (let ((found?)) (catch 'found - (dolist (overlay (phpinspect-bmap-overlays bmap)) + (phpinspect-splayt-traverse (overlay (phpinspect-bmap-overlays bmap)) (when (setq found? (phpinspect-bmap-token-meta overlay token)) (throw 'found found?))))))) @@ -347,27 +348,9 @@ giving up. If not provided, this is 100." (let* ((overlays (phpinspect-bmap-overlays bmap)) (start (+ (phpinspect-meta-start token-meta) pos-delta)) (end (+ (phpinspect-meta-end token-meta) pos-delta)) - (overlay `(overlay ,start ,end ,pos-delta ,bmap-overlay ,token-meta)) - (before)) + (overlay `(overlay ,start ,end ,pos-delta ,bmap-overlay ,token-meta))) (phpinspect-bmap-register bmap start end (phpinspect-meta-token token-meta) whitespace-before overlay) - - (if overlays - (progn - (catch 'break - (while (setq before (car overlays)) - (if (> (phpinspect-overlay-start overlay) (phpinspect-overlay-end before)) - (throw 'break nil) - (setq overlays (cdr overlays))))) - - (if (and before (cdr overlays)) - ;; Append after - (progn - (setcdr overlays (cons overlay (cdr overlays)))) - ;; Append at end of overlay list - (nconc (phpinspect-bmap-overlays bmap) (list overlay)))) - - ;; No exising overlays, overwrite - (push overlay (phpinspect-bmap-overlays bmap))))) + (phpinspect-splayt-insert (phpinspect-bmap-overlays bmap) (phpinspect-overlay-end overlay) overlay))) (defun phpinspect-bmap-make-location-resolver (bmap) (lambda (token) diff --git a/phpinspect-splayt.el b/phpinspect-splayt.el new file mode 100644 index 0000000000..fa96aa7f61 --- /dev/null +++ b/phpinspect-splayt.el @@ -0,0 +1,281 @@ +;;; phpinspect-splayt.el --- A Splay Tree Implementation -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 Free Software Foundation, Inc + +;; Author: Hugo Thunnissen <de...@hugot.nl> +;; Keywords: php, languages, tools, convenience +;; Version: 0 + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; A splay tree implementation exclusively using `cons' and `list' data +;; structures with the aim to have as low a memory footprint as possible. +;; +;; Important functions: +;; - `phpinspect-splayt-insert' +;; - `phpinspect-splayt-find' +;; - `phpinspect-splayt-traverse' + +;;; Code: + +(defsubst phpinspect-make-splayt-node (key value &optional left right) + (cons (cons key value) (cons left right))) + +(defsubst phpinspect-splayt-node-left (node) + (cadr node)) + +(defsubst phpinspect-splayt-node-right (node) + (cddr node)) + +(defsubst phpinspect-splayt-node-key (node) + (caar node)) + +(defsubst phpinspect-splayt-node-value (node) + (cdar node)) + +(gv-define-setter phpinspect-splayt-node-left (left node) `(setcar (cdr ,node) ,left)) +(gv-define-setter phpinspect-splayt-node-right (right node) `(setcdr (cdr ,node) ,right)) +(gv-define-setter phpinspect-splayt-node-key (key node) `(setcar (car ,node) ,value)) +(gv-define-setter phpinspect-splayt-node-value (value node) `(setcdr (car ,node) ,value)) + +(defsubst phpinspect-splayt-node-update-parent (node parent new-val) + (if (eq node (phpinspect-splayt-node-left parent)) + (setf (phpinspect-splayt-node-left parent) new-val) + (setf (phpinspect-splayt-node-right parent) new-val))) + +(defsubst phpinspect-make-splayt (&optional root-node) + (cons root-node nil)) + +(defsubst phpinspect-splayt-root-node (splayt) + (car splayt)) + +(gv-define-setter phpinspect-splayt-root-node (node splayt) `(setcar ,splayt ,node)) + +(defsubst phpinspect-splayt-node-rotate-right (node &optional parent splayt) + (let* ((left (phpinspect-splayt-node-left node)) + (left-right (phpinspect-splayt-node-right left))) + (setf (phpinspect-splayt-node-right left) node) + (setf (phpinspect-splayt-node-left node) left-right) + + (when (and splayt (eq node (phpinspect-splayt-root-node splayt))) + (setf (phpinspect-splayt-root-node splayt) left)) + + (when parent + (phpinspect-splayt-node-update-parent node parent left)))) + +(defsubst phpinspect-splayt-node-rotate-left (node &optional parent splayt) + (let* ((right (phpinspect-splayt-node-right node)) + (right-left (phpinspect-splayt-node-left right))) + (setf (phpinspect-splayt-node-left right) node) + (setf (phpinspect-splayt-node-right node) right-left) + + (when (and splayt (eq node (phpinspect-splayt-root-node splayt))) + (setf (phpinspect-splayt-root-node splayt) right)) + + (when parent + (phpinspect-splayt-node-update-parent node parent right)))) + +(defsubst phpinspect-make-splayt-nav (splayt &optional current-node parents) + (cons splayt (cons (or current-node (phpinspect-splayt-root-node splayt)) + parents))) + +(defsubst phpinspect-splayt-nav-splayt (nav) + (car nav)) + +(defsubst phpinspect-splayt-nav-current (nav) + (cadr nav)) + +(defsubst phpinspect-splayt-nav-parents (nav) + (cddr nav)) + +(gv-define-setter phpinspect-splayt-nav-current (node nav) `(setcar (cdr ,nav) ,node)) +(gv-define-setter phpinspect-splayt-nav-parents (parents nav) `(setcdr (cdr ,nav) ,parents)) + +(defsubst phpinspect-splayt-nav-right (nav) + (push (phpinspect-splayt-nav-current nav) (phpinspect-splayt-nav-parents nav)) + (setf (phpinspect-splayt-nav-current nav) + (phpinspect-splayt-node-right (phpinspect-splayt-nav-current nav)))) + +(defsubst phpinspect-splayt-nav-left (nav) + (push (phpinspect-splayt-nav-current nav) (phpinspect-splayt-nav-parents nav)) + (setf (phpinspect-splayt-nav-current nav) + (phpinspect-splayt-node-left (phpinspect-splayt-nav-current nav)))) + +(defsubst phpinspect-splayt-nav-has-left-p (nav) + (phpinspect-splayt-node-left (phpinspect-splayt-nav-current nav))) + +(defsubst phpinspect-splayt-nav-has-right-p (nav) + (phpinspect-splayt-node-right (phpinspect-splayt-nav-current nav))) + +(defsubst phpinspect-splayt-nav-up (nav) + (setf (phpinspect-splayt-nav-current nav) + (pop (phpinspect-splayt-nav-parents nav)))) + +(defsubst phpinspect-splayt-nav-current-value (nav) + (phpinspect-splayt-node-value (phpinspect-splayt-nav-current nav))) + +(defsubst phpinspect-splayt-nav-current-key (nav) + (phpinspect-splayt-node-key (phpinspect-splayt-nav-current nav))) + +(defsubst phpinspect-splayt-insert (splayt key value) + (phpinspect-splayt-insert-node splayt (phpinspect-make-splayt-node key value))) + +(defsubst phpinspect-splay (splayt node parents) + (let (grandparent great-grandparent) + (while parents + (setq parent (pop parents)) + (setq grandparent (pop parents)) + + (if grandparent + (cond + ;; Zig-Zig rotation + ((and (eq parent (phpinspect-splayt-node-left grandparent)) + (eq node (phpinspect-splayt-node-left parent))) + (phpinspect-splayt-node-rotate-right grandparent (car parents) splayt) + (phpinspect-splayt-node-rotate-right parent (car parents) splayt)) + + ;; Zag-Zag rotation + ((and (eq parent (phpinspect-splayt-node-right grandparent)) + (eq node (phpinspect-splayt-node-right parent))) + (phpinspect-splayt-node-rotate-left grandparent (car parents) splayt) + (phpinspect-splayt-node-rotate-left parent (car parents) splayt)) + + ;; Zig-Zag rotation + ((and (eq parent (phpinspect-splayt-node-right grandparent)) + (eq node (phpinspect-splayt-node-left parent))) + (phpinspect-splayt-node-rotate-right parent grandparent splayt) + (phpinspect-splayt-node-rotate-left grandparent (car parents) splayt)) + + ;; Zag-Zig rotation + ((and (eq parent (phpinspect-splayt-node-left grandparent)) + (eq node (phpinspect-splayt-node-right parent))) + (phpinspect-splayt-node-rotate-left parent grandparent splayt) + (phpinspect-splayt-node-rotate-right grandparent (car parents) splayt)) + (t + (error "Failed in determining rotation strategy"))) + ;; Else + (if (eq node (phpinspect-splayt-node-left parent)) + (phpinspect-splayt-node-rotate-right parent (car parents) splayt) + (phpinspect-splayt-node-rotate-left parent (car parents) splayt)))))) + +(defsubst phpinspect-splayt-insert-node (splayt node) + (if (not (phpinspect-splayt-root-node splayt)) + (setf (phpinspect-splayt-root-node splayt) node) + + ;; Else + (let ((nav (phpinspect-make-splayt-nav splayt))) + (catch 'break + (while t + (if (< (phpinspect-splayt-node-key node) + (phpinspect-splayt-nav-current-key nav)) + (if (phpinspect-splayt-nav-has-left-p nav) + (phpinspect-splayt-nav-left nav) + (setf (phpinspect-splayt-node-left (phpinspect-splayt-nav-current nav)) + node) + (throw 'break nil)) + + ;; Else + (if (phpinspect-splayt-nav-has-right-p nav) + (phpinspect-splayt-nav-right nav) + (setf (phpinspect-splayt-node-right (phpinspect-splayt-nav-current nav)) + node) + (throw 'break nil)))))))) + +(defmacro phpinspect-splayt-traverse (place-and-splayt &rest body) + "Traverse splay tree in cadr of PLACE-AND-SPLAYT, executing BODY. + +The car of PLACE-AND-SPLAYT is assigned the value of each node. + +Traversal is breadth-first to take advantage of the splay trees +main benefit: the most accessed interval of keys is likely to be +near the top of the tee." + (declare (indent 1)) + (let ((place (car place-and-splayt)) + (current-sym (gensym)) + (splayt-sym (gensym)) + (stack-sym (gensym)) + (queue-sym (gensym)) + (reverse-sym (gensym)) + (size-sym (gensym))) + `(let* ((,splayt-sym ,(cadr place-and-splayt)) + ;; Make place locally scoped variable if a symbol + (,queue-sym (list (phpinspect-splayt-root-node ,splayt-sym))) + (,reverse-sym t) + ,size-sym + ,stack-sym + ,(if (symbolp place) place (gensym))) + + (while ,queue-sym + (setq ,size-sym (length ,queue-sym)) + + (while (> ,size-sym 0) + (setq ,current-sym (car (last ,queue-sym)) + ,queue-sym (butlast ,queue-sym)) + + (if ,reverse-sym + (push ,current-sym ,stack-sym) + (setf ,place (phpinspect-splayt-node-value ,current-sym)) + ,@body) + + (when (phpinspect-splayt-node-right ,current-sym) + (setq ,queue-sym (nconc ,queue-sym (list (phpinspect-splayt-node-right ,current-sym))))) + + (when (phpinspect-splayt-node-left ,current-sym) + (setq ,queue-sym (nconc ,queue-sym (list (phpinspect-splayt-node-left ,current-sym))))) + + (setq ,size-sym (- ,size-sym 1))) + + (when ,reverse-sym + (while ,stack-sym + (setq ,current-sym (pop ,stack-sym)) + (setf ,place (phpinspect-splayt-node-value ,current-sym)) + ,@body)) + + (setq ,reverse-sym (not ,reverse-sym)))))) + +(defsubst phpinspect-splayt-find (splayt key &optional navigator matcher continue-predicate) + (unless navigator (setq navigator #'<)) + (unless matcher (setq matcher #'=)) + + (let ((nav (phpinspect-make-splayt-nav splayt)) + current next) + (when (phpinspect-splayt-nav-current nav) + (catch 'found + (while t + (setq current (phpinspect-splayt-nav-current nav) + next nil) + (cond + ((funcall navigator key (phpinspect-splayt-nav-current-key nav)) + (when (phpinspect-splayt-nav-has-left-p nav) + (phpinspect-splayt-nav-left nav) + (setq next (phpinspect-splayt-nav-current nav)))) + (t + (when (phpinspect-splayt-nav-has-right-p nav) + (phpinspect-splayt-nav-right nav) + (setq next (phpinspect-splayt-nav-current nav))))) + + (if (funcall matcher key (phpinspect-splayt-node-key current)) + (when (or (not next) + (not continue-predicate) + (not (funcall continue-predicate key (phpinspect-splayt-node-key next)))) + (phpinspect-splay + splayt current + (if next (cdr (phpinspect-splayt-nav-parents nav)) (phpinspect-splayt-nav-parents nav))) + (throw 'found (phpinspect-splayt-node-value current))) + (unless next + (throw 'found nil)))))))) + +(provide 'phpinspect-splayt) diff --git a/test/phpinspect-test.el b/test/phpinspect-test.el index 426838883c..53fc8be8e8 100644 --- a/test/phpinspect-test.el +++ b/test/phpinspect-test.el @@ -610,6 +610,7 @@ class Thing (load-file (concat phpinspect-test-directory "/test-bmap.el")) (load-file (concat phpinspect-test-directory "/test-edtrack.el")) (load-file (concat phpinspect-test-directory "/test-resolvecontext.el")) +(load-file (concat phpinspect-test-directory "/test-splayt.el")) (provide 'phpinspect-test) ;;; phpinspect-test.el ends here diff --git a/test/test-splayt.el b/test/test-splayt.el new file mode 100644 index 0000000000..c399f7c1c5 --- /dev/null +++ b/test/test-splayt.el @@ -0,0 +1,86 @@ +;; test-splayt.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 Free Software Foundation, Inc. + +;; Author: Hugo Thunnissen <de...@hugot.nl> + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; + +;;; Code: + +(require 'phpinspect-splayt) + +(ert-deftest phpinspect-splayt-node-rotate () + (let* ((one (phpinspect-make-splayt-node 1 "one")) + (node (phpinspect-make-splayt-node + 3 "three" + one + (phpinspect-make-splayt-node 4 "four")))) + (phpinspect-splayt-node-rotate-right node) + (should (equal (phpinspect-make-splayt-node + 1 "one" nil + (phpinspect-make-splayt-node + 3 "three" nil (phpinspect-make-splayt-node 4 "four"))) + one)) + + (phpinspect-splayt-node-rotate-left node one) + + (should (equal (phpinspect-make-splayt-node + 1 "one" nil + (phpinspect-make-splayt-node + 4 "four" (phpinspect-make-splayt-node 3 "three"))) + one)))) + +(ert-deftest phpinspect-splayt () + (let ((tree (phpinspect-make-splayt))) + (phpinspect-splayt-insert tree 9 "nine") + (phpinspect-splayt-insert tree 3 "three") + (phpinspect-splayt-insert tree 11 "eleven") + (phpinspect-splayt-insert tree 8 "eight") + (phpinspect-splayt-insert tree 12 "twelve") + (phpinspect-splayt-insert tree 4 "four") + (phpinspect-splayt-insert tree 1 "one") + + + (should (string= "eight" (phpinspect-splayt-find tree 8))) + (should (string= "one" (phpinspect-splayt-find tree 1))) + (should (string= "three" (phpinspect-splayt-find tree 3))) + (should (string= "nine" (phpinspect-splayt-find tree 9))) + (should (string= "four" (phpinspect-splayt-find tree 4))) + (should (string= "twelve" (phpinspect-splayt-find tree 12))) + (should (string= "eleven" (phpinspect-splayt-find tree 11))))) + +(ert-deftest phpinspect-splayt-traverse () + (let ((tree (phpinspect-make-splayt))) + (phpinspect-splayt-insert tree 9 "nine") + (phpinspect-splayt-insert tree 3 "three") + (phpinspect-splayt-insert tree 11 "eleven") + (phpinspect-splayt-insert tree 8 "eight") + (phpinspect-splayt-insert tree 12 "twelve") + (phpinspect-splayt-insert tree 4 "four") + (phpinspect-splayt-insert tree 1 "one") + + (let ((expected (sort '("nine" "three" "eleven" "eight" "twelve" "four" "one") #'string-lessp)) + (result)) + + (phpinspect-splayt-traverse (item tree) + (push item result)) + + (setq result (sort result #'string-lessp)) + + (should (equal expected result)))))