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> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 22:39:35 +0100 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 Inform macOS Zoom of the text cursor position so the zoomed viewport
follows keyboard focus in Emacs. 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> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100 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 Add the foundation for macOS VoiceOver accessibility in the NS
(Cocoa) port. No existing code paths are modified. (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> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100 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) protocol)
Implement the NSAccessibility text protocol for Emacs buffer windows. 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> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100 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 element
Add VoiceOver notification methods and mode-line readout. 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> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100 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. * src/nsterm.m (ns_ax_scan_interactive_spans): New function.
(EmacsAccessibilityInteractiveSpan): Implement AXButton/AXLink (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> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100 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 redisplay
Wire the accessibility infrastructure into EmacsView and the 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> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 12:58:11 +0100 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 appendix
* doc/emacs/macos.texi (VoiceOver Accessibility): New node. Document * 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> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 14:46:25 +0100 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 VoiceOver
Completion frameworks such as Vertico, Ivy, and Icomplete render 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> From: Martin Sukany <martin@sukany.cz>
Date: Sat, 28 Feb 2026 16:01:29 +0100 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 VoiceOver
Completion frameworks such as Corfu, Company-box, and similar 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> From: Daneel <daneel@sukany.cz>
Date: Sun, 1 Mar 2026 04:56:16 +0100 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 in ensureTextCache
The cache validity check in -[EmacsAccessibilityBuffer 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