7
7
//
8
8
////////////////////////////////////////////////////////////////////////////
9
9
10
+ using System ;
11
+ using System . ComponentModel ;
10
12
using System . Diagnostics ;
11
13
using System . Linq ;
12
14
using System . Threading ;
@@ -19,114 +21,117 @@ protected BufferPool()
19
21
{
20
22
}
21
23
22
- public abstract byte [ ] Rent ( int size , bool exactSize ) ;
24
+ public abstract byte [ ] Rent ( int minimumSize ) ;
23
25
public abstract void Return ( byte [ ] buffer ) ;
24
26
}
25
27
26
28
public sealed class DefaultBufferPool :
27
29
BufferPool
28
30
{
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
39
32
{
40
- private readonly byte [ ] ? [ ] buffers ;
33
+ private readonly int size ;
34
+ private readonly WeakReference wr ;
41
35
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 )
46
37
{
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 ) ;
60
40
}
61
41
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 ;
74
44
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 ;
78
50
}
79
51
80
- private readonly BufferHolder [ ] bufferHolders ;
81
- private int saved ;
52
+ private readonly BufferElement ? [ ] bufferElements ;
82
53
83
54
public DefaultBufferPool ( ) : this ( 16 )
84
55
{
85
56
}
86
57
87
58
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 ) ;
91
64
92
- public override byte [ ] Rent ( int size , bool exactSize )
65
+ public override byte [ ] Rent ( int minimumSize )
93
66
{
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 ++ )
104
68
{
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
+ }
107
100
}
108
101
109
102
#if DEBUG
110
- Debug . WriteLine ( $ "DefaultBufferPool: Rend : Size={ b . Length } / { size } , ExactSize= { exactSize } , Saved= { saved } ") ;
103
+ Debug . WriteLine ( $ "DefaultBufferPool: Created : Size={ minimumSize } ") ;
111
104
#endif
112
- return b ;
105
+ return new byte [ minimumSize ] ;
113
106
}
114
107
115
108
public override void Return ( byte [ ] buffer )
116
109
{
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 ++ )
124
113
{
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
+ }
126
130
}
127
131
132
+ // It was better to simply discard a buffer instance than the cost of extending the table.
128
133
#if DEBUG
129
- Debug . WriteLine ( $ "DefaultBufferPool: Returned : Size={ buffer . Length } , Saved= { saved } ") ;
134
+ Debug . WriteLine ( $ "DefaultBufferPool: Discarded : Size={ buffer . Length } ") ;
130
135
#endif
131
136
}
132
137
}
0 commit comments