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
+
5
+ // Ported and adapted from https://github.com/dotnet/roslyn
6
+
7
+ using System ;
8
+ using System . Diagnostics ;
9
+ using System . Threading ;
10
+
11
+ #pragma warning disable RS1035 , IDE0290
12
+
13
+ namespace Microsoft . CodeAnalysis . PooledObjects ;
14
+
15
+ /// <summary>
16
+ /// Generic implementation of object pooling pattern with predefined pool size limit. The main
17
+ /// purpose is that limited number of frequently used objects can be kept in the pool for
18
+ /// further recycling.
19
+ ///
20
+ /// Notes:
21
+ /// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there
22
+ /// is no space in the pool, extra returned objects will be dropped.
23
+ ///
24
+ /// 2) it is implied that if object was obtained from a pool, the caller will return it back in
25
+ /// a relatively short time. Keeping checked out objects for long durations is ok, but
26
+ /// reduces usefulness of pooling. Just new up your own.
27
+ ///
28
+ /// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
29
+ /// Rationale:
30
+ /// If there is no intent for reusing the object, do not use pool - just use "new".
31
+ /// </summary>
32
+ internal sealed class ObjectPool < T >
33
+ where T : class
34
+ {
35
+ // Storage for the pool objects. The first item is stored in a dedicated field because we
36
+ // expect to be able to satisfy most requests from it.
37
+ private T ? firstItem ;
38
+ private readonly Element [ ] items ;
39
+
40
+ // The factory is stored for the lifetime of the pool. We will call this only when pool needs to
41
+ // expand. compared to "new T()", Func gives more flexibility to implementers and faster
42
+ // than "new T()".
43
+ private readonly Func < T > factory ;
44
+
45
+ /// <summary>
46
+ /// Creates a new <see cref="ObjectPool{T}"/> instance with a given factory.
47
+ /// </summary>
48
+ /// <param name="factory">The factory to use to produce new objects.</param>
49
+ public ObjectPool ( Func < T > factory )
50
+ : this ( factory , Environment . ProcessorCount * 2 )
51
+ {
52
+ }
53
+
54
+ /// <summary>
55
+ /// Creates a new <see cref="ObjectPool{T}"/> instance with a given factory.
56
+ /// </summary>
57
+ /// <param name="factory">The factory to use to produce new objects.</param>
58
+ /// <param name="size">The size of the pool.</param>
59
+ public ObjectPool ( Func < T > factory , int size )
60
+ {
61
+ this . factory = factory ;
62
+ this . items = new Element [ size - 1 ] ;
63
+ }
64
+
65
+ /// <summary>
66
+ /// Produces an instance.
67
+ /// </summary>
68
+ /// <returns>The instance to return to the pool later.</returns>
69
+ /// <remarks>
70
+ /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
71
+ /// Note that Free will try to store recycled objects close to the start thus statistically
72
+ /// reducing how far we will typically search.
73
+ /// </remarks>
74
+ public T Allocate ( )
75
+ {
76
+ // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements.
77
+ // Note that the initial read is optimistically not synchronized. That is intentional.
78
+ // We will interlock only when we have a candidate. in a worst case we may miss some
79
+ // recently returned objects. Not a big deal.
80
+ T ? instance = this . firstItem ;
81
+ if ( instance == null || instance != Interlocked . CompareExchange ( ref this . firstItem , null , instance ) )
82
+ {
83
+ instance = AllocateSlow ( ) ;
84
+ }
85
+
86
+ return instance ;
87
+ }
88
+
89
+ /// <summary>
90
+ /// Slow path to produce a new instance.
91
+ /// </summary>
92
+ /// <returns>The instance to return to the pool later.</returns>
93
+ private T AllocateSlow ( )
94
+ {
95
+ Element [ ] items = this . items ;
96
+
97
+ for ( int i = 0 ; i < items . Length ; i ++ )
98
+ {
99
+ T ? instance = items [ i ] . Value ;
100
+
101
+ if ( instance is not null )
102
+ {
103
+ if ( instance == Interlocked . CompareExchange ( ref items [ i ] . Value , null , instance ) )
104
+ {
105
+ return instance ;
106
+ }
107
+ }
108
+ }
109
+
110
+ return this . factory ( ) ;
111
+ }
112
+
113
+ /// <summary>
114
+ /// Returns objects to the pool.
115
+ /// </summary>
116
+ /// <param name="obj">The object to return to the pool.</param>
117
+ /// <remarks>
118
+ /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
119
+ /// Note that Free will try to store recycled objects close to the start thus statistically
120
+ /// reducing how far we will typically search in Allocate.
121
+ /// </remarks>
122
+ public void Free ( T obj )
123
+ {
124
+ if ( this . firstItem is null )
125
+ {
126
+ this . firstItem = obj ;
127
+ }
128
+ else
129
+ {
130
+ FreeSlow ( obj ) ;
131
+ }
132
+ }
133
+
134
+ /// <summary>
135
+ /// Slow path to return an object to the pool.
136
+ /// </summary>
137
+ /// <param name="obj">The object to return to the pool.</param>
138
+ private void FreeSlow ( T obj )
139
+ {
140
+ Element [ ] items = this . items ;
141
+
142
+ for ( int i = 0 ; i < items . Length ; i ++ )
143
+ {
144
+ if ( items [ i ] . Value == null )
145
+ {
146
+ items [ i ] . Value = obj ;
147
+
148
+ break ;
149
+ }
150
+ }
151
+ }
152
+
153
+ /// <summary>
154
+ /// Wrapper to avoid array covariance.
155
+ /// </summary>
156
+ [ DebuggerDisplay ( "{Value,nq}" ) ]
157
+ private struct Element
158
+ {
159
+ /// <summary>
160
+ /// The value for the current element.
161
+ /// </summary>
162
+ public T ? Value ;
163
+ }
164
+ }
0 commit comments