perf: defer GC across interactive viewport rerenders

This commit is contained in:
Kinneyzhang 2026-08-25 17:42:01 +08:00
parent c591a9fcf4
commit b25328198c
3 changed files with 121 additions and 17 deletions

31
ebox.el
View File

@ -4626,18 +4626,25 @@ When VIEWPORT-HEIGHT is non-nil, height values using `(viewport-height)' are
resolved against that line count.
This preserves node and region identity and routes viewport-dependent changes
through dirty-set and patch-set execution before falling back to root rerender."
(let* ((state (ebox--buffer-render-state buffer))
(old-viewport-width (and state
(plist-get state :viewport-width))))
(unless (ebox-surface-buffer-mounted-p buffer)
(user-error "Ebox buffer has no mounted TP surface: %S" buffer))
(ebox--cancel-buffer-runtime-prewarm buffer)
(prog1
(ebox-surface-update-buffer-viewport
buffer viewport-width viewport-height)
(ebox--schedule-buffer-runtime-prewarm buffer nil t)
(ebox--schedule-buffer-reflow-cache-prewarm
buffer old-viewport-width viewport-width))))
(let ((execute
(lambda ()
(let* ((state (ebox--buffer-render-state buffer))
(old-viewport-width
(and state (plist-get state :viewport-width))))
(unless (ebox-surface-buffer-mounted-p buffer)
(user-error "Ebox buffer has no mounted TP surface: %S" buffer))
(ebox--cancel-buffer-runtime-prewarm buffer)
(prog1
(ebox-surface-update-buffer-viewport
buffer viewport-width viewport-height)
(ebox--schedule-buffer-runtime-prewarm buffer nil t)
(ebox--schedule-buffer-reflow-cache-prewarm
buffer old-viewport-width viewport-width))))))
(if noninteractive
(ebox--with-render-gc
(funcall execute))
(ebox--with-deferred-render-gc
(funcall execute)))))
(defun ebox--rerender-buffer-preserving-runtime (buffer)
"Rerender BUFFER from stored runtime state without rebuilding identity."

View File

@ -3277,6 +3277,103 @@
(when (and buffer (buffer-live-p buffer))
(kill-buffer buffer)))))
(ert-deftest ebox-interactive-viewport-rerender-owns-deferred-render-gc ()
"Keep GC outside an interactive viewport transaction until it settles."
(ebox-test--reset-runtime-state)
(let ((ebox-runtime-idle-prewarm nil)
(ebox-runtime-idle-reflow-cache-prewarm nil)
(buffer nil))
(unwind-protect
(progn
(setq buffer
(ebox-render-to-buffer
(generate-new-buffer-name " *ebox-test*")
(ebox-create :width '(viewport) :content "resize")))
(let ((noninteractive nil)
(gc-cons-threshold 1000)
(gc-cons-percentage 0.1)
(ebox-deferred-render-gc-cons-threshold 4000)
(ebox-render-gc-cons-percentage 0.8)
(ebox--deferred-render-gc-state nil)
(ebox--deferred-render-gc-timer nil)
(ebox--deferred-render-gc-depth 0)
(original-update
(symbol-function 'ebox-surface-update-buffer-viewport))
observed-threshold
observed-depth
scheduled
(gc-count 0))
(cl-letf (((symbol-function 'ebox-surface-update-buffer-viewport)
(lambda (&rest arguments)
(setq observed-threshold gc-cons-threshold
observed-depth ebox--deferred-render-gc-depth)
(apply original-update arguments)))
((symbol-function 'run-at-time)
(lambda (delay _repeat function &rest arguments)
(setq scheduled (list delay function arguments))
'ebox-test-viewport-gc-timer))
((symbol-function 'timerp)
(lambda (value)
(eq value 'ebox-test-viewport-gc-timer)))
((symbol-function 'cancel-timer) #'ignore)
((symbol-function 'garbage-collect)
(lambda () (cl-incf gc-count))))
(ebox-rerender-buffer-with-context buffer 320)
(should (= observed-threshold 4000))
(should (= observed-depth 1))
(should (= (plist-get (ebox--buffer-render-state buffer)
:viewport-width)
320))
(should (= ebox--deferred-render-gc-depth 0))
(should ebox--deferred-render-gc-state)
(should (eq ebox--deferred-render-gc-timer
'ebox-test-viewport-gc-timer))
(should (= (car scheduled) 0.2))
(apply (nth 1 scheduled) (nth 2 scheduled))
(should (= gc-cons-threshold 1000))
(should (= gc-cons-percentage 0.1))
(should (= gc-count 1)))))
(ebox-test--reset-runtime-state)
(when (and buffer (buffer-live-p buffer))
(kill-buffer buffer)))))
(ert-deftest ebox-batch-viewport-rerender-uses-render-gc-boundary ()
"Raise GC only during a batch viewport transaction and restore on return."
(ebox-test--reset-runtime-state)
(let ((ebox-runtime-idle-prewarm nil)
(ebox-runtime-idle-reflow-cache-prewarm nil)
(buffer nil))
(unwind-protect
(progn
(setq buffer
(ebox-render-to-buffer
(generate-new-buffer-name " *ebox-test*")
(ebox-create :width '(viewport) :content "resize")))
(let ((noninteractive t)
(gc-cons-threshold 1000)
(gc-cons-percentage 0.1)
(ebox-render-gc-cons-threshold 4000)
(ebox-render-gc-cons-percentage 0.8)
(original-update
(symbol-function 'ebox-surface-update-buffer-viewport))
observed-threshold
observed-percentage)
(cl-letf (((symbol-function 'ebox-surface-update-buffer-viewport)
(lambda (&rest arguments)
(setq observed-threshold gc-cons-threshold
observed-percentage gc-cons-percentage)
(apply original-update arguments))))
(ebox-rerender-buffer-with-context buffer 320))
(should (= observed-threshold 4000))
(should (= observed-percentage 0.8))
(should (= gc-cons-threshold 1000))
(should (= gc-cons-percentage 0.1))
(should-not ebox--deferred-render-gc-state)
(should-not ebox--deferred-render-gc-timer)))
(ebox-test--reset-runtime-state)
(when (and buffer (buffer-live-p buffer))
(kill-buffer buffer)))))
(ert-deftest ebox-deferred-render-gc-can-raise-an-active-burst-budget ()
"Measured layout pressure should only raise an active burst budget."
(let ((gc-cons-threshold 1000)

View File

@ -59,7 +59,7 @@
(lambda (_window)
(error "Window outer width must not be sampled")))
((symbol-function 'window-body-height)
(lambda (_window) 31)))
(lambda (_window &optional _pixelwise) 31)))
(let ((values (ebox-surface--context-values buffer nil nil)))
(should (= 775 (plist-get values :viewport-width)))
(should (= 31 (plist-get values :viewport-height)))))
@ -92,7 +92,7 @@
((symbol-function 'window-pixel-width)
(lambda (_window) 1430))
((symbol-function 'window-body-height)
(lambda (_window) 62)))
(lambda (_window &optional _pixelwise) 62)))
(let ((values (ebox-surface--context-values buffer nil nil)))
(should (= 1428 (plist-get values :viewport-width)))
(should (= 62 (plist-get values :viewport-height)))))
@ -130,7 +130,7 @@
(should pixelwise)
1400))
((symbol-function 'window-body-height)
(lambda (candidate)
(lambda (candidate &optional _pixelwise)
(should (eq candidate window))
60)))
(let ((values (ebox-surface--context-values buffer nil nil)))
@ -163,7 +163,7 @@
(should pixelwise)
(if (eq window 'current-frame-window) 901 333)))
((symbol-function 'window-body-height)
(lambda (_window) 31)))
(lambda (_window &optional _pixelwise) 31)))
(let ((values (ebox-surface--context-values buffer nil nil)))
(should (= 899 (plist-get values :viewport-width)))
(should (= 31 (plist-get values :viewport-height)))))
@ -188,7 +188,7 @@
((symbol-function 'ebox-surface--window-content-width)
(lambda (_window) 240))
((symbol-function 'window-body-height)
(lambda (_window) 20))
(lambda (_window &optional _pixelwise) 20))
((symbol-function 'ebox-rerender-buffer-with-context)
(lambda (target width height)
(setq calls (list target width height))))