Skip to content

perf(core): avoid Calendar allocations in the device date provider - #895

Merged
marandaneto merged 1 commit into
mainfrom
perf/device-date-provider
Oct 27, 2026
Merged

marandaneto merged 1 commit into
mainfrom
perf/device-date-provider

Conversation

@marandaneto

Copy link
Copy Markdown
Member

💡 Motivation and Context

PostHogDeviceDateProvider creates a Calendar just to return an epoch-millisecond Date or add a number of seconds. Constructing the calendar allocates timezone and calendar state that these operations do not need.

Use Date() for the current timestamp and Date(System.currentTimeMillis() + seconds * 1000L) for an offset. The Long multiplication preserves the full Int range. Both methods keep the same device-wall-clock behavior, including across timezone and daylight-saving changes. There is no public API change.

This benefits the core JVM SDK and the default Android date provider through Android 12L / API 32. Android 13+ already uses direct Date construction in its network-clock provider, which is unchanged.

💚 How did you test it?

Benchmarks

Measured with a temporary non-debuggable release harness on an Android 15 ARM64 emulator, with ART compiled in speed mode. Each comparison used 40 measurements per variant in eight interleaved blocks, with two excluded warmups per block. Measurements were taken before the latest main merge, against base 129a83a1, with only the date-provider implementation changed.

The timestamp benchmark warmed up 10,000 pairs of calls, then measured 100,000 calls each to currentDate() and addSecondsToCurrentDate(30). Returned dates escape through a volatile sink.

Median for 200,000 timestamp calls Before After Improvement
Wall time 250.34 ms 6.76 ms 37.1x faster
Main-thread CPU time 168.60 ms 4.93 ms 97.1% less
Allocated bytes 546,865,152 4,784,128 99.1% fewer

A separate SDK-setup comparison explicitly selected this device-clock provider through a wrapper, preventing Android 13+ from replacing it with the network-clock provider. Its median was 19.67 ms before and 17.74 ms after. This result is directional only: the 95% block-bootstrap time-difference interval was -6.03 to +0.57 ms, which includes zero. This was not a physical Android 12L device test, and no statistically proven overall startup improvement or default Android 13+ startup improvement is claimed.

The retained change is supported by the clear timestamp-operation improvement, not by the noisy setup result. Other timer experiments were discarded because they did not show a reliable overall benefit.

Correctness and validation

  • make testJava on JDK 17: 1,073 tests passed after merging the latest main.
  • Focused Android tests for PostHogAndroidTest, PostHogAndroidDateProviderTest, PostHogAppInstallIntegrationTest, and PostHogLifecycleObserverIntegrationTest: 57 tests passed.
  • make checkFormat and git diff --check passed.
  • Added wall-clock and negative, zero, positive, and extreme-second-offset tests. They passed on both implementations. Controlled mutations returning the epoch or using overflowing integer multiplication made the corresponding tests fail, then passed again after restoration.
  • A standalone JVM comparison checked 227,520 fixed-instant cases across 632 time zones and five locales, including daylight-saving boundaries, leap day, and extreme offsets. All matched the previous calendar behavior.
  • Reviewed Android's calendar implementation to confirm that adding seconds uses elapsed-millisecond arithmetic without timezone-offset adjustment.

📝 Checklist

  • All commits are signed (see Commit signing).
  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed. No public documentation change is needed for this internal optimization.
  • No breaking change. A changeset records the performance improvement.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file.

🤖 Agent context

Autonomy: Human-driven (agent-assisted).

Implemented with Pi using gpt-6.1-sol, Git, Gradle, Python, the changesets CLI, and GitHub CLI. Local session ID: 01a17727-1f78-754a-81a5-2b193f277c40; no public session link is available.

Kept the change limited to the date provider, its compatibility tests, and patch changesets for the affected packages. The temporary benchmark harness and investigation notes are not included. The test-authoring audit verified the wall-clock and overflow assertions with controlled mutations. An isolated autoreview of commit f841da0a5a376d3a397b786af70a54f017983a1a against origin/main reported no actionable findings. Human review is required before merging.

@marandaneto marandaneto self-assigned this Oct 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor
⚠️ Changeset declares `posthog-server-openfeature` but no source files in that package changed

This is informational — the PR is not blocked. Click the triangle above to collapse, or push a fix and this comment will auto-delete.

Declared in a changeset but no source files modified:

  • posthog-server-openfeature

Double-check this is intentional — for example, releasing a previously-merged change.

Changesets in this PR:

  • posthog — patch
  • posthog-android — patch
  • posthog-android-surveys-compose — patch
  • posthog-server — patch
  • posthog-server-openfeature — patch

@github-actions

Copy link
Copy Markdown
Contributor

posthog-android Compliance Report

Date: 2026-10-27 07:59:32 UTC
Duration: 117930ms

✅ All Tests Passed!

46/46 tests passed


Capture Tests

✅ 29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 289ms
Format Validation.Event Has Uuid ✅ 26ms
Format Validation.Event Has Lib Properties ✅ 26ms
Format Validation.Distinct Id Is String ✅ 26ms
Format Validation.Token Is Present ✅ 21ms
Format Validation.Custom Properties Preserved ✅ 22ms
Format Validation.Event Has Timestamp ✅ 21ms
Retry Behavior.Retries On 503 ✅ 7022ms
Retry Behavior.Does Not Retry On 400 ✅ 4017ms
Retry Behavior.Does Not Retry On 401 ✅ 4022ms
Retry Behavior.Respects Retry After Header ✅ 7021ms
Retry Behavior.Implements Backoff ✅ 17020ms
Retry Behavior.Retries On 500 ✅ 7016ms
Retry Behavior.Retries On 502 ✅ 7019ms
Retry Behavior.Retries On 504 ✅ 7016ms
Retry Behavior.Max Retries Respected ✅ 17034ms
Deduplication.Generates Unique Uuids ✅ 36ms
Deduplication.Preserves Uuid On Retry ✅ 7014ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 12018ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 7019ms
Deduplication.No Duplicate Events In Batch ✅ 28ms
Deduplication.Different Events Have Different Uuids ✅ 18ms
Compression.Sends Gzip When Enabled ✅ 16ms
Batch Format.Uses Proper Batch Structure ✅ 14ms
Batch Format.Flush With No Events Sends Nothing ✅ 12ms
Batch Format.Multiple Events Batched Together ✅ 24ms
Error Handling.Does Not Retry On 403 ✅ 4016ms
Error Handling.Does Not Retry On 413 ✅ 4014ms
Error Handling.Retries On 408 ✅ 5024ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 28ms
Request Payload.Flags Request Uses V2 Query Param ✅ 18ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 28ms
Request Payload.Flags Request Omits Authorization Header ✅ 19ms
Request Payload.Token In Flags Body Matches Init ✅ 14ms
Request Payload.Groups Round Trip ✅ 13ms
Request Payload.Groups Default To Empty Object ✅ 14ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 14ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 13ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 18ms
Request Lifecycle.No Flags Request On Init Alone ✅ 7ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 14ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 23ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 16ms
Retry Behavior.Retries Flags On 502 ✅ 315ms
Retry Behavior.Retries Flags On 504 ✅ 316ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 16ms

@marandaneto
marandaneto marked this pull request as ready for review October 27, 2026 08:14
@marandaneto
marandaneto requested a review from a team as a code owner October 27, 2026 08:14
@marandaneto
marandaneto merged commit 6a80525 into main Oct 27, 2026
17 checks passed
@marandaneto
marandaneto deleted the perf/device-date-provider branch October 27, 2026 14:21
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