perf(replay): optimize screenshot mode — 93% faster (14.7x)#536
Draft
marandaneto wants to merge 1 commit intomainfrom
Draft
perf(replay): optimize screenshot mode — 93% faster (14.7x)#536marandaneto wants to merge 1 commit intomainfrom
marandaneto wants to merge 1 commit intomainfrom
Conversation
Main-thread time for screenshot snapshot reduced from 3,319µs to ~230µs on a 200-view hierarchy (484 actual views including UIKit internals). Key optimizations: - Fast path for plain UIView/UIWindow containers (skip type casts) - Custom CGContext renderer (PostHogGraphicsImageRenderer) based on Sentry's approach - Native screen scale for drawHierarchy (avoids internal rescaling) - Cache config flags per snapshot via ReplayMaskConfig struct - Hex lookup table for toRGBString (avoid String(format:)) - Skip maskImage re-render when no maskableWidgets - Early image release in toDict to reduce peak memory - Filter touch phases before capturing location - Move lock + meta event creation off main thread - malloc instead of calloc for renderer buffer
Member
Author
|
@ioannisj autoresearch result, we'd need to test everything but the results are good |
Contributor
posthog-ios Compliance ReportDate: 2026-03-25 16:59:56 UTC ✅ All Tests Passed!0/0 tests passed |
Contributor
Oh wow! Will take a look and test. Looks promising from PR description |
6 tasks
KristijanKocev
added a commit
to KristijanKocev/posthog-ios
that referenced
this pull request
Mar 29, 2026
…mentation - switch replay screenshot triggering to a CADisplayLink-driven cadence, using throttleDelay only as a minimum spacing guard - add single-flight snapshot scheduling with one pending follow-up capture - bring in the custom CGContext-based screenshot renderer from PR PostHog#536 and use native screen scale for drawHierarchy captures - add session replay performance instrumentation for main-thread capture, masking, serialization, and background processing - optimize screenshot serialization helpers by avoiding unnecessary masked image work, caching replay mask config, and reducing color formatting overhead - extend the view layout publisher with CALayer-based change monitoring and suppression utilities to avoid self-triggered replay churn during capture - simplify touch replay handling to only emit began/ended touch events - wire the new replay performance tracker into the Xcode project
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
💡 Motivation and Context
Relates to #321
Session replay's screenshot capture was consuming 3,319µs per snapshot on the main thread — nearly 20% of a 60fps frame budget. Users on older devices reported frame drops and jank caused by the view hierarchy traversal and image rendering.
This PR reduces main-thread time to ~230µs (93.2% reduction, 14.7× faster), bringing frame budget impact down to 1.4% at 60fps — negligible.
Performance Results
Key Optimizations
Main-thread performance:
PostHogGraphicsImageRenderer) — Based on Sentry's approach. BypassesUIGraphicsImageRendereroverhead by directly allocating a CGContext withmalloc. Uses native screen scale which avoids internal rescaling indrawHierarchy.ReplayMaskConfig) — CachemaskAllTextInputs,maskAllImages,maskAllSandboxedViewsonce per snapshot instead of traversing weak ref + optional chain per view.findMaskableWidgetsandtoWireframe.location(in:)for non-began/ended phases (avoids coordinate conversion during drag/scroll).windowViewsLockaccess and meta event dict creation moved to dispatch queue.Off-main-thread + memory:
maskImagere-render whenmaskableWidgetsis empty (saves ~1.3MB)UIImageearly intoDict()before base64 encoding (reduces peak memory)toDict()(minimumCapacity: 16)toRGBString()— replacesString(format:)with direct char arraymallocinstead ofcallocfor renderer buffer (skip zero-init of ~12MB at 3x scale)Files Changed
PostHogReplayIntegration.swift— Fast paths, config caching, if-else chains, touch handler, async refactorPostHogGraphicsImageRenderer.swift— NEW custom CGContext rendererUIView+Util.swift— Custom renderer integration,isNoCapture()optimizationCGColor+Util.swift— Hex lookup table for color string formattingRRWireframe.swift—maskImageskip, early image release, dict pre-sizingRRStyle.swift— Dict pre-sizing💚 How did you test it?
make buildIOSandswift buildboth passmake formatclean (SwiftLint + SwiftFormat)📝 Checklist