Skip to content

perf(ios): cache fontScale to avoid dispatch_sync on every measurement#495

Open
hryhoriiK97 wants to merge 4 commits into
mainfrom
perf/ios-fontscale-cache
Open

perf(ios): cache fontScale to avoid dispatch_sync on every measurement#495
hryhoriiK97 wants to merge 4 commits into
mainfrom
perf/ios-fontscale-cache

Conversation

@hryhoriiK97

Copy link
Copy Markdown
Collaborator

Summary

  • Replaces per-measurement dispatch_sync to main thread for reading RCTFontSizeMultiplier() with a cached std::atomic<double>
  • First read still uses dispatch_sync; all subsequent measurements read the atomic directly — no main-thread hop
  • Registers a UIContentSizeCategoryDidChangeNotification observer to update the cache when the user changes Dynamic Type font size

Test plan

  • Verify measurement results are correct with default font scale
  • Change Dynamic Type size in Settings > Accessibility > Display & Text Size, verify markdown re-measures correctly
  • Profile a scrolling feed with many markdown cells — confirm no dispatch_sync to main in ENRMFontScaleForMeasurement after first call

Made with Cursor

ENRMFontScaleForMeasurement previously called dispatch_sync to the main
thread on every Yoga measurement to read RCTFontSizeMultiplier(). In a
scrolling feed with many markdown cells, this added one synchronous
main-thread hop per cell per layout pass.

Cache the value in a std::atomic<double> after the first read and update
it via UIContentSizeCategoryDidChangeNotification. Subsequent
measurements read the atomic directly with no thread synchronization.

Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes iOS shadow-tree measurement by avoiding a main-thread dispatch_sync on every markdown measurement when retrieving the React Native font scale multiplier.

Changes:

  • Adds a cached font-scale value backed by std::atomic<double> and lazy initialization via std::call_once.
  • Registers a Dynamic Type (UIContentSizeCategoryDidChangeNotification) observer to refresh the cached value when the user changes content size category.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +25 to +26
static std::atomic<double> cachedScale{0.0};
static std::once_flag flag;
Comment on lines 6 to 9
#include <algorithm>
#include <atomic>
#include <cmath>
#include <react/renderer/core/LayoutConstraints.h>
hryhoriiK97 and others added 3 commits July 3, 2026 14:00
…RGET_OS_OSX

Co-authored-by: Cursor <cursoragent@cursor.com>
static inline gives each translation unit its own cachedScale and
once_flag, causing duplicate dispatch_sync calls and notification
observers. Plain inline (C++17) shares a single definition and static
locals across all TUs.

Co-authored-by: Cursor <cursoragent@cursor.com>
std::once_flag/std::call_once require <mutex>, and the #if !TARGET_OS_OSX
guard requires <TargetConditionals.h> to be explicitly included rather
than relying on transitive includes.

Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

} else {
dispatch_sync(dispatch_get_main_queue(), readFontScale);
}
std::call_once(flag, [] {
Comment on lines +41 to +51
#if !TARGET_OS_OSX
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter]
addObserverForName:UIContentSizeCategoryDidChangeNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *) {
cachedScale.store(RCTFontSizeMultiplier(), std::memory_order_relaxed);
}];
});
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants