77//
88////////////////////////////////////////////////////////////////////////////
99
10+ using System ;
11+ using System . ComponentModel ;
1012using System . Diagnostics ;
1113using System . Linq ;
1214using System . Threading ;
@@ -19,114 +21,117 @@ protected BufferPool()
1921 {
2022 }
2123
22- public abstract byte [ ] Rent ( int size , bool exactSize ) ;
24+ public abstract byte [ ] Rent ( int minimumSize ) ;
2325 public abstract void Return ( byte [ ] buffer ) ;
2426}
2527
2628public sealed class DefaultBufferPool :
2729 BufferPool
2830{
29- // Imported from:
30- // https://github.com/kekyo/GitReader/blob/main/GitReader.Core/Internal/BufferPool.cs
31-
32- // Tried and tested, but simple strategies were the fastest.
33- // Probably because each buffer table and lookup fragments on the CPU cache.
34-
35- private const int bufferHolderLength = 13 ;
36-
37- [ DebuggerStepThrough ]
38- private sealed class BufferHolder
31+ private sealed class BufferElement
3932 {
40- private readonly byte [ ] ? [ ] buffers ;
33+ private readonly int size ;
34+ private readonly WeakReference wr ;
4135
42- public BufferHolder ( int maxReservedBufferElements ) =>
43- this . buffers = new byte [ maxReservedBufferElements ] [ ] ;
44-
45- public byte [ ] ? Rent ( int size , bool exactSize )
36+ public BufferElement ( byte [ ] buffer )
4637 {
47- for ( var index = 0 ; index < this . buffers . Length ; index ++ )
48- {
49- var buffer = this . buffers [ index ] ;
50- if ( buffer != null && ( exactSize ? ( buffer . Length == size ) : ( buffer . Length >= size ) ) )
51- {
52- if ( Interlocked . CompareExchange ( ref this . buffers [ index ] , null , buffer ) == buffer )
53- {
54- return buffer ;
55- }
56- }
57- }
58-
59- return null ;
38+ this . size = buffer . Length ;
39+ this . wr = new WeakReference ( buffer ) ;
6040 }
6141
62- public bool Return ( byte [ ] buffer )
63- {
64- for ( var index = 0 ; index < this . buffers . Length ; index ++ )
65- {
66- if ( this . buffers [ index ] == null )
67- {
68- if ( Interlocked . CompareExchange ( ref this . buffers [ index ] , buffer , null ) == null )
69- {
70- return true ;
71- }
72- }
73- }
42+ public bool IsAvailable =>
43+ this . wr . IsAlive ;
7444
75- // It was better to simply discard a buffer instance than the cost of extending the table.
76- return false ;
77- }
45+ public bool IsAvailableAndFit ( int minimumSize ) =>
46+ this . wr . IsAlive && ( minimumSize <= this . size ) ;
47+
48+ public byte [ ] ? ExtractBuffer ( ) =>
49+ ( byte [ ] ? ) this . wr . Target ;
7850 }
7951
80- private readonly BufferHolder [ ] bufferHolders ;
81- private int saved ;
52+ private readonly BufferElement ? [ ] bufferElements ;
8253
8354 public DefaultBufferPool ( ) : this ( 16 )
8455 {
8556 }
8657
8758 public DefaultBufferPool ( int maxReservedBufferElements ) =>
88- this . bufferHolders = Enumerable . Range ( 0 , bufferHolderLength ) .
89- Select ( _ => new BufferHolder ( maxReservedBufferElements ) ) .
90- ToArray ( ) ;
59+ this . bufferElements = new BufferElement ? [ maxReservedBufferElements ] ;
60+
61+ [ EditorBrowsable ( EditorBrowsableState . Advanced ) ]
62+ public int UnsafeAvailableCount =>
63+ this . bufferElements . Count ( bufferHolder => bufferHolder ? . IsAvailable ?? false ) ;
9164
92- public override byte [ ] Rent ( int size , bool exactSize )
65+ public override byte [ ] Rent ( int minimumSize )
9366 {
94- // NOTE: Size is determined on a "less than" basis when not exact size,
95- // which may result in placement in different buckets and missed opportunities for reuse.
96- // This implementation is ignored penalties.
97- int saved ;
98- var bufferHolder = this . bufferHolders [ size % this . bufferHolders . Length ] ;
99- if ( bufferHolder . Rent ( size , exactSize ) is { } b )
100- {
101- saved = Interlocked . Decrement ( ref this . saved ) ;
102- }
103- else
67+ for ( var index = 0 ; index < this . bufferElements . Length ; index ++ )
10468 {
105- b = new byte [ size ] ;
106- saved = this . saved ;
69+ var bufferElement = this . bufferElements [ index ] ;
70+
71+ // First phase:
72+ // * Determined: size and exactSize
73+ // * NOT determined: Availability
74+ if ( bufferElement ? . IsAvailableAndFit ( minimumSize ) ?? false )
75+ {
76+ if ( object . ReferenceEquals (
77+ Interlocked . CompareExchange (
78+ ref this . bufferElements [ index ] ,
79+ null ,
80+ bufferElement ) ,
81+ bufferElement ) &&
82+ // Second phase
83+ // * Determined: size, exactSize and availability
84+ bufferElement . ExtractBuffer ( ) is { } buffer )
85+ {
86+ #if DEBUG
87+ Debug . WriteLine ( $ "DefaultBufferPool: Rend: Size={ buffer . Length } /{ minimumSize } , Index={ index } ") ;
88+ #endif
89+ return buffer ;
90+ }
91+ }
92+ else if ( ! ( bufferElement ? . IsAvailable ?? true ) )
93+ {
94+ // Remove corrected element (and forgot).
95+ Interlocked . CompareExchange (
96+ ref this . bufferElements [ index ] ,
97+ null ,
98+ bufferElement ) ;
99+ }
107100 }
108101
109102#if DEBUG
110- Debug . WriteLine ( $ "DefaultBufferPool: Rend : Size={ b . Length } / { size } , ExactSize= { exactSize } , Saved= { saved } ") ;
103+ Debug . WriteLine ( $ "DefaultBufferPool: Created : Size={ minimumSize } ") ;
111104#endif
112- return b ;
105+ return new byte [ minimumSize ] ;
113106 }
114107
115108 public override void Return ( byte [ ] buffer )
116109 {
117- int saved ;
118- var bufferHolder = this . bufferHolders [ buffer . Length % this . bufferHolders . Length ] ;
119- if ( bufferHolder . Return ( buffer ) )
120- {
121- saved = Interlocked . Increment ( ref this . saved ) ;
122- }
123- else
110+ var newBufferElement = new BufferElement ( buffer ) ;
111+
112+ for ( var index = 0 ; index < this . bufferElements . Length ; index ++ )
124113 {
125- saved = this . saved ;
114+ var bufferElement = this . bufferElements [ index ] ;
115+ if ( bufferElement == null || ! bufferElement . IsAvailable )
116+ {
117+ if ( object . ReferenceEquals (
118+ Interlocked . CompareExchange (
119+ ref this . bufferElements [ index ] ,
120+ newBufferElement ,
121+ bufferElement ) ,
122+ bufferElement ) )
123+ {
124+ #if DEBUG
125+ Debug . WriteLine ( $ "DefaultBufferPool: Returned: Size={ buffer . Length } , Index={ index } ") ;
126+ #endif
127+ return ;
128+ }
129+ }
126130 }
127131
132+ // It was better to simply discard a buffer instance than the cost of extending the table.
128133#if DEBUG
129- Debug . WriteLine ( $ "DefaultBufferPool: Returned : Size={ buffer . Length } , Saved= { saved } ") ;
134+ Debug . WriteLine ( $ "DefaultBufferPool: Discarded : Size={ buffer . Length } ") ;
130135#endif
131136 }
132137}
0 commit comments