Skip to content

Commit 688c157

Browse files
committed
Add an object pool
1 parent 2559298 commit 688c157

6 files changed

Lines changed: 577 additions & 0 deletions

File tree

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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.Collections.Generic;
5+
using System.Text;
6+
7+
namespace StyleCop.Analyzers.Helpers.ObjectPools
8+
{
9+
internal static class SharedPoolExtensions
10+
{
11+
private const int Threshold = 512;
12+
13+
public static PooledObject<StringBuilder> GetPooledObject(this ObjectPool<StringBuilder> pool)
14+
{
15+
return PooledObject<StringBuilder>.Create(pool);
16+
}
17+
18+
public static PooledObject<Stack<TItem>> GetPooledObject<TItem>(this ObjectPool<Stack<TItem>> pool)
19+
{
20+
return PooledObject<Stack<TItem>>.Create(pool);
21+
}
22+
23+
public static PooledObject<Queue<TItem>> GetPooledObject<TItem>(this ObjectPool<Queue<TItem>> pool)
24+
{
25+
return PooledObject<Queue<TItem>>.Create(pool);
26+
}
27+
28+
public static PooledObject<HashSet<TItem>> GetPooledObject<TItem>(this ObjectPool<HashSet<TItem>> pool)
29+
{
30+
return PooledObject<HashSet<TItem>>.Create(pool);
31+
}
32+
33+
public static PooledObject<Dictionary<TKey, TValue>> GetPooledObject<TKey, TValue>(this ObjectPool<Dictionary<TKey, TValue>> pool)
34+
{
35+
return PooledObject<Dictionary<TKey, TValue>>.Create(pool);
36+
}
37+
38+
public static PooledObject<List<TItem>> GetPooledObject<TItem>(this ObjectPool<List<TItem>> pool)
39+
{
40+
return PooledObject<List<TItem>>.Create(pool);
41+
}
42+
43+
public static PooledObject<T> GetPooledObject<T>(this ObjectPool<T> pool)
44+
where T : class
45+
{
46+
return new PooledObject<T>(pool, p => p.Allocate(), (p, o) => p.Free(o));
47+
}
48+
49+
public static StringBuilder AllocateAndClear(this ObjectPool<StringBuilder> pool)
50+
{
51+
var sb = pool.Allocate();
52+
sb.Clear();
53+
54+
return sb;
55+
}
56+
57+
public static Stack<T> AllocateAndClear<T>(this ObjectPool<Stack<T>> pool)
58+
{
59+
var set = pool.Allocate();
60+
set.Clear();
61+
62+
return set;
63+
}
64+
65+
public static Queue<T> AllocateAndClear<T>(this ObjectPool<Queue<T>> pool)
66+
{
67+
var set = pool.Allocate();
68+
set.Clear();
69+
70+
return set;
71+
}
72+
73+
public static HashSet<T> AllocateAndClear<T>(this ObjectPool<HashSet<T>> pool)
74+
{
75+
var set = pool.Allocate();
76+
set.Clear();
77+
78+
return set;
79+
}
80+
81+
public static Dictionary<TKey, TValue> AllocateAndClear<TKey, TValue>(this ObjectPool<Dictionary<TKey, TValue>> pool)
82+
{
83+
var map = pool.Allocate();
84+
map.Clear();
85+
86+
return map;
87+
}
88+
89+
public static List<T> AllocateAndClear<T>(this ObjectPool<List<T>> pool)
90+
{
91+
var list = pool.Allocate();
92+
list.Clear();
93+
94+
return list;
95+
}
96+
97+
public static void ClearAndFree(this ObjectPool<StringBuilder> pool, StringBuilder sb)
98+
{
99+
if (sb == null)
100+
{
101+
return;
102+
}
103+
104+
sb.Clear();
105+
106+
if (sb.Capacity > Threshold)
107+
{
108+
sb.Capacity = Threshold;
109+
}
110+
111+
pool.Free(sb);
112+
}
113+
114+
public static void ClearAndFree<T>(this ObjectPool<HashSet<T>> pool, HashSet<T> set)
115+
{
116+
if (set == null)
117+
{
118+
return;
119+
}
120+
121+
var count = set.Count;
122+
set.Clear();
123+
124+
if (count > Threshold)
125+
{
126+
set.TrimExcess();
127+
}
128+
129+
pool.Free(set);
130+
}
131+
132+
public static void ClearAndFree<T>(this ObjectPool<Stack<T>> pool, Stack<T> set)
133+
{
134+
if (set == null)
135+
{
136+
return;
137+
}
138+
139+
var count = set.Count;
140+
set.Clear();
141+
142+
if (count > Threshold)
143+
{
144+
set.TrimExcess();
145+
}
146+
147+
pool.Free(set);
148+
}
149+
150+
public static void ClearAndFree<T>(this ObjectPool<Queue<T>> pool, Queue<T> set)
151+
{
152+
if (set == null)
153+
{
154+
return;
155+
}
156+
157+
var count = set.Count;
158+
set.Clear();
159+
160+
if (count > Threshold)
161+
{
162+
set.TrimExcess();
163+
}
164+
165+
pool.Free(set);
166+
}
167+
168+
public static void ClearAndFree<TKey, TValue>(this ObjectPool<Dictionary<TKey, TValue>> pool, Dictionary<TKey, TValue> map)
169+
{
170+
if (map == null)
171+
{
172+
return;
173+
}
174+
175+
// if map grew too big, don't put it back to pool
176+
if (map.Count > Threshold)
177+
{
178+
return;
179+
}
180+
181+
map.Clear();
182+
pool.Free(map);
183+
}
184+
185+
public static void ClearAndFree<T>(this ObjectPool<List<T>> pool, List<T> list)
186+
{
187+
if (list == null)
188+
{
189+
return;
190+
}
191+
192+
list.Clear();
193+
194+
if (list.Capacity > Threshold)
195+
{
196+
list.Capacity = Threshold;
197+
}
198+
199+
pool.Free(list);
200+
}
201+
}
202+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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

Comments
 (0)