patches: fix O(position) performance via UAZoomEnabled caching

Root cause (per Opus analysis): UAZoomEnabled() is a synchronous
Mach IPC roundtrip to macOS Accessibility server, called 3x per
redisplay cycle. At 60fps = 180 IPC roundtrips/second blocking the
main thread. Combined with Emacs's inherent O(position) redisplay
cost, this compounded into progressive choppy behavior.

Fix 1: ns_zoom_enabled_p() caches UAZoomEnabled() for 1 second.
Fix 2: ns_zoom_track_completion() rate-limited to 2 Hz.

Also includes BUF_CHARS_MODIFF fix (patch 0009) for VoiceOver cache.
This commit is contained in:
2026-03-01 05:23:59 +01:00
parent cd16d45584
commit 6b3843e0c6
11 changed files with 137 additions and 20 deletions

View File

@@ -1,7 +1,7 @@
From b38d702cb19f2b7c36d88d7e397323ea1aca1c9b Mon Sep 17 00:00:00 2001
From 8cecc655267f3adc31230a76d0d470159adc9d87 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 22:39:35 +0100
Subject: [PATCH 01/10] ns: integrate with macOS Zoom for cursor tracking
Subject: [PATCH 01/11] ns: integrate with macOS Zoom for cursor tracking
Inform macOS Zoom of the text cursor position so the zoomed viewport
follows keyboard focus in Emacs.

View File

@@ -1,7 +1,7 @@
From e6800d12d350def06dd6475fcb807ceaf7f82e02 Mon Sep 17 00:00:00 2001
From a750bf8ae17dd0dd6c14a23376d012c61367227f Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 02/10] ns: add accessibility base classes and text extraction
Subject: [PATCH 02/11] ns: add accessibility base classes and text extraction
Add the foundation for macOS VoiceOver accessibility in the NS
(Cocoa) port. No existing code paths are modified.

View File

@@ -1,7 +1,7 @@
From d3dd16835ff6b7456f987893ef610e3847a92fde Mon Sep 17 00:00:00 2001
From cd8512d541e63f044086d1dfd2fe44069d4c09ca Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 03/10] ns: implement buffer accessibility element (core
Subject: [PATCH 03/11] ns: implement buffer accessibility element (core
protocol)
Implement the NSAccessibility text protocol for Emacs buffer windows.

View File

@@ -1,7 +1,7 @@
From 601df3982e20e60f041fa2658aa7ef1efb69939b Mon Sep 17 00:00:00 2001
From 2cb651607c84eb100729db5d4c113d7fcf4e1048 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 04/10] ns: add buffer notification dispatch and mode-line
Subject: [PATCH 04/11] ns: add buffer notification dispatch and mode-line
element
Add VoiceOver notification methods and mode-line readout.

View File

@@ -1,7 +1,7 @@
From 8c67a2b5a89ac302cbac91790fdfb6827b74285a Mon Sep 17 00:00:00 2001
From e04d6ef8808c1e00b37fca85e6ac0a7f6104bdb7 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 05/10] ns: add interactive span elements for Tab navigation
Subject: [PATCH 05/11] ns: add interactive span elements for Tab navigation
* src/nsterm.m (ns_ax_scan_interactive_spans): New function.
(EmacsAccessibilityInteractiveSpan): Implement AXButton/AXLink

View File

@@ -1,7 +1,7 @@
From 3237374282389cd61bcd99beed187ec75d1b06fc Mon Sep 17 00:00:00 2001
From 88bcf29875755897a3d3d4cef04e2c0044ac8b9d Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 06/10] ns: integrate accessibility with EmacsView and
Subject: [PATCH 06/11] ns: integrate accessibility with EmacsView and
redisplay
Wire the accessibility infrastructure into EmacsView and the

View File

@@ -1,7 +1,7 @@
From e2b76f1850489e8188236bed10e4d7f28e5cde2b Mon Sep 17 00:00:00 2001
From 8b7858f8e43e6bd7abb58ca2da0883341b9c446a Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100
Subject: [PATCH 07/10] doc: add VoiceOver accessibility section to macOS
Subject: [PATCH 07/11] doc: add VoiceOver accessibility section to macOS
appendix
* doc/emacs/macos.texi (VoiceOver Accessibility): New node. Document

View File

@@ -1,7 +1,7 @@
From 3e868d0234c858fa20588e664354685ef8b08576 Mon Sep 17 00:00:00 2001
From 314e2a5f406a8eb0e6909be6e07fe7e8ddd1c440 Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 14:46:25 +0100
Subject: [PATCH 08/10] ns: announce overlay completion candidates for
Subject: [PATCH 08/11] ns: announce overlay completion candidates for
VoiceOver
Completion frameworks such as Vertico, Ivy, and Icomplete render

View File

@@ -1,7 +1,7 @@
From 5aba3491f8f5268f2e6093003b79fe69e7932a4b Mon Sep 17 00:00:00 2001
From a75ee6aff3734bd053865b3df734b75642f7305e Mon Sep 17 00:00:00 2001
From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 16:01:29 +0100
Subject: [PATCH 09/10] ns: announce child frame completion candidates for
Subject: [PATCH 09/11] ns: announce child frame completion candidates for
VoiceOver
Completion frameworks such as Corfu, Company-box, and similar

View File

@@ -1,7 +1,7 @@
From 4b739781a019fd1ad5b6ac1c9c12dc62e2c82ec3 Mon Sep 17 00:00:00 2001
From 1d67b993dc9072f36a7742fc3d284b7bd935a84d Mon Sep 17 00:00:00 2001
From: Daneel <daneel@sukany.cz>
Date: Sun, 1 Mar 2026 04:56:16 +0100
Subject: [PATCH 10/10] perf: use BUF_CHARS_MODIFF for AX text cache validity
Subject: [PATCH 10/11] perf: use BUF_CHARS_MODIFF for AX text cache validity
in ensureTextCache
The cache validity check in -[EmacsAccessibilityBuffer ensureTextCache]

View File

@@ -0,0 +1,117 @@
From 9e2b56b7aec560540ef9f49c9444c2c0fc903090 Mon Sep 17 00:00:00 2001
From: Daneel <daneel@sukany.cz>
Date: Sun, 1 Mar 2026 05:23:37 +0100
Subject: [PATCH 11/11] perf: cache UAZoomEnabled() and rate-limit completion
tracking
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
UAZoomEnabled() performs a synchronous Mach IPC roundtrip to the macOS
Accessibility server (~50-200 µs per call). With three call sites in
the redisplay hot path (ns_draw_window_cursor, ns_update_end fallback,
ns_zoom_track_completion) this adds up to 150-600 µs of IPC overhead
per redisplay cycle. At 60 fps, that is 9-36 ms/s of pure IPC cost.
The overhead blocks the main thread, extending the inter-frame interval
and making cursor movement feel choppy, especially at the end of large
files where Emacs's own redisplay work is already at its maximum.
* src/nsterm.m (ns_zoom_cached_enabled, ns_zoom_cache_time): New
static variables.
(ns_zoom_enabled_p): New inline wrapper around UAZoomEnabled().
Caches the result for 1 second using CFAbsoluteTimeGetCurrent(), a
near-free VDSO call. Zoom state changes only on explicit user action
in System Settings, so a 1-second TTL is indistinguishable from
querying on every frame. Replaces all three UAZoomEnabled() call
sites.
(ns_zoom_track_completion): Add 500 ms rate limit. Completion
candidate detection requires FOR_EACH_FRAME iteration and
Fget_char_property calls. Completion state changes at human
interaction speed (well below 2 Hz), so 500 ms polling is
imperceptible while eliminating per-redisplay Lisp overhead.
---
src/nsterm.m | 43 ++++++++++++++++++++++++++++++++++++++++---
1 file changed, 40 insertions(+), 3 deletions(-)
diff --git a/src/nsterm.m b/src/nsterm.m
index e3f9466..590287a 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -1092,6 +1092,31 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
ivy-current-match, corfu-current that mark the selected candidate. */
static bool ns_ax_face_is_selected (Lisp_Object face);
+/* Cached wrapper around UAZoomEnabled().
+ UAZoomEnabled() performs a synchronous Mach IPC roundtrip to the
+ macOS Accessibility server (~50-200 µs per call). With three call
+ sites in the redisplay hot path the overhead accumulates to
+ 150-600 µs per frame. Zoom state only changes on explicit user
+ action in System Settings, so a 1-second TTL is both safe and
+ indistinguishable from querying on every frame.
+
+ Uses CFAbsoluteTimeGetCurrent() (a VDSO read on modern macOS, ~5 ns)
+ to avoid a second IPC roundtrip for time. */
+static BOOL ns_zoom_cached_enabled;
+static CFAbsoluteTime ns_zoom_cache_time;
+
+static BOOL
+ns_zoom_enabled_p (void)
+{
+ CFAbsoluteTime now = CFAbsoluteTimeGetCurrent ();
+ if (now - ns_zoom_cache_time > 1.0)
+ {
+ ns_zoom_cached_enabled = UAZoomEnabled ();
+ ns_zoom_cache_time = now;
+ }
+ return ns_zoom_cached_enabled;
+}
+
/* Scan overlay before-string / after-string properties in the
selected window for a completion candidate with a "selected"
face. Return the 0-based visual line index of the selected
@@ -1215,11 +1240,23 @@ If a completion candidate is selected (overlay or child frame),
static void
ns_zoom_track_completion (struct frame *f, EmacsView *view)
{
- if (!UAZoomEnabled ())
+ if (!ns_zoom_enabled_p ())
return;
if (!WINDOWP (f->selected_window))
return;
+ /* Rate-limit completion scan to 2 Hz. Completion overlays and
+ child frames change at human interaction speed (<<10 Hz), so
+ checking every 500 ms is indistinguishable from every frame
+ while eliminating per-redisplay FOR_EACH_FRAME iteration. */
+ {
+ static CFAbsoluteTime last_completion_track;
+ CFAbsoluteTime now = CFAbsoluteTimeGetCurrent ();
+ if (now - last_completion_track < 0.5)
+ return;
+ last_completion_track = now;
+ }
+
specpdl_ref count = SPECPDL_INDEX ();
record_unwind_current_buffer ();
@@ -1323,7 +1360,7 @@ so the visual offset is (ov_line + 1) * line_h from
(zoomCursorUpdated is NO). */
#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
&& MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
- if (view && !view->zoomCursorUpdated && UAZoomEnabled ()
+ if (view && !view->zoomCursorUpdated && ns_zoom_enabled_p ()
&& !NSIsEmptyRect (view->lastCursorRect))
{
NSRect r = view->lastCursorRect;
@@ -3500,7 +3537,7 @@ EmacsView pixels (AppKit, flipped, top-left origin)
#if defined (MAC_OS_X_VERSION_MIN_REQUIRED) \
&& MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
- if (UAZoomEnabled ())
+ if (ns_zoom_enabled_p ())
{
NSRect windowRect = [view convertRect:r toView:nil];
NSRect screenRect
--
2.43.0