perf(android): Parse app start profiling config without JsonSerializer - #5867
Draft
runningcode wants to merge 2 commits into
Draft
perf(android): Parse app start profiling config without JsonSerializer#5867runningcode wants to merge 2 commits into
runningcode wants to merge 2 commits into
Conversation
…r (JAVA-621) SentryPerformanceProvider.launchAppStartProfiler ran in ContentProvider.onCreate — main thread, before Application.onCreate, on every cold start — and built a full JsonSerializer(SentryOptions.empty()) to read one small config file. That path only ever reads options.getLogger(), but JsonSerializer's constructor registers 56 deserializers to use exactly one of them. Calling the deserializer directly cuts the parse from 221 to 33 allocations and ~7.5us to ~3.7us (Pixel 10, androidx Microbenchmark, allocationCount is deterministic). Malformed input still yields null so callers keep reporting it as a deserialization failure rather than a read error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
📲 Install BuildsAndroid
|
Contributor
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 6b019b7 | 319.84 ms | 333.15 ms | 13.31 ms |
| c3ee041 | 310.64 ms | 361.90 ms | 51.26 ms |
| d501a7e | 348.06 ms | 431.42 ms | 83.36 ms |
| 3998a95 | 415.94 ms | 478.54 ms | 62.60 ms |
| ed33deb | 343.30 ms | 362.41 ms | 19.10 ms |
| 5b1a06b | 310.56 ms | 362.79 ms | 52.22 ms |
| 91bb874 | 310.68 ms | 359.24 ms | 48.56 ms |
| 0ee65e9 | 317.37 ms | 366.50 ms | 49.13 ms |
| 6edfca2 | 305.52 ms | 432.78 ms | 127.26 ms |
| 5e269de | 292.83 ms | 379.12 ms | 86.29 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 6b019b7 | 0 B | 0 B | 0 B |
| c3ee041 | 0 B | 0 B | 0 B |
| d501a7e | 0 B | 0 B | 0 B |
| 3998a95 | 1.58 MiB | 2.10 MiB | 532.96 KiB |
| ed33deb | 1.58 MiB | 2.13 MiB | 559.52 KiB |
| 5b1a06b | 0 B | 0 B | 0 B |
| 91bb874 | 1.58 MiB | 2.13 MiB | 559.07 KiB |
| 0ee65e9 | 0 B | 0 B | 0 B |
| 6edfca2 | 1.58 MiB | 2.13 MiB | 559.07 KiB |
| 5e269de | 0 B | 0 B | 0 B |
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.
📜 Description
SentryPerformanceProvider.launchAppStartProfilerparses the app start profiling config inContentProvider.onCreate— main thread, beforeApplication.onCreate, on every cold start. It did so via:JsonSerializer.deserialize(Reader, Class)only ever readsoptions.getLogger()on this path, but its constructor builds aHashMapand allocates 56 deserializer instances to use exactly one of them — andSentryOptions.empty()is itself 63 allocations.This replaces both with the one deserializer the path needs, which is already the established idiom in ~20 places in this codebase (
SpanContext,Contexts,SentryBaseEvent, …). Malformed input still returnsnulland logs"Error when deserializing", so callers keep reporting it as a deserialization failure rather than a read error.Measured on a Pixel 10 (API 37) with androidx Microbenchmark, release build:
JsonSerializer(before)Deserializer(after)allocationCountis deterministic, so the allocation figures are exact. The timings are steady-state (JIT-warmed, clocks unlocked), so they understate the real cold-start saving — warmup amortizes away the class loading and verification of those 56 deserializer classes, which the real one-shot path pays in full. No claim is made about measurable end-to-end startup improvement; ~3.9 µs is small against a ~1 s cold start. The point is main-thread work removed from beforeApplication.onCreate.💡 Motivation and Context
Fell out of researching JAVA-621 ("lazy-allocate
SentryOptionssub-options"), which is now closed as won't-do — see the measurement writeup on that issue. In short: the sub-options are only ~20 of the 63 allocations inSentryOptions.empty(), and they are read unconditionally duringSentryAndroid.init(ManifestMetadataReader's only guard isif (metadata != null); the replay block reads its own values back asreadBool/isEmpty()/size()defaults) — so lazy holders get forced on first init anyway, even with replay/logs/metrics disabled. This call site was the actual win hiding behind that issue.Related: JAVA-622 and JAVA-623 were previously closed for the same "small allocation win, not worth the trade off" reason.
GH Issue: #5707
Linear: JAVA-621
💚 How did you test it?
SentryPerformanceProviderTest(19 cases) passes unchanged. It writes the config withJsonSerializer.serializeand now reads it back with the direct deserializer, so it exercises a real round-trip across both implementations.when config file is malformed, profiler is not started, covering the truncated-JSON path that previously relied onJsonSerializer's catch-all — asserts both theERRORlog and the existingWARNING.:sentry-android-core:testReleaseUnitTestsuite green.📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
SentryPerformanceProvider:177also callsSentryOptions.empty(), forTracesSampler. That one genuinely needs a mutable options object (setProfileSessionSampleRate) and only runs when a sampled continuous-profiling config exists, so it is deliberately left alone.