VariableSizedBufferPool.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. using System;
  2. using System.Collections.Generic;
  3. namespace BestHTTP.Extensions
  4. {
  5. /// <summary>
  6. /// Private data struct that contains the size <-> byte arrays mapping.
  7. /// </summary>
  8. struct BufferStore
  9. {
  10. /// <summary>
  11. /// Size/length of the arrays stored in the buffers.
  12. /// </summary>
  13. public readonly long Size;
  14. /// <summary>
  15. ///
  16. /// </summary>
  17. public List<BufferDesc> buffers;
  18. public BufferStore(long size)
  19. {
  20. this.Size = size;
  21. this.buffers = new List<BufferDesc>();
  22. }
  23. /// <summary>
  24. /// Create a new store with its first byte[] to store.
  25. /// </summary>
  26. public BufferStore(long size, byte[] buffer)
  27. : this(size)
  28. {
  29. this.buffers.Add(new BufferDesc(buffer));
  30. }
  31. }
  32. struct BufferDesc
  33. {
  34. public static readonly BufferDesc Empty = new BufferDesc(null);
  35. /// <summary>
  36. /// The actual reference to the stored byte array.
  37. /// </summary>
  38. public byte[] buffer;
  39. /// <summary>
  40. /// When the buffer is put back to the pool. Based on this value the pool will calculate the age of the buffer.
  41. /// </summary>
  42. public DateTime released;
  43. public BufferDesc(byte[] buff)
  44. {
  45. this.buffer = buff;
  46. this.released = DateTime.UtcNow;
  47. }
  48. }
  49. public static class VariableSizedBufferPool
  50. {
  51. public static readonly byte[] NoData = new byte[0];
  52. /// <summary>
  53. /// Setting this property to false the pooling mechanism can be disabled.
  54. /// </summary>
  55. public static bool IsEnabled {
  56. get { return _isEnabled; }
  57. set
  58. {
  59. _isEnabled = value;
  60. // When set to non-enabled remove all stored entries
  61. if (!_isEnabled)
  62. Clear();
  63. }
  64. }
  65. public static volatile bool _isEnabled = true;
  66. /// <summary>
  67. /// Buffer entries that released back to the pool and older than this value are moved when next maintenance is triggered.
  68. /// </summary>
  69. public static TimeSpan RemoveOlderThan = TimeSpan.FromSeconds(30);
  70. /// <summary>
  71. /// How often pool maintenance must run.
  72. /// </summary>
  73. public static TimeSpan RunMaintenanceEvery = TimeSpan.FromSeconds(10);
  74. /// <summary>
  75. /// Minumum buffer size that the plugin will allocate when the requested size is smaller than this value, and canBeLarger is set to true.
  76. /// </summary>
  77. public static long MinBufferSize = 256;
  78. /// <summary>
  79. /// Maximum size of a buffer that the plugin will store.
  80. /// </summary>
  81. public static long MaxBufferSize = long.MaxValue;
  82. /// <summary>
  83. /// Maximum accomulated size of the stored buffers.
  84. /// </summary>
  85. public static long MaxPoolSize = 10 * 1024 * 1024;
  86. /// <summary>
  87. /// Whether to remove empty buffer stores from the free list.
  88. /// </summary>
  89. public static bool RemoveEmptyLists = true;
  90. /// <summary>
  91. /// If it set to true and a byte[] is released more than once it will log out an error.
  92. /// </summary>
  93. public static bool IsDoubleReleaseCheckEnabled = false;
  94. // It must be sorted by buffer size!
  95. private static List<BufferStore> FreeBuffers = new List<BufferStore>();
  96. private static DateTime lastMaintenance = DateTime.MinValue;
  97. // Statistics
  98. private static volatile int PoolSize = 0;
  99. private static volatile uint GetBuffers = 0;
  100. private static volatile uint ReleaseBuffers = 0;
  101. private static System.Text.StringBuilder statiscticsBuilder = new System.Text.StringBuilder();
  102. static VariableSizedBufferPool()
  103. {
  104. #if UNITY_EDITOR
  105. IsDoubleReleaseCheckEnabled = true;
  106. #else
  107. IsDoubleReleaseCheckEnabled = false;
  108. #endif
  109. }
  110. /// <summary>
  111. /// Get byte[] from the pool. If canBeLarge is true, the returned buffer might be larger than the requested size.
  112. /// </summary>
  113. public static byte[] Get(long size, bool canBeLarger)
  114. {
  115. if (!_isEnabled)
  116. return new byte[size];
  117. // Return a fix reference for 0 length requests. Any resize call (even Array.Resize) creates a new reference
  118. // so we are safe to expose it to multiple callers.
  119. if (size == 0)
  120. return VariableSizedBufferPool.NoData;
  121. lock (FreeBuffers)
  122. {
  123. if (FreeBuffers.Count == 0)
  124. return new byte[size];
  125. BufferDesc bufferDesc = FindFreeBuffer(size, canBeLarger);
  126. if (bufferDesc.buffer == null)
  127. {
  128. if (canBeLarger)
  129. {
  130. if (size < MinBufferSize)
  131. size = MinBufferSize;
  132. else if (!IsPowerOfTwo(size))
  133. size = NextPowerOf2(size);
  134. }
  135. return new byte[size];
  136. }
  137. else
  138. GetBuffers++;
  139. PoolSize -= bufferDesc.buffer.Length;
  140. return bufferDesc.buffer;
  141. }
  142. }
  143. /// <summary>
  144. /// Release a list of byte arrays back to the pool
  145. /// </summary>
  146. public static void Release(List<byte[]> buffers)
  147. {
  148. if (!_isEnabled || buffers == null || buffers.Count == 0)
  149. return;
  150. for (int i = 0; i < buffers.Count; ++i)
  151. Release(buffers[i]);
  152. }
  153. /// <summary>
  154. /// Release back a byte array to the pool.
  155. /// </summary>
  156. public static void Release(byte[] buffer)
  157. {
  158. if (!_isEnabled || buffer == null)
  159. return;
  160. int size = buffer.Length;
  161. if (size == 0 || size > MaxBufferSize)
  162. return;
  163. lock (FreeBuffers)
  164. {
  165. if (PoolSize + size > MaxPoolSize)
  166. return;
  167. PoolSize += size;
  168. ReleaseBuffers++;
  169. AddFreeBuffer(buffer);
  170. }
  171. }
  172. /// <summary>
  173. /// Resize a byte array. It will release the old one to the pool, and the new one is from the pool too.
  174. /// </summary>
  175. public static byte[] Resize(ref byte[] buffer, int newSize, bool canBeLarger)
  176. {
  177. if (!_isEnabled)
  178. {
  179. Array.Resize<byte>(ref buffer, newSize);
  180. return buffer;
  181. }
  182. byte[] newBuf = VariableSizedBufferPool.Get(newSize, canBeLarger);
  183. Array.Copy(buffer, 0, newBuf, 0, Math.Min(newBuf.Length, buffer.Length));
  184. VariableSizedBufferPool.Release(buffer);
  185. return buffer = newBuf;
  186. }
  187. /// <summary>
  188. /// Get textual statistics about the buffer pool.
  189. /// </summary>
  190. public static string GetStatistics(bool showEmptyBuffers = true)
  191. {
  192. lock (FreeBuffers)
  193. {
  194. statiscticsBuilder.Length = 0;
  195. statiscticsBuilder.AppendFormat("Pooled array reused count: {0:N0}\n", GetBuffers);
  196. statiscticsBuilder.AppendFormat("Release call count: {0:N0}\n", ReleaseBuffers);
  197. statiscticsBuilder.AppendFormat("PoolSize: {0:N0}\n", PoolSize);
  198. statiscticsBuilder.AppendFormat("Buffers: {0}\n", FreeBuffers.Count);
  199. for (int i = 0; i < FreeBuffers.Count; ++i)
  200. {
  201. BufferStore store = FreeBuffers[i];
  202. List<BufferDesc> buffers = store.buffers;
  203. if (showEmptyBuffers || buffers.Count > 0)
  204. statiscticsBuilder.AppendFormat("- Size: {0:N0} Count: {1:N0}\n", store.Size, buffers.Count);
  205. }
  206. return statiscticsBuilder.ToString();
  207. }
  208. }
  209. /// <summary>
  210. /// Remove all stored entries instantly.
  211. /// </summary>
  212. public static void Clear()
  213. {
  214. lock (FreeBuffers)
  215. {
  216. FreeBuffers.Clear();
  217. PoolSize = 0;
  218. }
  219. }
  220. /// <summary>
  221. /// Internal function called by the plugin the remove old, non-used buffers.
  222. /// </summary>
  223. internal static void Maintain()
  224. {
  225. DateTime now = DateTime.UtcNow;
  226. if (!_isEnabled || lastMaintenance + RunMaintenanceEvery > now)
  227. return;
  228. lastMaintenance = now;
  229. DateTime olderThan = now - RemoveOlderThan;
  230. lock (FreeBuffers)
  231. {
  232. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  233. HTTPManager.Logger.Information("VariableSizedBufferPool", "Before Maintain: " + GetStatistics());
  234. for (int i = 0; i < FreeBuffers.Count; ++i)
  235. {
  236. BufferStore store = FreeBuffers[i];
  237. List<BufferDesc> buffers = store.buffers;
  238. for (int cv = buffers.Count - 1; cv >= 0; cv--)
  239. {
  240. BufferDesc desc = buffers[cv];
  241. if (desc.released < olderThan)
  242. {
  243. // buffers stores available buffers ascending by age. So, when we find an old enough, we can
  244. // delete all entries in the [0..cv] range.
  245. int removeCount = cv + 1;
  246. buffers.RemoveRange(0, removeCount);
  247. PoolSize -= (int)(removeCount * store.Size);
  248. break;
  249. }
  250. }
  251. if (RemoveEmptyLists && buffers.Count == 0)
  252. FreeBuffers.RemoveAt(i--);
  253. }
  254. if (HTTPManager.Logger.Level == Logger.Loglevels.All)
  255. HTTPManager.Logger.Information("VariableSizedBufferPool", "After Maintain: " + GetStatistics());
  256. }
  257. }
  258. #region Private helper functions
  259. private static bool IsPowerOfTwo(long x)
  260. {
  261. return (x & (x - 1)) == 0;
  262. }
  263. private static long NextPowerOf2(long x)
  264. {
  265. long pow = 1;
  266. while (pow <= x)
  267. pow *= 2;
  268. return pow;
  269. }
  270. private static BufferDesc FindFreeBuffer(long size, bool canBeLarger)
  271. {
  272. for (int i = 0; i < FreeBuffers.Count; ++i)
  273. {
  274. BufferStore store = FreeBuffers[i];
  275. if (store.buffers.Count > 0 && (store.Size == size || (canBeLarger && store.Size > size)))
  276. {
  277. // Getting the last one has two desired effect:
  278. // 1.) RemoveAt should be quicker as it don't have to move all the remaining entries
  279. // 2.) Old, non-used buffers will age. Getting a buffer and putting it back will not keep buffers fresh.
  280. BufferDesc lastFree = store.buffers[store.buffers.Count - 1];
  281. store.buffers.RemoveAt(store.buffers.Count - 1);
  282. return lastFree;
  283. }
  284. }
  285. return BufferDesc.Empty;
  286. }
  287. private static void AddFreeBuffer(byte[] buffer)
  288. {
  289. int bufferLength = buffer.Length;
  290. for (int i = 0; i < FreeBuffers.Count; ++i)
  291. {
  292. BufferStore store = FreeBuffers[i];
  293. if (store.Size == bufferLength)
  294. {
  295. // We highly assume here that every buffer will be released only once.
  296. // Checking for double-release would mean that we have to do another O(n) operation, where n is the
  297. // count of the store's elements.
  298. if (IsDoubleReleaseCheckEnabled)
  299. for (int cv = 0; cv < store.buffers.Count; ++cv)
  300. if (store.buffers[cv].buffer == buffer)
  301. {
  302. HTTPManager.Logger.Error("VariableSizedBufferPool", "Buffer already added to the pool!");
  303. return;
  304. }
  305. store.buffers.Add(new BufferDesc(buffer));
  306. return;
  307. }
  308. if (store.Size > bufferLength)
  309. {
  310. FreeBuffers.Insert(i, new BufferStore(bufferLength, buffer));
  311. return;
  312. }
  313. }
  314. // When we reach this point, there's no same sized or larger BufferStore present, so we have to add a new one
  315. // to the endo of our list.
  316. FreeBuffers.Add(new BufferStore(bufferLength, buffer));
  317. }
  318. #endregion
  319. }
  320. }