Skip to content

perf(core): Avoid allocations in log calls when debug is off - #6600

Open
romtsn wants to merge 3 commits into
mainfrom
romtsn/perf/logger-call-site-allocations
Open

romtsn wants to merge 3 commits into
mainfrom
romtsn/perf/logger-call-site-allocations

Conversation

@romtsn

@romtsn romtsn commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

📜 Description

When debug = false, an SDK log call still allocates at the call site. The disabled logger then discards the result. This PR removes two sources of these allocations.

1. A varargs array on each call. javac makes a new Object[] for each varargs call, also for a call without arguments. ART does not remove it. ILogger now has default overloads for 0, 1 and 2 format arguments. DiagnosticLogger and NoOpLogger override them, and they check isEnabled before an array exists. The SDK call sites need no change: when they compile again, they use the new overloads. About 95% of the ~1,300 SDK log calls have 2 arguments or fewer.

2. Messages built before the call. 7 log calls on normal code paths built their message with string concatenation or toString(): 2 in DefaultCompositePerformanceCollector, called on each transaction stop, and 5 in SentryGestureListener, for example on each scroll. They now send %s arguments, so the SDK builds the message only when logging is enabled. Calls in catch blocks and at init are not changed.

The new ILogger methods are additions with default bodies, so existing ILogger implementations continue to work.

💡 Motivation and Context

Apps in production pay for SDK debug logs that they never see.

💚 How did you test it?

I used an androidx.benchmark microbenchmark on a Pixel 2 XL (API 30, debug = false, median). It was a temporary module and is not part of this PR. The logger is the real DiagnosticLogger that wraps AndroidLogger.

Benchmark main Overloads Overloads + call sites
log(level, msg) 48.6 ns / 1 alloc 17.8 ns / 0 17.9 ns / 0
log(level, msg, a) 55.2 ns / 1 18.5 ns / 0 18.5 ns / 0
log(level, msg, a, b) 70.2 ns / 1 18.7 ns / 0 18.7 ns / 0
log(level, msg, long) 77.9 ns / 2 39.3 ns / 1 39.0 ns / 1
log(level, "msg " + s) 602.8 ns / 5 598.1 ns / 4 612.0 ns / 4
captureMessage 598 allocs 594 594
Transaction with one child span 416 allocs 412 408
  • Each call: a disabled log call is about 3× faster and does not allocate.
  • SDK operations: they make about 1–2% fewer allocations. Their time changes by less than the noise between runs.
  • log(level, msg, long): the Long box stays, because the overloads take Object.

All unit tests pass (9,074 tests in 46 modules). Some tests verified the varargs overload with anyVararg(). Because the SDK calls now use the new overloads, those tests failed, or, in 4 never() checks, passed always. They now verify the overload that the SDK calls.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.

🤖 Generated with Claude Code

romtsn and others added 3 commits November 12, 2026 18:20
Every SDK log call allocated a varargs array at the call site, also when debug is off. ILogger now has fixed-arity default overloads for zero, one and two arguments, which DiagnosticLogger and NoOpLogger override. Recompiled call sites use them and do not allocate when logging is disabled.

Hot-path log calls in DefaultCompositePerformanceCollector and SentryGestureListener also built their messages eagerly with string concatenation or toString(). They now pass format arguments, so the message is built only when logging is enabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@romtsn romtsn added the sanity-check PR needs a lightweight review for obvious issues label Nov 12, 2026
@romtsn
romtsn marked this pull request as ready for review November 12, 2026 17:22
@romtsn
romtsn requested review from a team and adinauer as code owners November 12, 2026 17:22
@romtsn
romtsn requested review from 0xadam-brown and markushi and removed request for a team November 12, 2026 17:22

@runningcode runningcode left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@sentry

sentry Bot commented Nov 12, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.64.0 (1) release

⚙️ sentry-android Build Distribution Settings

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sanity-check PR needs a lightweight review for obvious issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants