@@ -55,12 +55,11 @@ public abstract class MemoryAllocator
5555 internal int SingleBufferAllocationLimitBytes { get ; private protected set ; } = OneGigabyte ;
5656
5757 /// <summary>
58- /// Gets a value indicating whether change tracking is currently suppressed for this instance.
58+ /// Gets a value indicating whether accumulative allocation tracking is currently suppressed for this instance.
5959 /// </summary>
6060 /// <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.
61+ /// This is used internally when an outer allocator or memory group reservation already owns the tracked bytes
62+ /// and nested allocations must not reserve or release them a second time.
6463 /// </remarks>
6564 private bool IsTrackingSuppressed => Volatile . Read ( ref this . trackingSuppressionCount ) > 0 ;
6665
@@ -105,9 +104,62 @@ public static MemoryAllocator Create(MemoryAllocatorOptions options)
105104 /// <param name="length">Size of the buffer to allocate.</param>
106105 /// <param name="options">The allocation options.</param>
107106 /// <returns>A buffer of values of type <typeparamref name="T"/>.</returns>
108- /// <exception cref="ArgumentOutOfRangeException">When length is zero or negative.</exception>
107+ /// <exception cref="ArgumentOutOfRangeException">When length is negative.</exception>
109108 /// <exception cref="InvalidMemoryOperationException">When length is over the capacity of the allocator.</exception>
110- public abstract IMemoryOwner < T > Allocate < T > ( int length , AllocationOptions options = AllocationOptions . None )
109+ public IMemoryOwner < T > Allocate < T > ( int length , AllocationOptions options = AllocationOptions . None )
110+ where T : struct
111+ {
112+ if ( length < 0 )
113+ {
114+ InvalidMemoryOperationException . ThrowNegativeAllocationException ( length ) ;
115+ }
116+
117+ ulong lengthInBytes = ( ulong ) length * ( ulong ) Unsafe . SizeOf < T > ( ) ;
118+ if ( lengthInBytes > ( ulong ) this . SingleBufferAllocationLimitBytes )
119+ {
120+ InvalidMemoryOperationException . ThrowAllocationOverLimitException ( lengthInBytes , this . SingleBufferAllocationLimitBytes ) ;
121+ }
122+
123+ long lengthInBytesLong = ( long ) lengthInBytes ;
124+ bool shouldTrack = ! this . IsTrackingSuppressed && lengthInBytesLong != 0 ;
125+ if ( shouldTrack )
126+ {
127+ this . ReserveAllocation ( lengthInBytesLong ) ;
128+ }
129+
130+ try
131+ {
132+ AllocationTrackedMemoryManager < T > owner = this . AllocateCore < T > ( length , options ) ;
133+ if ( shouldTrack )
134+ {
135+ owner . AttachAllocationTracking ( this , lengthInBytesLong ) ;
136+ }
137+
138+ return owner ;
139+ }
140+ catch
141+ {
142+ if ( shouldTrack )
143+ {
144+ this . ReleaseAccumulatedBytes ( lengthInBytesLong ) ;
145+ }
146+
147+ throw ;
148+ }
149+ }
150+
151+ /// <summary>
152+ /// Allocates a tracked memory owner for <see cref="Allocate{T}(int, AllocationOptions)"/>.
153+ /// </summary>
154+ /// <typeparam name="T">Type of the data stored in the buffer.</typeparam>
155+ /// <param name="length">Size of the buffer to allocate.</param>
156+ /// <param name="options">The allocation options.</param>
157+ /// <returns>A tracked memory owner of values of type <typeparamref name="T"/>.</returns>
158+ /// <remarks>
159+ /// Implementations should only allocate and initialize the concrete owner. The base allocator
160+ /// reserves bytes, attaches tracking to the returned owner, and releases the reservation if allocation fails.
161+ /// </remarks>
162+ protected abstract AllocationTrackedMemoryManager < T > AllocateCore < T > ( int length , AllocationOptions options = AllocationOptions . None )
111163 where T : struct ;
112164
113165 /// <summary>
@@ -149,19 +201,31 @@ internal MemoryGroup<T> AllocateGroup<T>(
149201 }
150202
151203 long totalLengthInBytesLong = ( long ) totalLengthInBytes ;
152- this . ReserveAllocation ( totalLengthInBytesLong ) ;
204+ bool shouldTrack = ! this . IsTrackingSuppressed && totalLengthInBytesLong != 0 ;
205+ if ( shouldTrack )
206+ {
207+ this . ReserveAllocation ( totalLengthInBytesLong ) ;
208+ }
153209
154210 using ( this . SuppressTracking ( ) )
155211 {
156212 try
157213 {
158214 MemoryGroup < T > group = this . AllocateGroupCore < T > ( totalLength , totalLengthInBytesLong , bufferAlignment , options ) ;
159- group . SetAllocationTracking ( this , totalLengthInBytesLong ) ;
215+ if ( shouldTrack )
216+ {
217+ group . AttachAllocationTracking ( this , totalLengthInBytesLong ) ;
218+ }
219+
160220 return group ;
161221 }
162222 catch
163223 {
164- this . ReleaseAccumulatedBytes ( totalLengthInBytesLong ) ;
224+ if ( shouldTrack )
225+ {
226+ this . ReleaseAccumulatedBytes ( totalLengthInBytesLong ) ;
227+ }
228+
165229 throw ;
166230 }
167231 }
@@ -172,28 +236,25 @@ internal virtual MemoryGroup<T> AllocateGroupCore<T>(long totalLengthInElements,
172236 => MemoryGroup < T > . Allocate ( this , totalLengthInElements , bufferAlignment , options ) ;
173237
174238 /// <summary>
175- /// Tracks the allocation of an <see cref="IMemoryOwner {T}" /> instance after reserving bytes .
239+ /// Allocates a single segment for <see cref="MemoryGroup {T}"/> construction .
176240 /// </summary>
177241 /// <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 )
242+ /// <param name="length">Size of the segment to allocate.</param>
243+ /// <param name="options">The allocation options.</param>
244+ /// <returns>A segment owner for the requested buffer length.</returns>
245+ /// <remarks>
246+ /// The default implementation uses <see cref="Allocate{T}(int, AllocationOptions)"/>. Built-in allocators
247+ /// can override this to supply raw segment owners when group construction must bypass nested tracking.
248+ /// </remarks>
249+ internal virtual IMemoryOwner < T > AllocateGroupBuffer < T > ( int length , AllocationOptions options = AllocationOptions . None )
182250 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- }
251+ => this . Allocate < T > ( length , options ) ;
191252
192253 /// <summary>
193254 /// Reserves accumulative allocation bytes before creating the underlying buffer.
194255 /// </summary>
195256 /// <param name="lengthInBytes">The number of bytes to reserve.</param>
196- protected void ReserveAllocation ( long lengthInBytes )
257+ private void ReserveAllocation ( long lengthInBytes )
197258 {
198259 if ( this . IsTrackingSuppressed || lengthInBytes <= 0 )
199260 {
@@ -225,13 +286,17 @@ internal void ReleaseAccumulatedBytes(long lengthInBytes)
225286 /// <summary>
226287 /// Suppresses accumulative allocation tracking for the lifetime of the returned scope.
227288 /// </summary>
228- /// <returns>An <see cref="IDisposable"/> that restores tracking when disposed.</returns>
229- internal IDisposable SuppressTracking ( ) => new TrackingSuppressionScope ( this ) ;
289+ /// <returns>A scope that restores tracking when disposed.</returns>
290+ /// <remarks>
291+ /// Returning the concrete scope type keeps nested allocator calls allocation-free on the hot path
292+ /// while preserving the same using-pattern at call sites.
293+ /// </remarks>
294+ private TrackingSuppressionScope SuppressTracking ( ) => new ( this ) ;
230295
231296 /// <summary>
232297 /// Temporarily suppresses accumulative allocation tracking within a scope.
233298 /// </summary>
234- private sealed class TrackingSuppressionScope : IDisposable
299+ private struct TrackingSuppressionScope : IDisposable
235300 {
236301 private MemoryAllocator ? allocator ;
237302
@@ -250,35 +315,4 @@ public void Dispose()
250315 }
251316 }
252317 }
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- }
284318}
0 commit comments