diff options
Diffstat (limited to 'src/System.Private.CoreLib/src/System/Threading/PinnableBufferCache.cs')
-rw-r--r-- | src/System.Private.CoreLib/src/System/Threading/PinnableBufferCache.cs | 693 |
1 files changed, 0 insertions, 693 deletions
diff --git a/src/System.Private.CoreLib/src/System/Threading/PinnableBufferCache.cs b/src/System.Private.CoreLib/src/System/Threading/PinnableBufferCache.cs deleted file mode 100644 index 5ebeb4441..000000000 --- a/src/System.Private.CoreLib/src/System/Threading/PinnableBufferCache.cs +++ /dev/null @@ -1,693 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -//#define ENABLE -//#define MINBUFFERS - -using System.Collections.Generic; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; - -namespace System.Threading -{ - // Reduced copy of System.Collections.Concurrent.ConcurrentStack<T> - internal class ConcurrentStack<T> - { - private class Node - { - internal readonly T _value; // Value of the node. - internal Node _next; // Next pointer. - - internal Node(T value) - { - _value = value; - _next = null; - } - } - - private volatile Node _head; // The stack is a singly linked list, and only remembers the head. - - private const int BACKOFF_MAX_YIELDS = 8; // Arbitrary number to cap backoff. - - public int Count - { - get - { - int count = 0; - - for (Node curr = _head; curr != null; curr = curr._next) - { - count++; //we don't handle overflow, to be consistent with existing generic collection types in CLR - } - - return count; - } - } - - public void Push(T item) - { - Node newNode = new Node(item); - newNode._next = _head; - if (Interlocked.CompareExchange(ref _head, newNode, newNode._next) == newNode._next) - { - return; - } - - // If we failed, go to the slow path and loop around until we succeed. - PushCore(newNode, newNode); - } - - private void PushCore(Node head, Node tail) - { - SpinWait spin = new SpinWait(); - - // Keep trying to CAS the existing head with the new node until we succeed. - do - { - spin.SpinOnce(); - // Reread the head and link our new node. - tail._next = _head; - } - while (Interlocked.CompareExchange( - ref _head, head, tail._next) != tail._next); - } - - public bool TryPop(out T result) - { - Node head = _head; - //stack is empty - if (head == null) - { - result = default(T); - return false; - } - if (Interlocked.CompareExchange(ref _head, head._next, head) == head) - { - result = head._value; - return true; - } - - // Fall through to the slow path. - return TryPopCore(out result); - } - - private bool TryPopCore(out T result) - { - Node poppedNode; - - if (TryPopCore(1, out poppedNode) == 1) - { - result = poppedNode._value; - return true; - } - - result = default(T); - return false; - } - - private int TryPopCore(int count, out Node poppedHead) - { - SpinWait spin = new SpinWait(); - - // Try to CAS the head with its current next. We stop when we succeed or - // when we notice that the stack is empty, whichever comes first. - Node head; - Node next; - int backoff = 1; - Random r = null; - while (true) - { - head = _head; - // Is the stack empty? - if (head == null) - { - poppedHead = null; - return 0; - } - next = head; - int nodesCount = 1; - for (; nodesCount < count && next._next != null; nodesCount++) - { - next = next._next; - } - - // Try to swap the new head. If we succeed, break out of the loop. - if (Interlocked.CompareExchange(ref _head, next._next, head) == head) - { - // Return the popped Node. - poppedHead = head; - return nodesCount; - } - - // We failed to CAS the new head. Spin briefly and retry. - for (int i = 0; i < backoff; i++) - { - spin.SpinOnce(); - } - - if (spin.NextSpinWillYield) - { - if (r == null) - { - r = new Random(); - } - backoff = r.Next(1, BACKOFF_MAX_YIELDS); - } - else - { - backoff *= 2; - } - } - } - } - - internal sealed class PinnableBufferCache - { - /// <summary> - /// Create a PinnableBufferCache that works on any object (it is intended for OverlappedData) - /// This is only used in mscorlib. - /// </summary> - internal PinnableBufferCache(string cacheName, Func<object> factory) - { - m_NotGen2 = new List<object>(DefaultNumberOfBuffers); - m_factory = factory; -#if ENABLE - // Check to see if we should disable the cache. - string envVarName = "PinnableBufferCache_" + cacheName + "_Disabled"; - try - { - string envVar = Environment.GetEnvironmentVariable(envVarName); - if (envVar != null) - { - PinnableBufferCacheEventSource.Log.DebugMessage("Creating " + cacheName + " PinnableBufferCacheDisabled=" + envVar); - int index = envVar.IndexOf(cacheName, StringComparison.OrdinalIgnoreCase); - if (0 <= index) - { - // The cache is disabled because we haven't set the cache name. - PinnableBufferCacheEventSource.Log.DebugMessage("Disabling " + cacheName); - return; - } - } - } - catch - { - // Ignore failures when reading the environment variable. - } -#endif -#if MINBUFFERS - // Allow the environment to specify a minimum buffer count. - string minEnvVarName = "PinnableBufferCache_" + cacheName + "_MinCount"; - try - { - string minEnvVar = Environment.GetEnvironmentVariable(minEnvVarName); - if (minEnvVar != null) - { - if (int.TryParse(minEnvVar, out m_minBufferCount)) - CreateNewBuffers(); - } - } - catch - { - // Ignore failures when reading the environment variable. - } -#endif - - PinnableBufferCacheEventSource.Log.Create(cacheName); - m_CacheName = cacheName; - } - - /// <summary> - /// Get a object from the buffer manager. If no buffers exist, allocate a new one. - /// </summary> - internal object Allocate() - { -#if ENABLE - // Check to see whether or not the cache is disabled. - if (m_CacheName == null) - return m_factory(); -#endif - // Fast path, get it from our Gen2 aged m_FreeList. - object returnBuffer; - if (!m_FreeList.TryPop(out returnBuffer)) - Restock(out returnBuffer); - -#if LOGGING - // Computing free count is expensive enough that we don't want to compute it unless logging is on. - if (PinnableBufferCacheEventSource.Log.IsEnabled()) - { - int numAllocCalls = Interlocked.Increment(ref m_numAllocCalls); - if (numAllocCalls >= 1024) - { - lock (this) - { - int previousNumAllocCalls = Interlocked.Exchange(ref m_numAllocCalls, 0); - if (previousNumAllocCalls >= 1024) - { - int nonGen2Count = 0; - foreach (object o in m_FreeList) - { - if (GC.GetGeneration(o) < GC.MaxGeneration) - { - nonGen2Count++; - } - } - - PinnableBufferCacheEventSource.Log.WalkFreeListResult(m_CacheName, m_FreeList.Count, nonGen2Count); - } - } - } - - PinnableBufferCacheEventSource.Log.AllocateBuffer(m_CacheName, PinnableBufferCacheEventSource.AddressOf(returnBuffer), returnBuffer.GetHashCode(), GC.GetGeneration(returnBuffer), m_FreeList.Count); - } -#endif - return returnBuffer; - } - - /// <summary> - /// Return a buffer back to the buffer manager. - /// </summary> - internal void Free(object buffer) - { -#if ENABLE - // Check to see whether or not the cache is disabled. - if (m_CacheName == null) - return; -#endif - if (PinnableBufferCacheEventSource.Log.IsEnabled()) - PinnableBufferCacheEventSource.Log.FreeBuffer(m_CacheName, PinnableBufferCacheEventSource.AddressOf(buffer), buffer.GetHashCode(), m_FreeList.Count); - - - // After we've done 3 gen1 GCs, assume that all buffers have aged into gen2 on the free path. - if ((m_gen1CountAtLastRestock + 3) > GC.CollectionCount(GC.MaxGeneration - 1)) - { - lock (this) - { - if (GC.GetGeneration(buffer) < GC.MaxGeneration) - { - // The buffer is not aged, so put it in the non-aged free list. - m_moreThanFreeListNeeded = true; - PinnableBufferCacheEventSource.Log.FreeBufferStillTooYoung(m_CacheName, m_NotGen2.Count); - m_NotGen2.Add(buffer); - m_gen1CountAtLastRestock = GC.CollectionCount(GC.MaxGeneration - 1); - return; - } - } - } - - // If we discovered that it is indeed Gen2, great, put it in the Gen2 list. - m_FreeList.Push(buffer); - } - - #region Private - - /// <summary> - /// Called when we don't have any buffers in our free list to give out. - /// </summary> - /// <returns></returns> - private void Restock(out object returnBuffer) - { - lock (this) - { - // Try again after getting the lock as another thread could have just filled the free list. If we don't check - // then we unnecessarily grab a new set of buffers because we think we are out. - if (m_FreeList.TryPop(out returnBuffer)) - return; - - // Lazy init, Ask that TrimFreeListIfNeeded be called on every Gen 2 GC. - if (m_restockSize == 0) - Gen2GcCallback.Register(Gen2GcCallbackFunc, this); - - // Indicate to the trimming policy that the free list is insufficent. - m_moreThanFreeListNeeded = true; - PinnableBufferCacheEventSource.Log.AllocateBufferFreeListEmpty(m_CacheName, m_NotGen2.Count); - - // Get more buffers if needed. - if (m_NotGen2.Count == 0) - CreateNewBuffers(); - - // We have no buffers in the aged freelist, so get one from the newer list. Try to pick the best one. - // Debug.Assert(m_NotGen2.Count != 0); - int idx = m_NotGen2.Count - 1; - if (GC.GetGeneration(m_NotGen2[idx]) < GC.MaxGeneration && GC.GetGeneration(m_NotGen2[0]) == GC.MaxGeneration) - idx = 0; - returnBuffer = m_NotGen2[idx]; - m_NotGen2.RemoveAt(idx); - - // Remember any sub-optimial buffer so we don't put it on the free list when it gets freed. - if (PinnableBufferCacheEventSource.Log.IsEnabled() && GC.GetGeneration(returnBuffer) < GC.MaxGeneration) - { - PinnableBufferCacheEventSource.Log.AllocateBufferFromNotGen2(m_CacheName, m_NotGen2.Count); - } - - // If we have a Gen1 collection, then everything on m_NotGen2 should have aged. Move them to the m_Free list. - if (!AgePendingBuffers()) - { - // Before we could age at set of buffers, we have handed out half of them. - // This implies we should be proactive about allocating more (since we will trim them if we over-allocate). - if (m_NotGen2.Count == m_restockSize / 2) - { - PinnableBufferCacheEventSource.Log.DebugMessage("Proactively adding more buffers to aging pool"); - CreateNewBuffers(); - } - } - } - } - - /// <summary> - /// See if we can promote the buffers to the free list. Returns true if sucessful. - /// </summary> - private bool AgePendingBuffers() - { - if (m_gen1CountAtLastRestock < GC.CollectionCount(GC.MaxGeneration - 1)) - { - // Allocate a temp list of buffers that are not actually in gen2, and swap it in once - // we're done scanning all buffers. - int promotedCount = 0; - List<object> notInGen2 = new List<object>(); - PinnableBufferCacheEventSource.Log.AllocateBufferAged(m_CacheName, m_NotGen2.Count); - for (int i = 0; i < m_NotGen2.Count; i++) - { - // We actually check every object to ensure that we aren't putting non-aged buffers into the free list. - object currentBuffer = m_NotGen2[i]; - if (GC.GetGeneration(currentBuffer) >= GC.MaxGeneration) - { - m_FreeList.Push(currentBuffer); - promotedCount++; - } - else - { - notInGen2.Add(currentBuffer); - } - } - PinnableBufferCacheEventSource.Log.AgePendingBuffersResults(m_CacheName, promotedCount, notInGen2.Count); - m_NotGen2 = notInGen2; - - return true; - } - return false; - } - - /// <summary> - /// Generates some buffers to age into Gen2. - /// </summary> - private void CreateNewBuffers() - { - // We choose a very modest number of buffers initially because for the client case. This is often enough. - if (m_restockSize == 0) - m_restockSize = 4; - else if (m_restockSize < DefaultNumberOfBuffers) - m_restockSize = DefaultNumberOfBuffers; - else if (m_restockSize < 256) - m_restockSize = m_restockSize * 2; // Grow quickly at small sizes - else if (m_restockSize < 4096) - m_restockSize = m_restockSize * 3 / 2; // Less aggressively at large ones - else - m_restockSize = 4096; // Cap how aggressive we are - - // Ensure we hit our minimums - if (m_minBufferCount > m_buffersUnderManagement) - m_restockSize = Math.Max(m_restockSize, m_minBufferCount - m_buffersUnderManagement); - - PinnableBufferCacheEventSource.Log.AllocateBufferCreatingNewBuffers(m_CacheName, m_buffersUnderManagement, m_restockSize); - for (int i = 0; i < m_restockSize; i++) - { - // Make a new buffer. - object newBuffer = m_factory(); - - // Create space between the objects. We do this because otherwise it forms a single plug (group of objects) - // and the GC pins the entire plug making them NOT move to Gen1 and Gen2. by putting space between them - // we ensure that object get a chance to move independently (even if some are pinned). - var dummyObject = new object(); - m_NotGen2.Add(newBuffer); - } - m_buffersUnderManagement += m_restockSize; - m_gen1CountAtLastRestock = GC.CollectionCount(GC.MaxGeneration - 1); - } - - /// <summary> - /// This is the static function that is called from the gen2 GC callback. - /// The input object is the cache itself. - /// NOTE: The reason that we make this functionstatic and take the cache as a parameter is that - /// otherwise, we root the cache to the Gen2GcCallback object, and leak the cache even when - /// the application no longer needs it. - /// </summary> - private static bool Gen2GcCallbackFunc(object targetObj) - { - return ((PinnableBufferCache)(targetObj)).TrimFreeListIfNeeded(); - } - - /// <summary> - /// This is called on every gen2 GC to see if we need to trim the free list. - /// NOTE: DO NOT CALL THIS DIRECTLY FROM THE GEN2GCCALLBACK. INSTEAD CALL IT VIA A STATIC FUNCTION (SEE ABOVE). - /// If you register a non-static function as a callback, then this object will be leaked. - /// </summary> - private bool TrimFreeListIfNeeded() - { - int curMSec = Environment.TickCount; - int deltaMSec = curMSec - m_msecNoUseBeyondFreeListSinceThisTime; - PinnableBufferCacheEventSource.Log.TrimCheck(m_CacheName, m_buffersUnderManagement, m_moreThanFreeListNeeded, deltaMSec); - - // If we needed more than just the set of aged buffers since the last time we were called, - // we obviously should not be trimming any memory, so do nothing except reset the flag - if (m_moreThanFreeListNeeded) - { - m_moreThanFreeListNeeded = false; - m_trimmingExperimentInProgress = false; - m_msecNoUseBeyondFreeListSinceThisTime = curMSec; - return true; - } - - // We require a minimum amount of clock time to pass (10 seconds) before we trim. Ideally this time - // is larger than the typical buffer hold time. - if (0 <= deltaMSec && deltaMSec < 10000) - return true; - - // If we got here we have spend the last few second without needing to lengthen the free list. Thus - // we have 'enough' buffers, but maybe we have too many. - // See if we can trim - lock (this) - { - // Hit a race, try again later. - if (m_moreThanFreeListNeeded) - { - m_moreThanFreeListNeeded = false; - m_trimmingExperimentInProgress = false; - m_msecNoUseBeyondFreeListSinceThisTime = curMSec; - return true; - } - - var freeCount = m_FreeList.Count; // This is expensive to fetch, do it once. - - // If there is something in m_NotGen2 it was not used for the last few seconds, it is trimable. - if (m_NotGen2.Count > 0) - { - // If we are not performing an experiment and we have stuff that is waiting to go into the - // free list but has not made it there, it could be becasue the 'slow path' of restocking - // has not happened, so force this (which should flush the list) and start over. - if (!m_trimmingExperimentInProgress) - { - PinnableBufferCacheEventSource.Log.TrimFlush(m_CacheName, m_buffersUnderManagement, freeCount, m_NotGen2.Count); - AgePendingBuffers(); - m_trimmingExperimentInProgress = true; - return true; - } - - PinnableBufferCacheEventSource.Log.TrimFree(m_CacheName, m_buffersUnderManagement, freeCount, m_NotGen2.Count); - m_buffersUnderManagement -= m_NotGen2.Count; - - // Possibly revise the restocking down. We don't want to grow agressively if we are trimming. - var newRestockSize = m_buffersUnderManagement / 4; - if (newRestockSize < m_restockSize) - m_restockSize = Math.Max(newRestockSize, DefaultNumberOfBuffers); - - m_NotGen2.Clear(); - m_trimmingExperimentInProgress = false; - return true; - } - - // Set up an experiment where we use 25% less buffers in our free list. We put them in - // m_NotGen2, and if they are needed they will be put back in the free list again. - var trimSize = freeCount / 4 + 1; - - // We are OK with a 15% overhead, do nothing in that case. - if (freeCount * 15 <= m_buffersUnderManagement || m_buffersUnderManagement - trimSize <= m_minBufferCount) - { - PinnableBufferCacheEventSource.Log.TrimFreeSizeOK(m_CacheName, m_buffersUnderManagement, freeCount); - return true; - } - - // Move buffers from the free list back to the non-aged list. If we don't use them by next time, then we'll consider trimming them. - PinnableBufferCacheEventSource.Log.TrimExperiment(m_CacheName, m_buffersUnderManagement, freeCount, trimSize); - object buffer; - for (int i = 0; i < trimSize; i++) - { - if (m_FreeList.TryPop(out buffer)) - m_NotGen2.Add(buffer); - } - m_msecNoUseBeyondFreeListSinceThisTime = curMSec; - m_trimmingExperimentInProgress = true; - } - - // Indicate that we want to be called back on the next Gen 2 GC. - return true; - } - - private const int DefaultNumberOfBuffers = 16; - private string m_CacheName; - private Func<object> m_factory; - - /// <summary> - /// Contains 'good' buffers to reuse. They are guarenteed to be Gen 2 ENFORCED! - /// </summary> - private ConcurrentStack<object> m_FreeList = new ConcurrentStack<object>(); - /// <summary> - /// Contains buffers that are not gen 2 and thus we do not wish to give out unless we have to. - /// To implement trimming we sometimes put aged buffers in here as a place to 'park' them - /// before true deletion. - /// </summary> - private List<object> m_NotGen2; - /// <summary> - /// What whas the gen 1 count the last time re restocked? If it is now greater, then - /// we know that all objects are in Gen 2 so we don't have to check. Should be updated - /// every time something gets added to the m_NotGen2 list. - /// </summary> - private int m_gen1CountAtLastRestock; - - /// <summary> - /// Used to ensure we have a minimum time between trimmings. - /// </summary> - private int m_msecNoUseBeyondFreeListSinceThisTime; - /// <summary> - /// To trim, we remove things from the free list (which is Gen 2) and see if we 'hit bottom' - /// This flag indicates that we hit bottom (we really needed a bigger free list). - /// </summary> - private bool m_moreThanFreeListNeeded; - /// <summary> - /// The total number of buffers that this cache has ever allocated. - /// Used in trimming heuristics. - /// </summary> - private int m_buffersUnderManagement; - /// <summary> - /// The number of buffers we added the last time we restocked. - /// </summary> - private int m_restockSize; - /// <summary> - /// Did we put some buffers into m_NotGen2 to see if we can trim? - /// </summary> - private bool m_trimmingExperimentInProgress; -#pragma warning disable 0649 - /// <summary> - /// A forced minimum number of buffers. - /// </summary> - private int m_minBufferCount; -#pragma warning restore 0649 -#if LOGGING - /// <summary> - /// The number of calls to Allocate. - /// </summary> - private int m_numAllocCalls; -#endif - - #endregion - } - - /// <summary> - /// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once) - /// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care) - /// </summary> - internal sealed class Gen2GcCallback : CriticalFinalizerObject - { - public Gen2GcCallback() - : base() - { - } - - /// <summary> - /// Schedule 'callback' to be called in the next GC. If the callback returns true it is - /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. - /// - /// NOTE: This callback will be kept alive until either the callback function returns false, - /// or the target object dies. - /// </summary> - public static void Register(Func<object, bool> callback, object targetObj) - { - // Create a unreachable object that remembers the callback function and target object. - Gen2GcCallback gcCallback = new Gen2GcCallback(); - gcCallback.Setup(callback, targetObj); - } - - #region Private - - private Func<object, bool> m_callback; - private GCHandle m_weakTargetObj; - - private void Setup(Func<object, bool> callback, object targetObj) - { - m_callback = callback; - m_weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak); - } - - ~Gen2GcCallback() - { - // Check to see if the target object is still alive. - object targetObj = m_weakTargetObj.Target; - if (targetObj == null) - { - // The target object is dead, so this callback object is no longer needed. - m_weakTargetObj.Free(); - return; - } - - // Execute the callback method. - try - { - if (!m_callback(targetObj)) - { - // If the callback returns false, this callback object is no longer needed. - return; - } - } - catch - { - // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception. - } - - // Resurrect ourselves by re-registering for finalization. - if (!Environment.HasShutdownStarted /*&& !AppDomain.CurrentDomain.IsFinalizingForUnload()*/) - { - GC.ReRegisterForFinalize(this); - } - } - - #endregion - } - - internal sealed class PinnableBufferCacheEventSource - { - public static readonly PinnableBufferCacheEventSource Log = new PinnableBufferCacheEventSource(); - - public bool IsEnabled() { return false; } - public void DebugMessage(string message) { } - public void Create(string cacheName) { } - public void AllocateBuffer(string cacheName, ulong objectId, int objectHash, int objectGen, int freeCountAfter) { } - public void AllocateBufferFromNotGen2(string cacheName, int notGen2CountAfter) { } - public void AllocateBufferCreatingNewBuffers(string cacheName, int totalBuffsBefore, int objectCount) { } - public void AllocateBufferAged(string cacheName, int agedCount) { } - public void AllocateBufferFreeListEmpty(string cacheName, int notGen2CountBefore) { } - public void FreeBuffer(string cacheName, ulong objectId, int objectHash, int freeCountBefore) { } - public void FreeBufferStillTooYoung(string cacheName, int notGen2CountBefore) { } - public void TrimCheck(string cacheName, int totalBuffs, bool neededMoreThanFreeList, int deltaMSec) { } - public void TrimFree(string cacheName, int totalBuffs, int freeListCount, int toBeFreed) { } - public void TrimExperiment(string cacheName, int totalBuffs, int freeListCount, int numTrimTrial) { } - public void TrimFreeSizeOK(string cacheName, int totalBuffs, int freeListCount) { } - public void TrimFlush(string cacheName, int totalBuffs, int freeListCount, int notGen2CountBefore) { } - public void AgePendingBuffersResults(string cacheName, int promotedToFreeListCount, int heldBackCount) { } - public void WalkFreeListResult(string cacheName, int freeListCount, int gen0BuffersInFreeList) { } - - static internal ulong AddressOf(object obj) - { - return 0; - } - } -} |