@@ -12,8 +12,6 @@ namespace SixLabors.ImageSharp.Memory;
1212public abstract class MemoryAllocator
1313{
1414 private const int OneGigabyte = 1 << 30 ;
15- private long accumulativeAllocatedBytes ;
16- private int trackingSuppressionCount ;
1715
1816 /// <summary>
1917 /// Gets the default platform-specific global <see cref="MemoryAllocator"/> instance that
@@ -25,44 +23,9 @@ public abstract class MemoryAllocator
2523 /// </summary>
2624 public static MemoryAllocator Default { get ; } = Create ( ) ;
2725
28- /// <summary>
29- /// Gets the maximum number of bytes that can be allocated by a memory group.
30- /// </summary>
31- /// <remarks>
32- /// The allocation limit is determined by the process architecture: 4 GB for 64-bit processes and
33- /// 1 GB for 32-bit processes.
34- /// </remarks>
35- internal long MemoryGroupAllocationLimitBytes { get ; private protected set ; } = Environment . Is64BitProcess ? 4L * OneGigabyte : OneGigabyte ;
36-
37- /// <summary>
38- /// Gets the maximum allowed total allocation size, in bytes, for the current process.
39- /// </summary>
40- /// <remarks>
41- /// Defaults to <see cref="long.MaxValue"/>, effectively imposing no limit on total allocations.
42- /// This property can be set to enforce a cap on total memory usage across all allocations made through this allocator instance, providing
43- /// a safeguard against excessive memory consumption.<br/>
44- /// When the cumulative size of active allocations exceeds this limit, an <see cref="InvalidMemoryOperationException"/> will be thrown to
45- /// prevent further allocations and signal that the limit has been breached.
46- /// </remarks>
47- internal long AccumulativeAllocationLimitBytes { get ; private protected set ; } = long . MaxValue ;
26+ internal long MemoryGroupAllocationLimitBytes { get ; private set ; } = Environment . Is64BitProcess ? 4L * OneGigabyte : OneGigabyte ;
4827
49- /// <summary>
50- /// Gets the maximum size, in bytes, that can be allocated for a single buffer.
51- /// </summary>
52- /// <remarks>
53- /// The single buffer allocation limit is set to 1 GB by default.
54- /// </remarks>
55- internal int SingleBufferAllocationLimitBytes { get ; private protected set ; } = OneGigabyte ;
56-
57- /// <summary>
58- /// Gets a value indicating whether change tracking is currently suppressed for this instance.
59- /// </summary>
60- /// <remarks>
61- /// When change tracking is suppressed, modifications to the object will not be recorded or
62- /// trigger change notifications. This property is used internally to temporarily disable tracking during
63- /// batch updates or initialization.
64- /// </remarks>
65- private bool IsTrackingSuppressed => Volatile . Read ( ref this . trackingSuppressionCount ) > 0 ;
28+ internal int SingleBufferAllocationLimitBytes { get ; private set ; } = OneGigabyte ;
6629
6730 /// <summary>
6831 /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes.
@@ -90,11 +53,6 @@ public static MemoryAllocator Create(MemoryAllocatorOptions options)
9053 allocator . SingleBufferAllocationLimitBytes = ( int ) Math . Min ( allocator . SingleBufferAllocationLimitBytes , allocator . MemoryGroupAllocationLimitBytes ) ;
9154 }
9255
93- if ( options . AccumulativeAllocationLimitMegabytes . HasValue )
94- {
95- allocator . AccumulativeAllocationLimitBytes = options . AccumulativeAllocationLimitMegabytes . Value * 1024L * 1024L ;
96- }
97-
9856 return allocator ;
9957 }
10058
@@ -114,10 +72,6 @@ public abstract IMemoryOwner<T> Allocate<T>(int length, AllocationOptions option
11472 /// Releases all retained resources not being in use.
11573 /// Eg: by resetting array pools and letting GC to free the arrays.
11674 /// </summary>
117- /// <remarks>
118- /// This does not dispose active allocations; callers are responsible for disposing all
119- /// <see cref="IMemoryOwner{T}"/> instances to release memory.
120- /// </remarks>
12175 public virtual void ReleaseRetainedResources ( )
12276 {
12377 }
@@ -148,137 +102,11 @@ internal MemoryGroup<T> AllocateGroup<T>(
148102 InvalidMemoryOperationException . ThrowAllocationOverLimitException ( totalLengthInBytes , this . MemoryGroupAllocationLimitBytes ) ;
149103 }
150104
151- long totalLengthInBytesLong = ( long ) totalLengthInBytes ;
152- this . ReserveAllocation ( totalLengthInBytesLong ) ;
153-
154- using ( this . SuppressTracking ( ) )
155- {
156- try
157- {
158- MemoryGroup < T > group = this . AllocateGroupCore < T > ( totalLength , totalLengthInBytesLong , bufferAlignment , options ) ;
159- group . SetAllocationTracking ( this , totalLengthInBytesLong ) ;
160- return group ;
161- }
162- catch
163- {
164- this . ReleaseAccumulatedBytes ( totalLengthInBytesLong ) ;
165- throw ;
166- }
167- }
105+ // Cast to long is safe because we already checked that the total length is within the limit.
106+ return this . AllocateGroupCore < T > ( totalLength , ( long ) totalLengthInBytes , bufferAlignment , options ) ;
168107 }
169108
170109 internal virtual MemoryGroup < T > AllocateGroupCore < T > ( long totalLengthInElements , long totalLengthInBytes , int bufferAlignment , AllocationOptions options )
171110 where T : struct
172111 => MemoryGroup < T > . Allocate ( this , totalLengthInElements , bufferAlignment , options ) ;
173-
174- /// <summary>
175- /// Tracks the allocation of an <see cref="IMemoryOwner{T}" /> instance after reserving bytes.
176- /// </summary>
177- /// <typeparam name="T">Type of the data stored in the buffer.</typeparam>
178- /// <param name="owner">The allocation to track.</param>
179- /// <param name="lengthInBytes">The allocation size in bytes.</param>
180- /// <returns>The tracked allocation.</returns>
181- protected IMemoryOwner < T > TrackAllocation < T > ( IMemoryOwner < T > owner , ulong lengthInBytes )
182- where T : struct
183- {
184- if ( this . IsTrackingSuppressed || lengthInBytes == 0 )
185- {
186- return owner ;
187- }
188-
189- return new TrackingMemoryOwner < T > ( owner , this , ( long ) lengthInBytes ) ;
190- }
191-
192- /// <summary>
193- /// Reserves accumulative allocation bytes before creating the underlying buffer.
194- /// </summary>
195- /// <param name="lengthInBytes">The number of bytes to reserve.</param>
196- protected void ReserveAllocation ( long lengthInBytes )
197- {
198- if ( this . IsTrackingSuppressed || lengthInBytes <= 0 )
199- {
200- return ;
201- }
202-
203- long total = Interlocked . Add ( ref this . accumulativeAllocatedBytes , lengthInBytes ) ;
204- if ( total > this . AccumulativeAllocationLimitBytes )
205- {
206- _ = Interlocked . Add ( ref this . accumulativeAllocatedBytes , - lengthInBytes ) ;
207- InvalidMemoryOperationException . ThrowAllocationOverLimitException ( ( ulong ) lengthInBytes , this . AccumulativeAllocationLimitBytes ) ;
208- }
209- }
210-
211- /// <summary>
212- /// Releases accumulative allocation bytes previously tracked by this allocator.
213- /// </summary>
214- /// <param name="lengthInBytes">The number of bytes to release.</param>
215- internal void ReleaseAccumulatedBytes ( long lengthInBytes )
216- {
217- if ( lengthInBytes <= 0 )
218- {
219- return ;
220- }
221-
222- _ = Interlocked . Add ( ref this . accumulativeAllocatedBytes , - lengthInBytes ) ;
223- }
224-
225- /// <summary>
226- /// Suppresses accumulative allocation tracking for the lifetime of the returned scope.
227- /// </summary>
228- /// <returns>An <see cref="IDisposable"/> that restores tracking when disposed.</returns>
229- internal IDisposable SuppressTracking ( ) => new TrackingSuppressionScope ( this ) ;
230-
231- /// <summary>
232- /// Temporarily suppresses accumulative allocation tracking within a scope.
233- /// </summary>
234- private sealed class TrackingSuppressionScope : IDisposable
235- {
236- private MemoryAllocator ? allocator ;
237-
238- public TrackingSuppressionScope ( MemoryAllocator allocator )
239- {
240- this . allocator = allocator ;
241- _ = Interlocked . Increment ( ref allocator . trackingSuppressionCount ) ;
242- }
243-
244- public void Dispose ( )
245- {
246- if ( this . allocator != null )
247- {
248- _ = Interlocked . Decrement ( ref this . allocator . trackingSuppressionCount ) ;
249- this . allocator = null ;
250- }
251- }
252- }
253-
254- /// <summary>
255- /// Wraps an <see cref="IMemoryOwner{T}"/> to release accumulative tracking on dispose.
256- /// </summary>
257- private sealed class TrackingMemoryOwner < T > : IMemoryOwner < T >
258- where T : struct
259- {
260- private IMemoryOwner < T > ? owner ;
261- private readonly MemoryAllocator allocator ;
262- private readonly long lengthInBytes ;
263-
264- public TrackingMemoryOwner ( IMemoryOwner < T > owner , MemoryAllocator allocator , long lengthInBytes )
265- {
266- this . owner = owner ;
267- this . allocator = allocator ;
268- this . lengthInBytes = lengthInBytes ;
269- }
270-
271- public Memory < T > Memory => this . owner ? . Memory ?? Memory < T > . Empty ;
272-
273- public void Dispose ( )
274- {
275- // Ensure only one caller disposes the inner owner and releases the reservation.
276- IMemoryOwner < T > ? inner = Interlocked . Exchange ( ref this . owner , null ) ;
277- if ( inner != null )
278- {
279- inner . Dispose ( ) ;
280- this . allocator . ReleaseAccumulatedBytes ( this . lengthInBytes ) ;
281- }
282- }
283- }
284112}
0 commit comments