5 using System.Diagnostics.Tracing;
7 using System.Runtime.InteropServices;
8 using System.Runtime.ConstrainedExecution;
9 using System.Collections.Generic;
10 using System.Collections.Concurrent;
11 using System.Threading;
12 using System.Runtime.CompilerServices;
13 using System.Diagnostics;
14 using System.Security.Permissions;
16 #if PINNABLEBUFFERCACHE_MSCORLIB
17 namespace System.Threading
22 internal sealed class PinnableBufferCache
25 /// Create a new cache for pinned byte[] buffers
27 /// <param name="cacheName">A name used in diagnostic messages</param>
28 /// <param name="numberOfElements">The size of byte[] buffers in the cache (they are all the same size)</param>
29 public PinnableBufferCache(string cacheName, int numberOfElements) : this(cacheName, () => new byte[numberOfElements]) { }
32 /// Get a buffer from the buffer manager. If no buffers exist, allocate a new one.
34 public byte[] AllocateBuffer() { return (byte[])Allocate(); }
37 /// Return a buffer back to the buffer manager.
39 public void FreeBuffer(byte[] buffer) { Free(buffer); }
42 /// Create a PinnableBufferCache that works on any object (it is intended for OverlappedData)
43 /// This is only used in mscorlib.
45 #if (ENABLE || MINBUFFERS)
46 [EnvironmentPermission(SecurityAction.Assert, Unrestricted = true)]
47 [System.Security.SecuritySafeCritical]
49 internal PinnableBufferCache(string cacheName, Func<object> factory)
51 m_NotGen2 = new List<object>(DefaultNumberOfBuffers);
54 // Check to see if we should disable the cache.
55 string envVarName = "PinnableBufferCache_" + cacheName + "_Disabled";
58 string envVar = Environment.GetEnvironmentVariable(envVarName);
61 PinnableBufferCacheEventSource.Log.DebugMessage("Creating " + cacheName + " PinnableBufferCacheDisabled=" + envVar);
62 int index = envVar.IndexOf(cacheName, StringComparison.OrdinalIgnoreCase);
65 // The cache is disabled because we haven't set the cache name.
66 PinnableBufferCacheEventSource.Log.DebugMessage("Disabling " + cacheName);
73 // Ignore failures when reading the environment variable.
77 // Allow the environment to specify a minimum buffer count.
78 string minEnvVarName = "PinnableBufferCache_" + cacheName + "_MinCount";
81 string minEnvVar = Environment.GetEnvironmentVariable(minEnvVarName);
82 if (minEnvVar != null)
84 if (int.TryParse(minEnvVar, out m_minBufferCount))
90 // Ignore failures when reading the environment variable.
94 PinnableBufferCacheEventSource.Log.Create(cacheName);
95 m_CacheName = cacheName;
99 /// Get a object from the buffer manager. If no buffers exist, allocate a new one.
101 [System.Security.SecuritySafeCritical]
102 internal object Allocate()
105 // Check to see whether or not the cache is disabled.
106 if (m_CacheName == null)
109 // Fast path, get it from our Gen2 aged m_FreeList.
111 if (!m_FreeList.TryPop(out returnBuffer))
112 Restock(out returnBuffer);
114 // Computing free count is expensive enough that we don't want to compute it unless logging is on.
115 if (PinnableBufferCacheEventSource.Log.IsEnabled())
117 int numAllocCalls = Interlocked.Increment(ref m_numAllocCalls);
118 if (numAllocCalls >= 1024)
122 int previousNumAllocCalls = Interlocked.Exchange(ref m_numAllocCalls, 0);
123 if (previousNumAllocCalls >= 1024)
125 int nonGen2Count = 0;
126 foreach (object o in m_FreeList)
128 if (GC.GetGeneration(o) < GC.MaxGeneration)
134 PinnableBufferCacheEventSource.Log.WalkFreeListResult(m_CacheName, m_FreeList.Count, nonGen2Count);
139 PinnableBufferCacheEventSource.Log.AllocateBuffer(m_CacheName, PinnableBufferCacheEventSource.AddressOf(returnBuffer), returnBuffer.GetHashCode(), GC.GetGeneration(returnBuffer), m_FreeList.Count);
145 /// Return a buffer back to the buffer manager.
147 [System.Security.SecuritySafeCritical]
148 internal void Free(object buffer)
151 // Check to see whether or not the cache is disabled.
152 if (m_CacheName == null)
155 if (PinnableBufferCacheEventSource.Log.IsEnabled())
156 PinnableBufferCacheEventSource.Log.FreeBuffer(m_CacheName, PinnableBufferCacheEventSource.AddressOf(buffer), buffer.GetHashCode(), m_FreeList.Count);
160 if (PinnableBufferCacheEventSource.Log.IsEnabled())
161 PinnableBufferCacheEventSource.Log.FreeBufferNull(m_CacheName, m_FreeList.Count);
166 // After we've done 3 gen1 GCs, assume that all buffers have aged into gen2 on the free path.
167 if ((m_gen1CountAtLastRestock + 3) > GC.CollectionCount(GC.MaxGeneration - 1))
171 if (GC.GetGeneration(buffer) < GC.MaxGeneration)
173 // The buffer is not aged, so put it in the non-aged free list.
174 m_moreThanFreeListNeeded = true;
175 PinnableBufferCacheEventSource.Log.FreeBufferStillTooYoung(m_CacheName, m_NotGen2.Count);
176 m_NotGen2.Add(buffer);
177 m_gen1CountAtLastRestock = GC.CollectionCount(GC.MaxGeneration - 1);
183 // If we discovered that it is indeed Gen2, great, put it in the Gen2 list.
184 m_FreeList.Push(buffer);
190 /// Called when we don't have any buffers in our free list to give out.
192 /// <returns></returns>
193 [System.Security.SecuritySafeCritical]
194 private void Restock(out object returnBuffer)
198 // Try again after getting the lock as another thread could have just filled the free list. If we don't check
199 // then we unnecessarily grab a new set of buffers because we think we are out.
200 if (m_FreeList.TryPop(out returnBuffer))
203 // Lazy init, Ask that TrimFreeListIfNeeded be called on every Gen 2 GC.
204 if (m_restockSize == 0)
205 Gen2GcCallback.Register(Gen2GcCallbackFunc, this);
207 // Indicate to the trimming policy that the free list is insufficent.
208 m_moreThanFreeListNeeded = true;
209 PinnableBufferCacheEventSource.Log.AllocateBufferFreeListEmpty(m_CacheName, m_NotGen2.Count);
211 // Get more buffers if needed.
212 if (m_NotGen2.Count == 0)
215 // We have no buffers in the aged freelist, so get one from the newer list. Try to pick the best one.
216 // Debug.Assert(m_NotGen2.Count != 0);
217 int idx = m_NotGen2.Count - 1;
218 if (GC.GetGeneration(m_NotGen2[idx]) < GC.MaxGeneration && GC.GetGeneration(m_NotGen2[0]) == GC.MaxGeneration)
220 returnBuffer = m_NotGen2[idx];
221 m_NotGen2.RemoveAt(idx);
223 // Remember any sub-optimial buffer so we don't put it on the free list when it gets freed.
224 if (PinnableBufferCacheEventSource.Log.IsEnabled() && GC.GetGeneration(returnBuffer) < GC.MaxGeneration)
226 PinnableBufferCacheEventSource.Log.AllocateBufferFromNotGen2(m_CacheName, m_NotGen2.Count);
229 // If we have a Gen1 collection, then everything on m_NotGen2 should have aged. Move them to the m_Free list.
230 if (!AgePendingBuffers())
232 // Before we could age at set of buffers, we have handed out half of them.
233 // This implies we should be proactive about allocating more (since we will trim them if we over-allocate).
234 if (m_NotGen2.Count == m_restockSize / 2)
236 PinnableBufferCacheEventSource.Log.DebugMessage("Proactively adding more buffers to aging pool");
244 /// See if we can promote the buffers to the free list. Returns true if sucessful.
246 [System.Security.SecuritySafeCritical]
247 private bool AgePendingBuffers()
249 if (m_gen1CountAtLastRestock < GC.CollectionCount(GC.MaxGeneration - 1))
251 // Allocate a temp list of buffers that are not actually in gen2, and swap it in once
252 // we're done scanning all buffers.
253 int promotedCount = 0;
254 List<object> notInGen2 = new List<object>();
255 PinnableBufferCacheEventSource.Log.AllocateBufferAged(m_CacheName, m_NotGen2.Count);
256 for (int i = 0; i < m_NotGen2.Count; i++)
258 // We actually check every object to ensure that we aren't putting non-aged buffers into the free list.
259 object currentBuffer = m_NotGen2[i];
260 if (GC.GetGeneration(currentBuffer) >= GC.MaxGeneration)
262 m_FreeList.Push(currentBuffer);
267 notInGen2.Add(currentBuffer);
270 PinnableBufferCacheEventSource.Log.AgePendingBuffersResults(m_CacheName, promotedCount, notInGen2.Count);
271 m_NotGen2 = notInGen2;
279 /// Generates some buffers to age into Gen2.
281 private void CreateNewBuffers()
283 // We choose a very modest number of buffers initially because for the client case. This is often enough.
284 if (m_restockSize == 0)
286 else if (m_restockSize < DefaultNumberOfBuffers)
287 m_restockSize = DefaultNumberOfBuffers;
288 else if (m_restockSize < 256)
289 m_restockSize = m_restockSize * 2; // Grow quickly at small sizes
290 else if (m_restockSize < 4096)
291 m_restockSize = m_restockSize * 3 / 2; // Less agressively at large ones
293 m_restockSize = 4096; // Cap how agressive we are
295 // Ensure we hit our minimums
296 if (m_minBufferCount > m_buffersUnderManagement)
297 m_restockSize = Math.Max(m_restockSize, m_minBufferCount - m_buffersUnderManagement);
299 PinnableBufferCacheEventSource.Log.AllocateBufferCreatingNewBuffers(m_CacheName, m_buffersUnderManagement, m_restockSize);
300 for (int i = 0; i < m_restockSize; i++)
302 // Make a new buffer.
303 object newBuffer = m_factory();
305 // Create space between the objects. We do this because otherwise it forms a single plug (group of objects)
306 // and the GC pins the entire plug making them NOT move to Gen1 and Gen2. by putting space between them
307 // we ensure that object get a chance to move independently (even if some are pinned).
308 var dummyObject = new object();
309 m_NotGen2.Add(newBuffer);
311 m_buffersUnderManagement += m_restockSize;
312 m_gen1CountAtLastRestock = GC.CollectionCount(GC.MaxGeneration - 1);
316 /// This is the static function that is called from the gen2 GC callback.
317 /// The input object is the cache itself.
318 /// NOTE: The reason that we make this functionstatic and take the cache as a parameter is that
319 /// otherwise, we root the cache to the Gen2GcCallback object, and leak the cache even when
320 /// the application no longer needs it.
322 [System.Security.SecuritySafeCritical]
323 private static bool Gen2GcCallbackFunc(object targetObj)
325 return ((PinnableBufferCache)(targetObj)).TrimFreeListIfNeeded();
329 /// This is called on every gen2 GC to see if we need to trim the free list.
330 /// NOTE: DO NOT CALL THIS DIRECTLY FROM THE GEN2GCCALLBACK. INSTEAD CALL IT VIA A STATIC FUNCTION (SEE ABOVE).
331 /// If you register a non-static function as a callback, then this object will be leaked.
333 [System.Security.SecuritySafeCritical]
334 private bool TrimFreeListIfNeeded()
336 int curMSec = Environment.TickCount;
337 int deltaMSec = curMSec - m_msecNoUseBeyondFreeListSinceThisTime;
338 PinnableBufferCacheEventSource.Log.TrimCheck(m_CacheName, m_buffersUnderManagement, m_moreThanFreeListNeeded, deltaMSec);
340 // If we needed more than just the set of aged buffers since the last time we were called,
341 // we obviously should not be trimming any memory, so do nothing except reset the flag
342 if (m_moreThanFreeListNeeded)
344 m_moreThanFreeListNeeded = false;
345 m_trimmingExperimentInProgress = false;
346 m_msecNoUseBeyondFreeListSinceThisTime = curMSec;
350 // We require a minimum amount of clock time to pass (10 seconds) before we trim. Ideally this time
351 // is larger than the typical buffer hold time.
352 if (0 <= deltaMSec && deltaMSec < 10000)
355 // If we got here we have spend the last few second without needing to lengthen the free list. Thus
356 // we have 'enough' buffers, but maybe we have too many.
357 // See if we can trim
360 // Hit a ----, try again later.
361 if (m_moreThanFreeListNeeded)
363 m_moreThanFreeListNeeded = false;
364 m_trimmingExperimentInProgress = false;
365 m_msecNoUseBeyondFreeListSinceThisTime = curMSec;
369 var freeCount = m_FreeList.Count; // This is expensive to fetch, do it once.
371 // If there is something in m_NotGen2 it was not used for the last few seconds, it is trimable.
372 if (m_NotGen2.Count > 0)
374 // If we are not performing an experiment and we have stuff that is waiting to go into the
375 // free list but has not made it there, it could be becasue the 'slow path' of restocking
376 // has not happened, so force this (which should flush the list) and start over.
377 if (!m_trimmingExperimentInProgress)
379 PinnableBufferCacheEventSource.Log.TrimFlush(m_CacheName, m_buffersUnderManagement, freeCount, m_NotGen2.Count);
381 m_trimmingExperimentInProgress = true;
385 PinnableBufferCacheEventSource.Log.TrimFree(m_CacheName, m_buffersUnderManagement, freeCount, m_NotGen2.Count);
386 m_buffersUnderManagement -= m_NotGen2.Count;
388 // Possibly revise the restocking down. We don't want to grow agressively if we are trimming.
389 var newRestockSize = m_buffersUnderManagement / 4;
390 if (newRestockSize < m_restockSize)
391 m_restockSize = Math.Max(newRestockSize, DefaultNumberOfBuffers);
394 m_trimmingExperimentInProgress = false;
398 // Set up an experiment where we use 25% less buffers in our free list. We put them in
399 // m_NotGen2, and if they are needed they will be put back in the free list again.
400 var trimSize = freeCount / 4 + 1;
402 // We are OK with a 15% overhead, do nothing in that case.
403 if (freeCount * 15 <= m_buffersUnderManagement || m_buffersUnderManagement - trimSize <= m_minBufferCount)
405 PinnableBufferCacheEventSource.Log.TrimFreeSizeOK(m_CacheName, m_buffersUnderManagement, freeCount);
409 // Move buffers from teh free list back to the non-aged list. If we don't use them by next time, then we'll consider trimming them.
410 PinnableBufferCacheEventSource.Log.TrimExperiment(m_CacheName, m_buffersUnderManagement, freeCount, trimSize);
412 for (int i = 0; i < trimSize; i++)
414 if (m_FreeList.TryPop(out buffer))
415 m_NotGen2.Add(buffer);
417 m_msecNoUseBeyondFreeListSinceThisTime = curMSec;
418 m_trimmingExperimentInProgress = true;
421 // Indicate that we want to be called back on the next Gen 2 GC.
425 private const int DefaultNumberOfBuffers = 16;
426 private string m_CacheName;
427 private Func<object> m_factory;
430 /// Contains 'good' buffers to reuse. They are guarenteed to be Gen 2 ENFORCED!
432 private ConcurrentStack<object> m_FreeList = new ConcurrentStack<object>();
434 /// Contains buffers that are not gen 2 and thus we do not wish to give out unless we have to.
435 /// To implement trimming we sometimes put aged buffers in here as a place to 'park' them
436 /// before true deletion.
438 private List<object> m_NotGen2;
440 /// What whas the gen 1 count the last time re restocked? If it is now greater, then
441 /// we know that all objects are in Gen 2 so we don't have to check. Should be updated
442 /// every time something gets added to the m_NotGen2 list.
444 private int m_gen1CountAtLastRestock;
447 /// Used to ensure we have a minimum time between trimmings.
449 private int m_msecNoUseBeyondFreeListSinceThisTime;
451 /// To trim, we remove things from the free list (which is Gen 2) and see if we 'hit bottom'
452 /// This flag indicates that we hit bottom (we really needed a bigger free list).
454 private bool m_moreThanFreeListNeeded;
456 /// The total number of buffers that this cache has ever allocated.
457 /// Used in trimming heuristics.
459 private int m_buffersUnderManagement;
461 /// The number of buffers we added the last time we restocked.
463 private int m_restockSize;
465 /// Did we put some buffers into m_NotGen2 to see if we can trim?
467 private bool m_trimmingExperimentInProgress;
469 /// A forced minimum number of buffers.
471 private int m_minBufferCount;
473 /// The number of calls to Allocate.
475 private int m_numAllocCalls;
481 /// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once)
482 /// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care)
484 internal sealed class Gen2GcCallback : CriticalFinalizerObject
486 [System.Security.SecuritySafeCritical]
487 public Gen2GcCallback()
493 /// Schedule 'callback' to be called in the next GC. If the callback returns true it is
494 /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop.
496 /// NOTE: This callback will be kept alive until either the callback function returns false,
497 /// or the target object dies.
499 public static void Register(Func<object, bool> callback, object targetObj)
501 // Create a unreachable object that remembers the callback function and target object.
502 Gen2GcCallback gcCallback = new Gen2GcCallback();
503 gcCallback.Setup(callback, targetObj);
508 private Func<object, bool> m_callback;
509 private GCHandle m_weakTargetObj;
511 [System.Security.SecuritySafeCritical]
512 private void Setup(Func<object, bool> callback, object targetObj)
514 m_callback = callback;
515 m_weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak);
518 [System.Security.SecuritySafeCritical]
521 // Check to see if the target object is still alive.
522 if (!m_weakTargetObj.IsAllocated)
527 object targetObj = m_weakTargetObj.Target;
528 if (targetObj == null)
530 // The target object is dead, so this callback object is no longer needed.
531 m_weakTargetObj.Free();
535 // Execute the callback method.
538 if (!m_callback(targetObj))
540 // If the callback returns false, this callback object is no longer needed.
546 // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception.
549 // Resurrect ourselves by re-registering for finalization.
550 if (!Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload())
552 GC.ReRegisterForFinalize(this);
561 internal sealed class PinnableBufferCacheEventSource
563 public static readonly PinnableBufferCacheEventSource Log = new PinnableBufferCacheEventSource();
565 public bool IsEnabled() { return false; }
566 public void DebugMessage(string message) {}
567 public void DebugMessage1(string message, long value) {}
568 public void DebugMessage2(string message, long value1, long value2) {}
569 public void DebugMessage3(string message, long value1, long value2, long value3) {}
570 public void Create(string cacheName) {}
571 public void AllocateBuffer(string cacheName, ulong objectId, int objectHash, int objectGen, int freeCountAfter) {}
572 public void AllocateBufferFromNotGen2(string cacheName, int notGen2CountAfter) {}
573 public void AllocateBufferCreatingNewBuffers(string cacheName, int totalBuffsBefore, int objectCount) {}
574 public void AllocateBufferAged(string cacheName, int agedCount) {}
575 public void AllocateBufferFreeListEmpty(string cacheName, int notGen2CountBefore) {}
576 public void FreeBuffer(string cacheName, ulong objectId, int objectHash, int freeCountBefore) {}
577 public void FreeBufferNull(string cacheName, int freeCountBefore) { }
578 public void FreeBufferStillTooYoung(string cacheName, int notGen2CountBefore) {}
579 public void TrimCheck(string cacheName, int totalBuffs, bool neededMoreThanFreeList, int deltaMSec) {}
580 public void TrimFree(string cacheName, int totalBuffs, int freeListCount, int toBeFreed) {}
581 public void TrimExperiment(string cacheName, int totalBuffs, int freeListCount, int numTrimTrial) {}
582 public void TrimFreeSizeOK(string cacheName, int totalBuffs, int freeListCount) {}
583 public void TrimFlush(string cacheName, int totalBuffs, int freeListCount, int notGen2CountBefore) {}
584 public void AgePendingBuffersResults(string cacheName, int promotedToFreeListCount, int heldBackCount) {}
585 public void WalkFreeListResult(string cacheName, int freeListCount, int gen0BuffersInFreeList) {}
587 static internal ulong AddressOf(object obj)
592 [System.Security.SecuritySafeCritical]
593 static internal unsafe long AddressOfObject(byte[] array)
600 /// PinnableBufferCacheEventSource is a private eventSource that we are using to
601 /// debug and monitor the effectiveness of PinnableBufferCache
603 #if PINNABLEBUFFERCACHE_MSCORLIB
604 [EventSource(Name = "Microsoft-DotNETRuntime-PinnableBufferCache")]
606 [EventSource(Name = "Microsoft-DotNETRuntime-PinnableBufferCache-System")]
608 internal sealed class PinnableBufferCacheEventSource : EventSource
610 public static readonly PinnableBufferCacheEventSource Log = new PinnableBufferCacheEventSource();
612 [Event(1, Level = EventLevel.Verbose)]
613 public void DebugMessage(string message) { if (IsEnabled()) WriteEvent(1, message); }
614 [Event(2, Level = EventLevel.Verbose)]
615 public void DebugMessage1(string message, long value) { if (IsEnabled()) WriteEvent(2, message, value); }
616 [Event(3, Level = EventLevel.Verbose)]
617 public void DebugMessage2(string message, long value1, long value2) { if (IsEnabled()) WriteEvent(3, message, value1, value2); }
618 [Event(18, Level = EventLevel.Verbose)]
619 public void DebugMessage3(string message, long value1, long value2, long value3) { if (IsEnabled()) WriteEvent(18, message, value1, value2, value3); }
622 public void Create(string cacheName) { if (IsEnabled()) WriteEvent(4, cacheName); }
624 [Event(5, Level = EventLevel.Verbose)]
625 public void AllocateBuffer(string cacheName, ulong objectId, int objectHash, int objectGen, int freeCountAfter) { if (IsEnabled()) WriteEvent(5, cacheName, objectId, objectHash, objectGen, freeCountAfter); }
627 public void AllocateBufferFromNotGen2(string cacheName, int notGen2CountAfter) { if (IsEnabled()) WriteEvent(6, cacheName, notGen2CountAfter); }
629 public void AllocateBufferCreatingNewBuffers(string cacheName, int totalBuffsBefore, int objectCount) { if (IsEnabled()) WriteEvent(7, cacheName, totalBuffsBefore, objectCount); }
631 public void AllocateBufferAged(string cacheName, int agedCount) { if (IsEnabled()) WriteEvent(8, cacheName, agedCount); }
633 public void AllocateBufferFreeListEmpty(string cacheName, int notGen2CountBefore) { if (IsEnabled()) WriteEvent(9, cacheName, notGen2CountBefore); }
635 [Event(10, Level = EventLevel.Verbose)]
636 public void FreeBuffer(string cacheName, ulong objectId, int objectHash, int freeCountBefore) { if (IsEnabled()) WriteEvent(10, cacheName, objectId, objectHash, freeCountBefore); }
638 public void FreeBufferStillTooYoung(string cacheName, int notGen2CountBefore) { if (IsEnabled()) WriteEvent(11, cacheName, notGen2CountBefore); }
641 public void TrimCheck(string cacheName, int totalBuffs, bool neededMoreThanFreeList, int deltaMSec) { if (IsEnabled()) WriteEvent(13, cacheName, totalBuffs, neededMoreThanFreeList, deltaMSec); }
643 public void TrimFree(string cacheName, int totalBuffs, int freeListCount, int toBeFreed) { if (IsEnabled()) WriteEvent(14, cacheName, totalBuffs, freeListCount, toBeFreed); }
645 public void TrimExperiment(string cacheName, int totalBuffs, int freeListCount, int numTrimTrial) { if (IsEnabled()) WriteEvent(15, cacheName, totalBuffs, freeListCount, numTrimTrial); }
647 public void TrimFreeSizeOK(string cacheName, int totalBuffs, int freeListCount) { if (IsEnabled()) WriteEvent(16, cacheName, totalBuffs, freeListCount); }
649 public void TrimFlush(string cacheName, int totalBuffs, int freeListCount, int notGen2CountBefore) { if (IsEnabled()) WriteEvent(17, cacheName, totalBuffs, freeListCount, notGen2CountBefore); }
651 public void AgePendingBuffersResults(string cacheName, int promotedToFreeListCount, int heldBackCount) { if (IsEnabled()) WriteEvent(20, cacheName, promotedToFreeListCount, heldBackCount); }
653 public void WalkFreeListResult(string cacheName, int freeListCount, int gen0BuffersInFreeList) { if (IsEnabled()) WriteEvent(21, cacheName, freeListCount, gen0BuffersInFreeList); }
655 public void FreeBufferNull(string cacheName, int freeCountBefore) { if(IsEnabled()) WriteEvent(22, cacheName, freeCountBefore); }
658 static internal ulong AddressOf(object obj)
660 var asByteArray = obj as byte[];
661 if (asByteArray != null)
662 return (ulong)AddressOfByteArray(asByteArray);
666 [System.Security.SecuritySafeCritical]
667 static internal unsafe long AddressOfByteArray(byte[] array)
671 fixed (byte* ptr = array)
672 return (long)(ptr - 2 * sizeof(void*));