InternalBufferManager.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. //----------------------------------------------------------------------------
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. //----------------------------------------------------------------------------
  4. namespace System.Runtime
  5. {
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Threading;
  9. #if DEBUG
  10. using System.Collections.Concurrent;
  11. using System.Diagnostics;
  12. using System.Globalization;
  13. using System.Security;
  14. using System.Security.Permissions;
  15. #endif //DEBUG
  16. abstract class InternalBufferManager
  17. {
  18. protected InternalBufferManager()
  19. {
  20. }
  21. public abstract byte[] TakeBuffer(int bufferSize);
  22. public abstract void ReturnBuffer(byte[] buffer);
  23. public abstract void Clear();
  24. public static InternalBufferManager Create(long maxBufferPoolSize, int maxBufferSize)
  25. {
  26. if (maxBufferPoolSize == 0)
  27. {
  28. return GCBufferManager.Value;
  29. }
  30. else
  31. {
  32. Fx.Assert(maxBufferPoolSize > 0 && maxBufferSize >= 0, "bad params, caller should verify");
  33. return new PooledBufferManager(maxBufferPoolSize, maxBufferSize);
  34. }
  35. }
  36. class PooledBufferManager : InternalBufferManager
  37. {
  38. const int minBufferSize = 128;
  39. const int maxMissesBeforeTuning = 8;
  40. const int initialBufferCount = 1;
  41. readonly object tuningLock;
  42. int[] bufferSizes;
  43. BufferPool[] bufferPools;
  44. long memoryLimit;
  45. long remainingMemory;
  46. bool areQuotasBeingTuned;
  47. int totalMisses;
  48. #if DEBUG
  49. ConcurrentDictionary<int, string> buffersPooled = new ConcurrentDictionary<int, string>();
  50. #endif //DEBUG
  51. public PooledBufferManager(long maxMemoryToPool, int maxBufferSize)
  52. {
  53. this.tuningLock = new object();
  54. this.memoryLimit = maxMemoryToPool;
  55. this.remainingMemory = maxMemoryToPool;
  56. List<BufferPool> bufferPoolList = new List<BufferPool>();
  57. for (int bufferSize = minBufferSize;;)
  58. {
  59. long bufferCountLong = this.remainingMemory / bufferSize;
  60. int bufferCount = bufferCountLong > int.MaxValue ? int.MaxValue : (int)bufferCountLong;
  61. if (bufferCount > initialBufferCount)
  62. {
  63. bufferCount = initialBufferCount;
  64. }
  65. bufferPoolList.Add(BufferPool.CreatePool(bufferSize, bufferCount));
  66. this.remainingMemory -= (long)bufferCount * bufferSize;
  67. if (bufferSize >= maxBufferSize)
  68. {
  69. break;
  70. }
  71. long newBufferSizeLong = (long)bufferSize * 2;
  72. if (newBufferSizeLong > (long)maxBufferSize)
  73. {
  74. bufferSize = maxBufferSize;
  75. }
  76. else
  77. {
  78. bufferSize = (int)newBufferSizeLong;
  79. }
  80. }
  81. this.bufferPools = bufferPoolList.ToArray();
  82. this.bufferSizes = new int[bufferPools.Length];
  83. for (int i = 0; i < bufferPools.Length; i++)
  84. {
  85. this.bufferSizes[i] = bufferPools[i].BufferSize;
  86. }
  87. }
  88. public override void Clear()
  89. {
  90. #if DEBUG
  91. this.buffersPooled.Clear();
  92. #endif //DEBUG
  93. for (int i = 0; i < this.bufferPools.Length; i++)
  94. {
  95. BufferPool bufferPool = this.bufferPools[i];
  96. bufferPool.Clear();
  97. }
  98. }
  99. void ChangeQuota(ref BufferPool bufferPool, int delta)
  100. {
  101. if (TraceCore.BufferPoolChangeQuotaIsEnabled(Fx.Trace))
  102. {
  103. TraceCore.BufferPoolChangeQuota(Fx.Trace, bufferPool.BufferSize, delta);
  104. }
  105. BufferPool oldBufferPool = bufferPool;
  106. int newLimit = oldBufferPool.Limit + delta;
  107. BufferPool newBufferPool = BufferPool.CreatePool(oldBufferPool.BufferSize, newLimit);
  108. for (int i = 0; i < newLimit; i++)
  109. {
  110. byte[] buffer = oldBufferPool.Take();
  111. if (buffer == null)
  112. {
  113. break;
  114. }
  115. newBufferPool.Return(buffer);
  116. newBufferPool.IncrementCount();
  117. }
  118. this.remainingMemory -= oldBufferPool.BufferSize * delta;
  119. bufferPool = newBufferPool;
  120. }
  121. void DecreaseQuota(ref BufferPool bufferPool)
  122. {
  123. ChangeQuota(ref bufferPool, -1);
  124. }
  125. int FindMostExcessivePool()
  126. {
  127. long maxBytesInExcess = 0;
  128. int index = -1;
  129. for (int i = 0; i < this.bufferPools.Length; i++)
  130. {
  131. BufferPool bufferPool = this.bufferPools[i];
  132. if (bufferPool.Peak < bufferPool.Limit)
  133. {
  134. long bytesInExcess = (bufferPool.Limit - bufferPool.Peak) * (long)bufferPool.BufferSize;
  135. if (bytesInExcess > maxBytesInExcess)
  136. {
  137. index = i;
  138. maxBytesInExcess = bytesInExcess;
  139. }
  140. }
  141. }
  142. return index;
  143. }
  144. int FindMostStarvedPool()
  145. {
  146. long maxBytesMissed = 0;
  147. int index = -1;
  148. for (int i = 0; i < this.bufferPools.Length; i++)
  149. {
  150. BufferPool bufferPool = this.bufferPools[i];
  151. if (bufferPool.Peak == bufferPool.Limit)
  152. {
  153. long bytesMissed = bufferPool.Misses * (long)bufferPool.BufferSize;
  154. if (bytesMissed > maxBytesMissed)
  155. {
  156. index = i;
  157. maxBytesMissed = bytesMissed;
  158. }
  159. }
  160. }
  161. return index;
  162. }
  163. BufferPool FindPool(int desiredBufferSize)
  164. {
  165. for (int i = 0; i < this.bufferSizes.Length; i++)
  166. {
  167. if (desiredBufferSize <= this.bufferSizes[i])
  168. {
  169. return this.bufferPools[i];
  170. }
  171. }
  172. return null;
  173. }
  174. void IncreaseQuota(ref BufferPool bufferPool)
  175. {
  176. ChangeQuota(ref bufferPool, 1);
  177. }
  178. public override void ReturnBuffer(byte[] buffer)
  179. {
  180. Fx.Assert(buffer != null, "caller must verify");
  181. #if DEBUG
  182. int hash = buffer.GetHashCode();
  183. if (!this.buffersPooled.TryAdd(hash, CaptureStackTrace()))
  184. {
  185. string originalStack;
  186. if (!this.buffersPooled.TryGetValue(hash, out originalStack))
  187. {
  188. originalStack = "NULL";
  189. }
  190. Fx.Assert(
  191. string.Format(
  192. CultureInfo.InvariantCulture,
  193. "Buffer '{0}' has already been returned to the bufferManager before. Previous CallStack: {1} Current CallStack: {2}",
  194. hash,
  195. originalStack,
  196. CaptureStackTrace()));
  197. }
  198. #endif //DEBUG
  199. BufferPool bufferPool = FindPool(buffer.Length);
  200. if (bufferPool != null)
  201. {
  202. if (buffer.Length != bufferPool.BufferSize)
  203. {
  204. throw Fx.Exception.Argument("buffer", InternalSR.BufferIsNotRightSizeForBufferManager);
  205. }
  206. if (bufferPool.Return(buffer))
  207. {
  208. bufferPool.IncrementCount();
  209. }
  210. }
  211. }
  212. public override byte[] TakeBuffer(int bufferSize)
  213. {
  214. Fx.Assert(bufferSize >= 0, "caller must ensure a non-negative argument");
  215. BufferPool bufferPool = FindPool(bufferSize);
  216. byte[] returnValue;
  217. if (bufferPool != null)
  218. {
  219. byte[] buffer = bufferPool.Take();
  220. if (buffer != null)
  221. {
  222. bufferPool.DecrementCount();
  223. returnValue = buffer;
  224. }
  225. else
  226. {
  227. if (bufferPool.Peak == bufferPool.Limit)
  228. {
  229. bufferPool.Misses++;
  230. if (++totalMisses >= maxMissesBeforeTuning)
  231. {
  232. TuneQuotas();
  233. }
  234. }
  235. if (TraceCore.BufferPoolAllocationIsEnabled(Fx.Trace))
  236. {
  237. TraceCore.BufferPoolAllocation(Fx.Trace, bufferPool.BufferSize);
  238. }
  239. returnValue = Fx.AllocateByteArray(bufferPool.BufferSize);
  240. }
  241. }
  242. else
  243. {
  244. if (TraceCore.BufferPoolAllocationIsEnabled(Fx.Trace))
  245. {
  246. TraceCore.BufferPoolAllocation(Fx.Trace, bufferSize);
  247. }
  248. returnValue = Fx.AllocateByteArray(bufferSize);
  249. }
  250. #if DEBUG
  251. string dummy;
  252. this.buffersPooled.TryRemove(returnValue.GetHashCode(), out dummy);
  253. #endif //DEBUG
  254. return returnValue;
  255. }
  256. #if DEBUG
  257. [SecuritySafeCritical]
  258. [PermissionSet(SecurityAction.Assert, Unrestricted = true)]
  259. private static string CaptureStackTrace()
  260. {
  261. return new StackTrace(true).ToString();
  262. }
  263. #endif //DEBUG
  264. void TuneQuotas()
  265. {
  266. if (this.areQuotasBeingTuned)
  267. {
  268. return;
  269. }
  270. bool lockHeld = false;
  271. try
  272. {
  273. Monitor.TryEnter(this.tuningLock, ref lockHeld);
  274. // Don't bother if another thread already has the lock
  275. if (!lockHeld || this.areQuotasBeingTuned)
  276. {
  277. return;
  278. }
  279. this.areQuotasBeingTuned = true;
  280. }
  281. finally
  282. {
  283. if (lockHeld)
  284. {
  285. Monitor.Exit(this.tuningLock);
  286. }
  287. }
  288. // find the "poorest" pool
  289. int starvedIndex = FindMostStarvedPool();
  290. if (starvedIndex >= 0)
  291. {
  292. BufferPool starvedBufferPool = this.bufferPools[starvedIndex];
  293. if (this.remainingMemory < starvedBufferPool.BufferSize)
  294. {
  295. // find the "richest" pool
  296. int excessiveIndex = FindMostExcessivePool();
  297. if (excessiveIndex >= 0)
  298. {
  299. // steal from the richest
  300. DecreaseQuota(ref this.bufferPools[excessiveIndex]);
  301. }
  302. }
  303. if (this.remainingMemory >= starvedBufferPool.BufferSize)
  304. {
  305. // give to the poorest
  306. IncreaseQuota(ref this.bufferPools[starvedIndex]);
  307. }
  308. }
  309. // reset statistics
  310. for (int i = 0; i < this.bufferPools.Length; i++)
  311. {
  312. BufferPool bufferPool = this.bufferPools[i];
  313. bufferPool.Misses = 0;
  314. }
  315. this.totalMisses = 0;
  316. this.areQuotasBeingTuned = false;
  317. }
  318. abstract class BufferPool
  319. {
  320. int bufferSize;
  321. int count;
  322. int limit;
  323. int misses;
  324. int peak;
  325. public BufferPool(int bufferSize, int limit)
  326. {
  327. this.bufferSize = bufferSize;
  328. this.limit = limit;
  329. }
  330. public int BufferSize
  331. {
  332. get { return this.bufferSize; }
  333. }
  334. public int Limit
  335. {
  336. get { return this.limit; }
  337. }
  338. public int Misses
  339. {
  340. get { return this.misses; }
  341. set { this.misses = value; }
  342. }
  343. public int Peak
  344. {
  345. get { return this.peak; }
  346. }
  347. public void Clear()
  348. {
  349. this.OnClear();
  350. this.count = 0;
  351. }
  352. public void DecrementCount()
  353. {
  354. int newValue = this.count - 1;
  355. if (newValue >= 0)
  356. {
  357. this.count = newValue;
  358. }
  359. }
  360. public void IncrementCount()
  361. {
  362. int newValue = this.count + 1;
  363. if (newValue <= this.limit)
  364. {
  365. this.count = newValue;
  366. if (newValue > this.peak)
  367. {
  368. this.peak = newValue;
  369. }
  370. }
  371. }
  372. internal abstract byte[] Take();
  373. internal abstract bool Return(byte[] buffer);
  374. internal abstract void OnClear();
  375. internal static BufferPool CreatePool(int bufferSize, int limit)
  376. {
  377. // To avoid many buffer drops during training of large objects which
  378. // get allocated on the LOH, we use the LargeBufferPool and for
  379. // bufferSize < 85000, the SynchronizedPool. However if bufferSize < 85000
  380. // and (bufferSize + array-overhead) > 85000, this would still use
  381. // the SynchronizedPool even though it is allocated on the LOH.
  382. if (bufferSize < 85000)
  383. {
  384. return new SynchronizedBufferPool(bufferSize, limit);
  385. }
  386. else
  387. {
  388. return new LargeBufferPool(bufferSize, limit);
  389. }
  390. }
  391. class SynchronizedBufferPool : BufferPool
  392. {
  393. SynchronizedPool<byte[]> innerPool;
  394. internal SynchronizedBufferPool(int bufferSize, int limit)
  395. : base(bufferSize, limit)
  396. {
  397. this.innerPool = new SynchronizedPool<byte[]>(limit);
  398. }
  399. internal override void OnClear()
  400. {
  401. this.innerPool.Clear();
  402. }
  403. internal override byte[] Take()
  404. {
  405. return this.innerPool.Take();
  406. }
  407. internal override bool Return(byte[] buffer)
  408. {
  409. return this.innerPool.Return(buffer);
  410. }
  411. }
  412. class LargeBufferPool : BufferPool
  413. {
  414. Stack<byte[]> items;
  415. internal LargeBufferPool(int bufferSize, int limit)
  416. : base(bufferSize, limit)
  417. {
  418. this.items = new Stack<byte[]>(limit);
  419. }
  420. object ThisLock
  421. {
  422. get
  423. {
  424. return this.items;
  425. }
  426. }
  427. internal override void OnClear()
  428. {
  429. lock (ThisLock)
  430. {
  431. this.items.Clear();
  432. }
  433. }
  434. internal override byte[] Take()
  435. {
  436. lock (ThisLock)
  437. {
  438. if (this.items.Count > 0)
  439. {
  440. return this.items.Pop();
  441. }
  442. }
  443. return null;
  444. }
  445. internal override bool Return(byte[] buffer)
  446. {
  447. lock (ThisLock)
  448. {
  449. if (this.items.Count < this.Limit)
  450. {
  451. this.items.Push(buffer);
  452. return true;
  453. }
  454. }
  455. return false;
  456. }
  457. }
  458. }
  459. }
  460. class GCBufferManager : InternalBufferManager
  461. {
  462. static GCBufferManager value = new GCBufferManager();
  463. GCBufferManager()
  464. {
  465. }
  466. public static GCBufferManager Value
  467. {
  468. get { return value; }
  469. }
  470. public override void Clear()
  471. {
  472. }
  473. public override byte[] TakeBuffer(int bufferSize)
  474. {
  475. return Fx.AllocateByteArray(bufferSize);
  476. }
  477. public override void ReturnBuffer(byte[] buffer)
  478. {
  479. // do nothing, GC will reclaim this buffer
  480. }
  481. }
  482. }
  483. }