Skip to content

[Mono.Android] Guard GREF/WREF logging P/Invokes behind Logger.LogGlobalRef check#10785

Closed
simonrozsival wants to merge 1 commit intomainfrom
fix-unnecessary-gref-logging
Closed

[Mono.Android] Guard GREF/WREF logging P/Invokes behind Logger.LogGlobalRef check#10785
simonrozsival wants to merge 1 commit intomainfrom
fix-unnecessary-gref-logging

Conversation

@simonrozsival
Copy link
Member

@simonrozsival simonrozsival commented Feb 9, 2026

Summary

The AndroidObjectReferenceManager overrides for CreateGlobalReference, DeleteGlobalReference, CreateWeakGlobalReference, and DeleteWeakGlobalReference were unconditionally calling native logging P/Invokes on every invocation, even when GREF logging was disabled (Logger.LogGlobalRef == false).

While the native side does an early return when LOG_GREF is off, the managed-to-native transition itself has significant overhead that adds up in tight loops (e.g. GC bridge processing on CoreCLR, which swaps GREF↔WREF for thousands of objects per collection).

Changes

  • Add managed _grefc and _weak_grefc counters using Interlocked.Increment/Decrement to maintain reference counts without P/Invoke
  • Keep the gref_gc_threshold full-GC trigger working unconditionally (uses managed counter)
  • Switch GlobalReferenceCount and WeakGlobalReferenceCount properties to read from managed counters via Volatile.Read instead of native P/Invoke getters

Impact

  • When logging is off (default): No P/Invoke calls, just base.* delegation + managed counter update. Eliminates 4+ unnecessary managed→native transitions per GREF/WREF operation.
  • When logging is on: Identical behavior to before (P/Invokes still called for logging + native counter sync).
  • GREF threshold GC trigger: Still works — uses managed _grefc counter.

Performance

Measured on physical device (Samsung, CoreCLR, Release):

  • Bridge cleanup phase with 3740+ objects: 385ms → 4ms (~100x improvement)
  • Per-object NewGlobalRef cost dropped from ~7000µs to ~0µs (the 7ms was the P/Invoke overhead)

Copilot AI review requested due to automatic review settings February 9, 2026 14:47
Copy link

Copilot AI left a comment

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 .NET for Android runtime JNI reference management by avoiding managed→native P/Invoke transitions for GREF/WREF logging when global-ref logging is disabled, aligning the behavior with the existing local-ref logging guards.

Changes:

  • Wrapped _monodroid_gref_log_new/_delete and _monodroid_weak_gref_new/_delete P/Invokes in if (Logger.LogGlobalRef) checks.
  • Avoided building thread/stacktrace logging payloads when global-ref logging is disabled.

Comment on lines 181 to 191
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

Guarding _monodroid_gref_log_new() (and the related delete/weak gref P/Invokes) behind Logger.LogGlobalRef means the native GREF/WREF counters will no longer be updated when GREF logging is disabled. This changes the behavior of RuntimeNativeMethods._monodroid_gref_get() / Java.Interop.Runtime.GlobalReferenceCount and also disables the gref_gc_threshold full-GC trigger in CreateGlobalReference for the default (logging-off) case.

If the goal is to avoid the managed→native transition but keep the counter/threshold behavior, consider tracking GREF/WREF counts in managed code (e.g., Interlocked fields in this manager) and performing the threshold check unconditionally, while only doing stacktrace construction + native logging when Logger.LogGlobalRef is enabled.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

Ok, good copilot saw it, too!

Copy link
Member

Choose a reason for hiding this comment

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

I think this doesn't have _log_ in the name, it's actually deleting? So, we can't change this here?

Copy link
Member

Choose a reason for hiding this comment

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

I think this doesn't have _log_ in the name, it's actually creating a new gref? So, we can't change this here?

Copy link
Member

Choose a reason for hiding this comment

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

If you iterate on this PR, maybe you can put at the top instead:

if (!Logger.LogGlobalRef) {
    return;
}

So, the diff would be a lot smaller: no indentation.

Comment on lines 210 to 216
Copy link
Member

Choose a reason for hiding this comment

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

I agree checking in managed is better, can we remove the equivalant Logger.LogGlobalRef in native now? Or can it get called from places in native code where we'd need to check in both places?

…balRef check

The AndroidObjectReferenceManager overrides for CreateGlobalReference,
DeleteGlobalReference, CreateWeakGlobalReference, and
DeleteWeakGlobalReference were unconditionally calling native logging
P/Invokes on every invocation, even when GREF logging was disabled.

While the native side does an early return when LOG_GREF is off, the
managed-to-native transition itself has significant overhead that adds
up in tight loops (e.g. GC bridge processing with thousands of refs).

Changes:
- Guard all 4 logging P/Invoke calls behind `if (Logger.LogGlobalRef)`
  early-return checks, matching the existing local ref pattern
- Add managed `_grefc` and `_weak_grefc` counters using Interlocked
  to replace the native counter P/Invokes
- Keep the gref_gc_threshold full-GC trigger working unconditionally
- Switch GlobalReferenceCount/WeakGlobalReferenceCount properties to
  read from managed counters via Volatile.Read

Performance (physical device, CoreCLR, Release, 3740 objects):
- Bridge cleanup: 385ms → 4ms (~100x improvement)
- Per-object NewGlobalRef overhead: ~7000µs → ~0µs
@simonrozsival simonrozsival force-pushed the fix-unnecessary-gref-logging branch from d4ce8ca to 99aec81 Compare February 9, 2026 15:52
@simonrozsival
Copy link
Member Author

This is going to require a different approach, but it's definitely part of what's needed to make #10748 viable.

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

Comments