private void CollectInfrequently(long privateBytes) { // not thread-safe, only invoke from timer callback Debug.Assert(_inPBytesMonitorThread == 1); // The Server GC on x86 can traverse ~200mb per CPU per second, and the maximum heap size // is about 3400mb, so the worst case scenario on x86 would take about 8 seconds to collect // on a dual CPU box. // // The Server GC on x64 can traverse ~300mb per CPU per second, so a 6000 MB heap will take // about 10 seconds to collect on a dual CPU box. The worst case scenario on x64 would make // you want to return your hardware for a refund. long timeSinceInducedGC = DateTime.UtcNow.Subtract(_inducedGCFinishTime).Ticks; bool infrequent = (timeSinceInducedGC > _inducedGCMinInterval); // if we haven't collected recently, or if the trim percent is low (less than 50%), // we need to collect again if (infrequent || _lastTrimPercent < 50) { // if we're inducing GC too frequently, increase the trim percentage, but don't go above 50% if (!infrequent) { _lastTrimPercent = Math.Min(50, _lastTrimPercent + 10); } // if we're inducing GC infrequently, we may want to decrease the trim percentage else if (_lastTrimPercent > 10 && timeSinceInducedGC > 2 * _inducedGCMinInterval) { _lastTrimPercent = Math.Max(10, _lastTrimPercent - 10); } int percent = (_totalCacheSize > 0) ? _lastTrimPercent : 0; long trimmedOrExpired = 0; if (percent > 0) { Stopwatch sw1 = Stopwatch.StartNew(); trimmedOrExpired = _appManager.TrimCaches(percent); sw1.Stop(); _trimDurationTicks = sw1.Elapsed.Ticks; } // if (trimmedOrExpired == 0 || _appManager.ShutdownInProgress) { return; } // collect and record statistics Stopwatch sw2 = Stopwatch.StartNew(); GC.Collect(); sw2.Stop(); _inducedGCCount++; // only used for debugging _inducedGCFinishTime = DateTime.UtcNow; _inducedGCDurationTicks = sw2.Elapsed.Ticks; _inducedGCPostPrivateBytes = NextSample(); _inducedGCPrivateBytesChange = privateBytes - _inducedGCPostPrivateBytes; // target 3.3% Time in GC, but don't induce a GC more than once every 5 seconds // Notes on calculation below: If G is duration of garbage collection and T is duration // between starting the next collection, then G/T is % Time in GC. If we target 3.3%, // then G/T = 3.3% = 33/1000, so T = G * 1000/33. _inducedGCMinInterval = Math.Max(_inducedGCDurationTicks * 1000 / 33, 5 * TimeSpan.TicksPerSecond); // no more frequently than every 60 seconds if change is less than 1% if (_inducedGCPrivateBytesChange * 100 <= privateBytes) { _inducedGCMinInterval = Math.Max(_inducedGCMinInterval, 60 * TimeSpan.TicksPerSecond); } #if DBG Debug.Trace("CacheMemory", "GC.COLLECT STATS " + "TrimCaches(" + percent + ")" + ", trimDurationSeconds=" + (_trimDurationTicks / TimeSpan.TicksPerSecond) + ", trimmedOrExpired=" + trimmedOrExpired + ", #secondsSinceInducedGC=" + (timeSinceInducedGC / TimeSpan.TicksPerSecond) + ", InducedGCCount=" + _inducedGCCount + ", gcDurationSeconds=" + (_inducedGCDurationTicks / TimeSpan.TicksPerSecond) + ", PrePrivateBytes=" + privateBytes + ", PostPrivateBytes=" + _inducedGCPostPrivateBytes + ", PrivateBytesChange=" + _inducedGCPrivateBytesChange + ", gcMinIntervalSeconds=" + (_inducedGCMinInterval / TimeSpan.TicksPerSecond)); #endif #if PERF SafeNativeMethods.OutputDebugString(" ** COLLECT **: " + percent + "%, " + (_trimDurationTicks / TimeSpan.TicksPerSecond) + " seconds" + ", infrequent=" + infrequent + ", removed=" + trimmedOrExpired + ", sinceIGC=" + (timeSinceInducedGC / TimeSpan.TicksPerSecond) + ", IGCCount=" + _inducedGCCount + ", IGCDuration=" + (_inducedGCDurationTicks / TimeSpan.TicksPerSecond) + ", preBytes=" + privateBytes + ", postBytes=" + _inducedGCPostPrivateBytes + ", byteChange=" + _inducedGCPrivateBytesChange + ", IGCMinInterval=" + (_inducedGCMinInterval / TimeSpan.TicksPerSecond) + "\n"); #endif } }