1+ // Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+ using System ;
5+ using System . Diagnostics ;
6+ using System . Threading ;
7+
8+ namespace StyleCop . Analyzers . Helpers . ObjectPools
9+ {
10+ /// <summary>
11+ /// Generic implementation of object pooling pattern with predefined pool size limit. The main
12+ /// purpose is that limited number of frequently used objects can be kept in the pool for
13+ /// further recycling.
14+ ///
15+ /// Notes:
16+ /// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there
17+ /// is no space in the pool, extra returned objects will be dropped.
18+ ///
19+ /// 2) it is implied that if object was obtained from a pool, the caller will return it back in
20+ /// a relatively short time. Keeping checked out objects for long durations is ok, but
21+ /// reduces usefulness of pooling. Just new up your own.
22+ ///
23+ /// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
24+ /// Rationale:
25+ /// If there is no intent for reusing the object, do not use pool - just use "new".
26+ /// </summary>
27+ /// <typeparam name="T">The type of the objects in this cache.</typeparam>
28+ internal class ObjectPool < T >
29+ where T : class
30+ {
31+ private readonly Element [ ] items ;
32+
33+ // factory is stored for the lifetime of the pool. We will call this only when pool needs to
34+ // expand. compared to "new T()", Func gives more flexibility to implementers and faster
35+ // than "new T()".
36+ private readonly Func < T > factory ;
37+
38+ // Storage for the pool objects. The first item is stored in a dedicated field because we
39+ // expect to be able to satisfy most requests from it.
40+ private T firstItem ;
41+
42+ internal ObjectPool ( Func < T > factory )
43+ : this ( factory , Environment . ProcessorCount * 2 )
44+ {
45+ }
46+
47+ internal ObjectPool ( Func < T > factory , int size )
48+ {
49+ Debug . Assert ( size >= 1 , "The object pool can't be empty" ) ;
50+ this . factory = factory ;
51+ this . items = new Element [ size - 1 ] ;
52+ }
53+
54+ /// <summary>
55+ /// Produces an instance.
56+ /// </summary>
57+ /// <remarks>
58+ /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
59+ /// Note that Free will try to store recycled objects close to the start thus statistically
60+ /// reducing how far we will typically search.
61+ /// </remarks>
62+ /// <returns>A (poosibly) cached instance of type <typeparamref name="T"/>.</returns>
63+ internal T Allocate ( )
64+ {
65+ // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements.
66+ // Note that the initial read is optimistically not synchronized. That is intentional.
67+ // We will interlock only when we have a candidate. in a worst case we may miss some
68+ // recently returned objects. Not a big deal.
69+ T inst = this . firstItem ;
70+ if ( inst == null || inst != Interlocked . CompareExchange ( ref this . firstItem , null , inst ) )
71+ {
72+ inst = this . AllocateSlow ( ) ;
73+ }
74+
75+ return inst ;
76+ }
77+
78+ /// <summary>
79+ /// Returns objects to the pool.
80+ /// </summary>
81+ /// <remarks>
82+ /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
83+ /// Note that Free will try to store recycled objects close to the start thus statistically
84+ /// reducing how far we will typically search in Allocate.
85+ /// </remarks>
86+ /// <param name="obj">The object to free.</param>
87+ internal void Free ( T obj )
88+ {
89+ if ( this . firstItem == null )
90+ {
91+ // Intentionally not using interlocked here.
92+ // In a worst case scenario two objects may be stored into same slot.
93+ // It is very unlikely to happen and will only mean that one of the objects will get collected.
94+ this . firstItem = obj ;
95+ }
96+ else
97+ {
98+ this . FreeSlow ( obj ) ;
99+ }
100+ }
101+
102+ private T CreateInstance ( )
103+ {
104+ var inst = this . factory ( ) ;
105+ return inst ;
106+ }
107+
108+ private T AllocateSlow ( )
109+ {
110+ var items = this . items ;
111+
112+ for ( int i = 0 ; i < items . Length ; i ++ )
113+ {
114+ // Note that the initial read is optimistically not synchronized. That is intentional.
115+ // We will interlock only when we have a candidate. in a worst case we may miss some
116+ // recently returned objects. Not a big deal.
117+ T inst = items [ i ] . Value ;
118+ if ( inst != null )
119+ {
120+ if ( inst == Interlocked . CompareExchange ( ref items [ i ] . Value , null , inst ) )
121+ {
122+ return inst ;
123+ }
124+ }
125+ }
126+
127+ return this . CreateInstance ( ) ;
128+ }
129+
130+ private void FreeSlow ( T obj )
131+ {
132+ var items = this . items ;
133+ for ( int i = 0 ; i < items . Length ; i ++ )
134+ {
135+ if ( items [ i ] . Value == null )
136+ {
137+ // Intentionally not using interlocked here.
138+ // In a worst case scenario two objects may be stored into same slot.
139+ // It is very unlikely to happen and will only mean that one of the objects will get collected.
140+ items [ i ] . Value = obj ;
141+ break ;
142+ }
143+ }
144+ }
145+
146+ [ DebuggerDisplay ( "{Value,nq}" ) ]
147+ private struct Element
148+ {
149+ internal T Value ;
150+ }
151+ }
152+ }
0 commit comments