DefaultArrayPool.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. #if NETCORE
  5. using System;
  6. namespace SharpCompress.Buffers
  7. {
  8. internal sealed partial class DefaultArrayPool<T> : ArrayPool<T>
  9. {
  10. /// <summary>The default maximum length of each array in the pool (2^20).</summary>
  11. private const int DefaultMaxArrayLength = 1024 * 1024;
  12. /// <summary>The default maximum number of arrays per bucket that are available for rent.</summary>
  13. private const int DefaultMaxNumberOfArraysPerBucket = 50;
  14. /// <summary>Lazily-allocated empty array used when arrays of length 0 are requested.</summary>
  15. private static T[] s_emptyArray; // we support contracts earlier than those with Array.Empty<T>()
  16. private readonly Bucket[] _buckets;
  17. internal DefaultArrayPool() : this(DefaultMaxArrayLength, DefaultMaxNumberOfArraysPerBucket)
  18. {
  19. }
  20. internal DefaultArrayPool(int maxArrayLength, int maxArraysPerBucket)
  21. {
  22. if (maxArrayLength <= 0)
  23. {
  24. throw new ArgumentOutOfRangeException(nameof(maxArrayLength));
  25. }
  26. if (maxArraysPerBucket <= 0)
  27. {
  28. throw new ArgumentOutOfRangeException(nameof(maxArraysPerBucket));
  29. }
  30. // Our bucketing algorithm has a min length of 2^4 and a max length of 2^30.
  31. // Constrain the actual max used to those values.
  32. const int MinimumArrayLength = 0x10, MaximumArrayLength = 0x40000000;
  33. if (maxArrayLength > MaximumArrayLength)
  34. {
  35. maxArrayLength = MaximumArrayLength;
  36. }
  37. else if (maxArrayLength < MinimumArrayLength)
  38. {
  39. maxArrayLength = MinimumArrayLength;
  40. }
  41. // Create the buckets.
  42. int poolId = Id;
  43. int maxBuckets = Utilities.SelectBucketIndex(maxArrayLength);
  44. var buckets = new Bucket[maxBuckets + 1];
  45. for (int i = 0; i < buckets.Length; i++)
  46. {
  47. buckets[i] = new Bucket(Utilities.GetMaxSizeForBucket(i), maxArraysPerBucket, poolId);
  48. }
  49. _buckets = buckets;
  50. }
  51. /// <summary>Gets an ID for the pool to use with events.</summary>
  52. private int Id => GetHashCode();
  53. public override T[] Rent(int minimumLength)
  54. {
  55. // Arrays can't be smaller than zero. We allow requesting zero-length arrays (even though
  56. // pooling such an array isn't valuable) as it's a valid length array, and we want the pool
  57. // to be usable in general instead of using `new`, even for computed lengths.
  58. if (minimumLength < 0)
  59. {
  60. throw new ArgumentOutOfRangeException(nameof(minimumLength));
  61. }
  62. else if (minimumLength == 0)
  63. {
  64. // No need for events with the empty array. Our pool is effectively infinite
  65. // and we'll never allocate for rents and never store for returns.
  66. return s_emptyArray ?? (s_emptyArray = new T[0]);
  67. }
  68. T[] buffer = null;
  69. int index = Utilities.SelectBucketIndex(minimumLength);
  70. if (index < _buckets.Length)
  71. {
  72. // Search for an array starting at the 'index' bucket. If the bucket is empty, bump up to the
  73. // next higher bucket and try that one, but only try at most a few buckets.
  74. const int MaxBucketsToTry = 2;
  75. int i = index;
  76. do
  77. {
  78. // Attempt to rent from the bucket. If we get a buffer from it, return it.
  79. buffer = _buckets[i].Rent();
  80. if (buffer != null)
  81. {
  82. return buffer;
  83. }
  84. }
  85. while (++i < _buckets.Length && i != index + MaxBucketsToTry);
  86. // The pool was exhausted for this buffer size. Allocate a new buffer with a size corresponding
  87. // to the appropriate bucket.
  88. buffer = new T[_buckets[index]._bufferLength];
  89. }
  90. else
  91. {
  92. // The request was for a size too large for the pool. Allocate an array of exactly the requested length.
  93. // When it's returned to the pool, we'll simply throw it away.
  94. buffer = new T[minimumLength];
  95. }
  96. return buffer;
  97. }
  98. public override void Return(T[] array, bool clearArray = false)
  99. {
  100. if (array == null)
  101. {
  102. throw new ArgumentNullException(nameof(array));
  103. }
  104. else if (array.Length == 0)
  105. {
  106. // Ignore empty arrays. When a zero-length array is rented, we return a singleton
  107. // rather than actually taking a buffer out of the lowest bucket.
  108. return;
  109. }
  110. // Determine with what bucket this array length is associated
  111. int bucket = Utilities.SelectBucketIndex(array.Length);
  112. // If we can tell that the buffer was allocated, drop it. Otherwise, check if we have space in the pool
  113. if (bucket < _buckets.Length)
  114. {
  115. // Clear the array if the user requests
  116. if (clearArray)
  117. {
  118. Array.Clear(array, 0, array.Length);
  119. }
  120. // Return the buffer to its bucket. In the future, we might consider having Return return false
  121. // instead of dropping a bucket, in which case we could try to return to a lower-sized bucket,
  122. // just as how in Rent we allow renting from a higher-sized bucket.
  123. _buckets[bucket].Return(array);
  124. }
  125. }
  126. }
  127. }
  128. #endif