@@ -29,6 +29,25 @@ public class AndroidHighPerformanceSessionManager : IHighPerformanceSessionManag
2929 /// </summary>
3030 private static bool gcLatencyModeSupported = true ;
3131
32+ /// <summary>
33+ /// One-shot disable for <see cref="GC.TryStartNoGCRegion(long, bool)"/>.
34+ /// The API is not implemented on Mono for Android and will throw
35+ /// <see cref="NotImplementedException"/> or <see cref="PlatformNotSupportedException"/>
36+ /// on older runtimes. We disable it permanently on the first failure.
37+ /// </summary>
38+ private static bool noGCRegionSupported = true ;
39+
40+ /// <summary>Whether the current session successfully started a no-GC region.</summary>
41+ private bool noGCRegionActive ;
42+
43+ /// <summary>
44+ /// Heap budget in bytes for <see cref="GC.TryStartNoGCRegion(long, bool)"/>.
45+ /// 64 MB covers typical per-map allocation rates (~4–8 MB/s × ~8 s per map load).
46+ /// When this budget is exhausted the runtime silently reverts to normal GC, so
47+ /// the value acts as an upper-bound guarantee rather than a hard limit.
48+ /// </summary>
49+ private const long NO_GC_REGION_BUDGET_BYTES = 64 * 1024 * 1024 ;
50+
3251 public IDisposable BeginSession ( )
3352 {
3453 enterSession ( ) ;
@@ -87,6 +106,34 @@ private void enterSession()
87106 gcLatencyModeSupported = false ;
88107 Logger . Log ( "GCSettings.LatencyMode unsupported on this runtime; high-performance GC tuning disabled." ) ;
89108 }
109+
110+ // Suppress all GC generations for the duration of the session.
111+ // GC.TryStartNoGCRegion(budget, disallowFullBlockingGC:false) blocks Gen0+Gen1+Gen2
112+ // collection until the budget is exhausted; if allocation exceeds the budget
113+ // the runtime silently reverts to normal GC — the failure mode is "old behaviour",
114+ // not a crash. A 64 MB budget covers typical per-map allocation rates.
115+ // This eliminates the residual Gen0/Gen1 pauses that SustainedLowLatency
116+ // (which only suppresses Gen2) leaves intact.
117+ //
118+ // GC.TryStartNoGCRegion is a .NET Core / .NET 5+ API. Mono for Android
119+ // older runtimes throw NotImplementedException; we disable it permanently
120+ // on the first failure to avoid repeated catching overhead.
121+ if ( noGCRegionSupported && ! noGCRegionActive )
122+ {
123+ try
124+ {
125+ // disallowFullBlockingGC: false — if allocation exceeds the budget,
126+ // the runtime silently reverts to normal GC instead of throwing.
127+ noGCRegionActive = GC . TryStartNoGCRegion ( NO_GC_REGION_BUDGET_BYTES , disallowFullBlockingGC : false ) ;
128+ if ( noGCRegionActive )
129+ Logger . Log ( "High performance session: no-GC region started (64 MB budget)" ) ;
130+ }
131+ catch
132+ {
133+ noGCRegionSupported = false ;
134+ Logger . Log ( "GC.TryStartNoGCRegion unsupported on this runtime; skipping no-GC region." ) ;
135+ }
136+ }
90137 }
91138
92139 private void exitSession ( )
@@ -111,6 +158,23 @@ private void exitSession()
111158 {
112159 gcLatencyModeSupported = false ;
113160 }
161+
162+ if ( noGCRegionActive )
163+ {
164+ try
165+ {
166+ GC . EndNoGCRegion ( ) ;
167+ }
168+ catch
169+ {
170+ // EndNoGCRegion can throw InvalidOperationException if we are not actually
171+ // inside a no-GC region (e.g. budget was exhausted and the runtime exited
172+ // it automatically). Swallow: the goal was to reduce pauses and the
173+ // runtime has already managed the transition gracefully.
174+ }
175+
176+ noGCRegionActive = false ;
177+ }
114178 }
115179 }
116180}
0 commit comments