e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
572 lines
18 KiB
C#
572 lines
18 KiB
C#
//----------------------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//----------------------------------------------------------------------------
|
|
namespace System.Runtime
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
|
|
#if DEBUG
|
|
using System.Collections.Concurrent;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Security;
|
|
using System.Security.Permissions;
|
|
#endif //DEBUG
|
|
|
|
abstract class InternalBufferManager
|
|
{
|
|
protected InternalBufferManager()
|
|
{
|
|
}
|
|
|
|
public abstract byte[] TakeBuffer(int bufferSize);
|
|
public abstract void ReturnBuffer(byte[] buffer);
|
|
public abstract void Clear();
|
|
|
|
public static InternalBufferManager Create(long maxBufferPoolSize, int maxBufferSize)
|
|
{
|
|
if (maxBufferPoolSize == 0)
|
|
{
|
|
return GCBufferManager.Value;
|
|
}
|
|
else
|
|
{
|
|
Fx.Assert(maxBufferPoolSize > 0 && maxBufferSize >= 0, "bad params, caller should verify");
|
|
return new PooledBufferManager(maxBufferPoolSize, maxBufferSize);
|
|
}
|
|
}
|
|
|
|
class PooledBufferManager : InternalBufferManager
|
|
{
|
|
const int minBufferSize = 128;
|
|
const int maxMissesBeforeTuning = 8;
|
|
const int initialBufferCount = 1;
|
|
readonly object tuningLock;
|
|
|
|
int[] bufferSizes;
|
|
BufferPool[] bufferPools;
|
|
long memoryLimit;
|
|
long remainingMemory;
|
|
bool areQuotasBeingTuned;
|
|
int totalMisses;
|
|
#if DEBUG
|
|
ConcurrentDictionary<int, string> buffersPooled = new ConcurrentDictionary<int, string>();
|
|
#endif //DEBUG
|
|
|
|
public PooledBufferManager(long maxMemoryToPool, int maxBufferSize)
|
|
{
|
|
this.tuningLock = new object();
|
|
this.memoryLimit = maxMemoryToPool;
|
|
this.remainingMemory = maxMemoryToPool;
|
|
List<BufferPool> bufferPoolList = new List<BufferPool>();
|
|
|
|
for (int bufferSize = minBufferSize;;)
|
|
{
|
|
long bufferCountLong = this.remainingMemory / bufferSize;
|
|
|
|
int bufferCount = bufferCountLong > int.MaxValue ? int.MaxValue : (int)bufferCountLong;
|
|
|
|
if (bufferCount > initialBufferCount)
|
|
{
|
|
bufferCount = initialBufferCount;
|
|
}
|
|
|
|
bufferPoolList.Add(BufferPool.CreatePool(bufferSize, bufferCount));
|
|
|
|
this.remainingMemory -= (long)bufferCount * bufferSize;
|
|
|
|
if (bufferSize >= maxBufferSize)
|
|
{
|
|
break;
|
|
}
|
|
|
|
long newBufferSizeLong = (long)bufferSize * 2;
|
|
|
|
if (newBufferSizeLong > (long)maxBufferSize)
|
|
{
|
|
bufferSize = maxBufferSize;
|
|
}
|
|
else
|
|
{
|
|
bufferSize = (int)newBufferSizeLong;
|
|
}
|
|
}
|
|
|
|
this.bufferPools = bufferPoolList.ToArray();
|
|
this.bufferSizes = new int[bufferPools.Length];
|
|
for (int i = 0; i < bufferPools.Length; i++)
|
|
{
|
|
this.bufferSizes[i] = bufferPools[i].BufferSize;
|
|
}
|
|
}
|
|
|
|
public override void Clear()
|
|
{
|
|
#if DEBUG
|
|
this.buffersPooled.Clear();
|
|
#endif //DEBUG
|
|
|
|
for (int i = 0; i < this.bufferPools.Length; i++)
|
|
{
|
|
BufferPool bufferPool = this.bufferPools[i];
|
|
bufferPool.Clear();
|
|
}
|
|
}
|
|
|
|
void ChangeQuota(ref BufferPool bufferPool, int delta)
|
|
{
|
|
|
|
if (TraceCore.BufferPoolChangeQuotaIsEnabled(Fx.Trace))
|
|
{
|
|
TraceCore.BufferPoolChangeQuota(Fx.Trace, bufferPool.BufferSize, delta);
|
|
}
|
|
|
|
BufferPool oldBufferPool = bufferPool;
|
|
int newLimit = oldBufferPool.Limit + delta;
|
|
BufferPool newBufferPool = BufferPool.CreatePool(oldBufferPool.BufferSize, newLimit);
|
|
for (int i = 0; i < newLimit; i++)
|
|
{
|
|
byte[] buffer = oldBufferPool.Take();
|
|
if (buffer == null)
|
|
{
|
|
break;
|
|
}
|
|
newBufferPool.Return(buffer);
|
|
newBufferPool.IncrementCount();
|
|
}
|
|
this.remainingMemory -= oldBufferPool.BufferSize * delta;
|
|
bufferPool = newBufferPool;
|
|
}
|
|
|
|
void DecreaseQuota(ref BufferPool bufferPool)
|
|
{
|
|
ChangeQuota(ref bufferPool, -1);
|
|
}
|
|
|
|
int FindMostExcessivePool()
|
|
{
|
|
long maxBytesInExcess = 0;
|
|
int index = -1;
|
|
|
|
for (int i = 0; i < this.bufferPools.Length; i++)
|
|
{
|
|
BufferPool bufferPool = this.bufferPools[i];
|
|
|
|
if (bufferPool.Peak < bufferPool.Limit)
|
|
{
|
|
long bytesInExcess = (bufferPool.Limit - bufferPool.Peak) * (long)bufferPool.BufferSize;
|
|
|
|
if (bytesInExcess > maxBytesInExcess)
|
|
{
|
|
index = i;
|
|
maxBytesInExcess = bytesInExcess;
|
|
}
|
|
}
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
int FindMostStarvedPool()
|
|
{
|
|
long maxBytesMissed = 0;
|
|
int index = -1;
|
|
|
|
for (int i = 0; i < this.bufferPools.Length; i++)
|
|
{
|
|
BufferPool bufferPool = this.bufferPools[i];
|
|
|
|
if (bufferPool.Peak == bufferPool.Limit)
|
|
{
|
|
long bytesMissed = bufferPool.Misses * (long)bufferPool.BufferSize;
|
|
|
|
if (bytesMissed > maxBytesMissed)
|
|
{
|
|
index = i;
|
|
maxBytesMissed = bytesMissed;
|
|
}
|
|
}
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
BufferPool FindPool(int desiredBufferSize)
|
|
{
|
|
for (int i = 0; i < this.bufferSizes.Length; i++)
|
|
{
|
|
if (desiredBufferSize <= this.bufferSizes[i])
|
|
{
|
|
return this.bufferPools[i];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
void IncreaseQuota(ref BufferPool bufferPool)
|
|
{
|
|
ChangeQuota(ref bufferPool, 1);
|
|
}
|
|
|
|
public override void ReturnBuffer(byte[] buffer)
|
|
{
|
|
Fx.Assert(buffer != null, "caller must verify");
|
|
|
|
#if DEBUG
|
|
int hash = buffer.GetHashCode();
|
|
if (!this.buffersPooled.TryAdd(hash, CaptureStackTrace()))
|
|
{
|
|
string originalStack;
|
|
if (!this.buffersPooled.TryGetValue(hash, out originalStack))
|
|
{
|
|
originalStack = "NULL";
|
|
}
|
|
|
|
Fx.Assert(
|
|
string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
"Buffer '{0}' has already been returned to the bufferManager before. Previous CallStack: {1} Current CallStack: {2}",
|
|
hash,
|
|
originalStack,
|
|
CaptureStackTrace()));
|
|
|
|
}
|
|
#endif //DEBUG
|
|
|
|
BufferPool bufferPool = FindPool(buffer.Length);
|
|
if (bufferPool != null)
|
|
{
|
|
if (buffer.Length != bufferPool.BufferSize)
|
|
{
|
|
throw Fx.Exception.Argument("buffer", InternalSR.BufferIsNotRightSizeForBufferManager);
|
|
}
|
|
|
|
if (bufferPool.Return(buffer))
|
|
{
|
|
bufferPool.IncrementCount();
|
|
}
|
|
}
|
|
}
|
|
|
|
public override byte[] TakeBuffer(int bufferSize)
|
|
{
|
|
Fx.Assert(bufferSize >= 0, "caller must ensure a non-negative argument");
|
|
|
|
BufferPool bufferPool = FindPool(bufferSize);
|
|
byte[] returnValue;
|
|
if (bufferPool != null)
|
|
{
|
|
byte[] buffer = bufferPool.Take();
|
|
if (buffer != null)
|
|
{
|
|
bufferPool.DecrementCount();
|
|
returnValue = buffer;
|
|
}
|
|
else
|
|
{
|
|
if (bufferPool.Peak == bufferPool.Limit)
|
|
{
|
|
bufferPool.Misses++;
|
|
if (++totalMisses >= maxMissesBeforeTuning)
|
|
{
|
|
TuneQuotas();
|
|
}
|
|
}
|
|
|
|
if (TraceCore.BufferPoolAllocationIsEnabled(Fx.Trace))
|
|
{
|
|
TraceCore.BufferPoolAllocation(Fx.Trace, bufferPool.BufferSize);
|
|
}
|
|
|
|
returnValue = Fx.AllocateByteArray(bufferPool.BufferSize);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (TraceCore.BufferPoolAllocationIsEnabled(Fx.Trace))
|
|
{
|
|
TraceCore.BufferPoolAllocation(Fx.Trace, bufferSize);
|
|
}
|
|
|
|
returnValue = Fx.AllocateByteArray(bufferSize);
|
|
}
|
|
|
|
#if DEBUG
|
|
string dummy;
|
|
this.buffersPooled.TryRemove(returnValue.GetHashCode(), out dummy);
|
|
#endif //DEBUG
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
#if DEBUG
|
|
[SecuritySafeCritical]
|
|
[PermissionSet(SecurityAction.Assert, Unrestricted = true)]
|
|
private static string CaptureStackTrace()
|
|
{
|
|
return new StackTrace(true).ToString();
|
|
}
|
|
#endif //DEBUG
|
|
|
|
void TuneQuotas()
|
|
{
|
|
if (this.areQuotasBeingTuned)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool lockHeld = false;
|
|
try
|
|
{
|
|
Monitor.TryEnter(this.tuningLock, ref lockHeld);
|
|
|
|
// Don't bother if another thread already has the lock
|
|
if (!lockHeld || this.areQuotasBeingTuned)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.areQuotasBeingTuned = true;
|
|
}
|
|
finally
|
|
{
|
|
if (lockHeld)
|
|
{
|
|
Monitor.Exit(this.tuningLock);
|
|
}
|
|
}
|
|
|
|
// find the "poorest" pool
|
|
int starvedIndex = FindMostStarvedPool();
|
|
if (starvedIndex >= 0)
|
|
{
|
|
BufferPool starvedBufferPool = this.bufferPools[starvedIndex];
|
|
|
|
if (this.remainingMemory < starvedBufferPool.BufferSize)
|
|
{
|
|
// find the "richest" pool
|
|
int excessiveIndex = FindMostExcessivePool();
|
|
if (excessiveIndex >= 0)
|
|
{
|
|
// steal from the richest
|
|
DecreaseQuota(ref this.bufferPools[excessiveIndex]);
|
|
}
|
|
}
|
|
|
|
if (this.remainingMemory >= starvedBufferPool.BufferSize)
|
|
{
|
|
// give to the poorest
|
|
IncreaseQuota(ref this.bufferPools[starvedIndex]);
|
|
}
|
|
}
|
|
|
|
// reset statistics
|
|
for (int i = 0; i < this.bufferPools.Length; i++)
|
|
{
|
|
BufferPool bufferPool = this.bufferPools[i];
|
|
bufferPool.Misses = 0;
|
|
}
|
|
|
|
this.totalMisses = 0;
|
|
this.areQuotasBeingTuned = false;
|
|
}
|
|
|
|
abstract class BufferPool
|
|
{
|
|
int bufferSize;
|
|
int count;
|
|
int limit;
|
|
int misses;
|
|
int peak;
|
|
|
|
public BufferPool(int bufferSize, int limit)
|
|
{
|
|
this.bufferSize = bufferSize;
|
|
this.limit = limit;
|
|
}
|
|
|
|
public int BufferSize
|
|
{
|
|
get { return this.bufferSize; }
|
|
}
|
|
|
|
public int Limit
|
|
{
|
|
get { return this.limit; }
|
|
}
|
|
|
|
public int Misses
|
|
{
|
|
get { return this.misses; }
|
|
set { this.misses = value; }
|
|
}
|
|
|
|
public int Peak
|
|
{
|
|
get { return this.peak; }
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
this.OnClear();
|
|
this.count = 0;
|
|
}
|
|
|
|
public void DecrementCount()
|
|
{
|
|
int newValue = this.count - 1;
|
|
if (newValue >= 0)
|
|
{
|
|
this.count = newValue;
|
|
}
|
|
}
|
|
|
|
public void IncrementCount()
|
|
{
|
|
int newValue = this.count + 1;
|
|
if (newValue <= this.limit)
|
|
{
|
|
this.count = newValue;
|
|
if (newValue > this.peak)
|
|
{
|
|
this.peak = newValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal abstract byte[] Take();
|
|
internal abstract bool Return(byte[] buffer);
|
|
internal abstract void OnClear();
|
|
|
|
internal static BufferPool CreatePool(int bufferSize, int limit)
|
|
{
|
|
// To avoid many buffer drops during training of large objects which
|
|
// get allocated on the LOH, we use the LargeBufferPool and for
|
|
// bufferSize < 85000, the SynchronizedPool. However if bufferSize < 85000
|
|
// and (bufferSize + array-overhead) > 85000, this would still use
|
|
// the SynchronizedPool even though it is allocated on the LOH.
|
|
if (bufferSize < 85000)
|
|
{
|
|
return new SynchronizedBufferPool(bufferSize, limit);
|
|
}
|
|
else
|
|
{
|
|
return new LargeBufferPool(bufferSize, limit);
|
|
}
|
|
}
|
|
|
|
class SynchronizedBufferPool : BufferPool
|
|
{
|
|
SynchronizedPool<byte[]> innerPool;
|
|
|
|
internal SynchronizedBufferPool(int bufferSize, int limit)
|
|
: base(bufferSize, limit)
|
|
{
|
|
this.innerPool = new SynchronizedPool<byte[]>(limit);
|
|
}
|
|
|
|
internal override void OnClear()
|
|
{
|
|
this.innerPool.Clear();
|
|
}
|
|
|
|
internal override byte[] Take()
|
|
{
|
|
return this.innerPool.Take();
|
|
}
|
|
|
|
internal override bool Return(byte[] buffer)
|
|
{
|
|
return this.innerPool.Return(buffer);
|
|
}
|
|
}
|
|
|
|
class LargeBufferPool : BufferPool
|
|
{
|
|
Stack<byte[]> items;
|
|
|
|
internal LargeBufferPool(int bufferSize, int limit)
|
|
: base(bufferSize, limit)
|
|
{
|
|
this.items = new Stack<byte[]>(limit);
|
|
}
|
|
|
|
object ThisLock
|
|
{
|
|
get
|
|
{
|
|
return this.items;
|
|
}
|
|
}
|
|
|
|
internal override void OnClear()
|
|
{
|
|
lock (ThisLock)
|
|
{
|
|
this.items.Clear();
|
|
}
|
|
}
|
|
|
|
internal override byte[] Take()
|
|
{
|
|
lock (ThisLock)
|
|
{
|
|
if (this.items.Count > 0)
|
|
{
|
|
return this.items.Pop();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
internal override bool Return(byte[] buffer)
|
|
{
|
|
lock (ThisLock)
|
|
{
|
|
if (this.items.Count < this.Limit)
|
|
{
|
|
this.items.Push(buffer);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class GCBufferManager : InternalBufferManager
|
|
{
|
|
static GCBufferManager value = new GCBufferManager();
|
|
|
|
GCBufferManager()
|
|
{
|
|
}
|
|
|
|
public static GCBufferManager Value
|
|
{
|
|
get { return value; }
|
|
}
|
|
|
|
public override void Clear()
|
|
{
|
|
}
|
|
|
|
public override byte[] TakeBuffer(int bufferSize)
|
|
{
|
|
return Fx.AllocateByteArray(bufferSize);
|
|
}
|
|
|
|
public override void ReturnBuffer(byte[] buffer)
|
|
{
|
|
// do nothing, GC will reclaim this buffer
|
|
}
|
|
}
|
|
}
|
|
}
|