Skip to content

Commit b1efea6

Browse files
author
Oren (electricessence)
committed
Refined improvments including detailed benchmarking.
1 parent 7e9d570 commit b1efea6

12 files changed

Lines changed: 237 additions & 100 deletions
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/* Based on Roslyn's ObjectPool */
2+
3+
using System;
4+
using System.Diagnostics;
5+
using System.Threading;
6+
7+
namespace Open.Disposable
8+
{
9+
/// <summary>
10+
/// An extremely fast ObjectPool when the capacity is in the low 100s.
11+
/// </summary>
12+
/// <typeparam name="T"></typeparam>
13+
public class InterlockedArrayObjectPool<T> : ObjectPoolBase<T>
14+
where T : class
15+
{
16+
17+
public InterlockedArrayObjectPool(Func<T> factory, Action<T> recycler, int capacity = DEFAULT_CAPACITY)
18+
: base(factory, recycler, capacity)
19+
{
20+
AllowPocket = true;
21+
}
22+
23+
public InterlockedArrayObjectPool(Func<T> factory, int capacity = DEFAULT_CAPACITY)
24+
: this(factory, null, capacity)
25+
{
26+
}
27+
28+
Element[] Pool;
29+
30+
[DebuggerDisplay("{Value,nq}")]
31+
protected struct Element
32+
{
33+
T _value;
34+
internal bool Save(ref T value)
35+
{
36+
if (_value != null) return false;
37+
value = Interlocked.Exchange(ref _value, value);
38+
return value == null;
39+
}
40+
41+
internal T TryRetrieve()
42+
{
43+
var item = _value;
44+
if (item != null)
45+
{
46+
if (item == Interlocked.CompareExchange(ref _value, null, item))
47+
{
48+
return item;
49+
}
50+
}
51+
return null;
52+
}
53+
}
54+
55+
protected override bool Receive(T item)
56+
{
57+
var elements = Pool;
58+
var len = elements?.Length ?? 0;
59+
60+
for (int i = 0; i < len; i++)
61+
{
62+
if (elements[i].Save(ref item))
63+
return true;
64+
}
65+
66+
return false;
67+
}
68+
69+
protected override T TryTakeInternal()
70+
{
71+
// We missed getting the first item or it wasn't there.
72+
var elements = Pool;
73+
var len = elements?.Length ?? 0;
74+
75+
for (int i = 0; i < len; i++)
76+
{
77+
var item = elements[i].TryRetrieve();
78+
if (item != null) return item;
79+
}
80+
81+
return null;
82+
}
83+
84+
protected override void OnDispose(bool calledExplicitly)
85+
{
86+
Pool = null;
87+
}
88+
89+
}
90+
91+
public static class InterlockedArrayObjectPool
92+
{
93+
public static InterlockedArrayObjectPool<T> Create<T>(Func<T> factory, int capacity = Constants.DEFAULT_CAPACITY)
94+
where T : class
95+
{
96+
return new InterlockedArrayObjectPool<T>(factory, capacity);
97+
}
98+
99+
public static InterlockedArrayObjectPool<T> Create<T>(int capacity = Constants.DEFAULT_CAPACITY)
100+
where T : class, new()
101+
{
102+
return Create(() => new T(), capacity);
103+
}
104+
}
105+
}
Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class OptimisticArrayObjectPool<T> : ObjectPoolBase<T>
1717
public OptimisticArrayObjectPool(Func<T> factory, Action<T> recycler, int capacity = DEFAULT_CAPACITY)
1818
: base(factory, recycler, capacity)
1919
{
20-
_pool = new Element[capacity - 1];
20+
AllowPocket = false;
2121
}
2222

2323
public OptimisticArrayObjectPool(Func<T> factory, int capacity = DEFAULT_CAPACITY)
@@ -26,27 +26,16 @@ public OptimisticArrayObjectPool(Func<T> factory, int capacity = DEFAULT_CAPACIT
2626
}
2727

2828

29-
3029
[DebuggerDisplay("{Value,nq}")]
31-
private struct Element
30+
protected struct Element
3231
{
3332
internal T Value;
3433
}
3534

36-
Element[] _pool;
3735
T _firstItem;
36+
Element[] Pool;
3837

39-
protected override bool CanGive(T item)
40-
{
41-
throw new NotImplementedException();
42-
}
43-
44-
public override void Give(T item)
45-
{
46-
GiveInternal(item);
47-
}
48-
49-
protected override bool GiveInternal(T item)
38+
protected override bool Receive(T item)
5039
{
5140
// First see if optimisically we can store in _firstItem;
5241
if (_firstItem == null)
@@ -57,7 +46,7 @@ protected override bool GiveInternal(T item)
5746
// Else iterate to find an empty slot.
5847
else
5948
{
60-
var elements = _pool;
49+
var elements = Pool;
6150
var len = elements?.Length ?? 0;
6251

6352
for (int i = 0; i < len; i++)
@@ -84,7 +73,7 @@ protected override T TryTakeInternal()
8473
return item;
8574

8675
// We missed getting the first item or it wasn't there.
87-
var elements = _pool;
76+
var elements = Pool;
8877
var len = elements?.Length ?? 0;
8978

9079
for (int i = 0; i < len; i++)
@@ -105,11 +94,9 @@ protected override T TryTakeInternal()
10594

10695
protected override void OnDispose(bool calledExplicitly)
10796
{
108-
_pool = null;
109-
_firstItem = null;
97+
Pool = null;
11098
}
11199

112-
113100
}
114101

115102
public static class OptimisticArrayObjectPool

source/CollectionWrapperObjectPool.cs renamed to source/Collection/CollectionWrapperObjectPool.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,21 @@ public CollectionWrapperObjectPool(TCollection pool, Func<T> factory, int capaci
2525

2626
public override int Count => Pool?.Count ?? 0;
2727

28-
protected override bool GiveInternal(T item)
28+
protected override bool Receive(T item)
2929
{
30-
lock (Pool)
30+
var p = Pool;
31+
if (p != null)
3132
{
32-
if (Count >= MaxSize) return false;
33-
Pool.Add(item);
33+
lock (p)
34+
{
35+
// It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt.
36+
// The lock operation should be quick enough to not pile up too many items.
37+
p.Add(item);
38+
return true;
39+
}
3440
}
3541

36-
return true;
42+
return false;
3743
}
3844

3945
protected override T TryTakeInternal()

source/ConcurrentBagObjectPool.cs renamed to source/Collection/ConcurrentBagObjectPool.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ public ConcurrentBagObjectPool(Func<T> factory, int capacity = DEFAULT_CAPACITY)
2626

2727
public override int Count => Pool?.Count ?? 0;
2828

29-
protected override bool GiveInternal(T item)
29+
protected override bool Receive(T item)
3030
{
31-
Pool.Add(item); // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt.
31+
var p = Pool;
32+
if (p == null) return false;
33+
p.Add(item); // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt.
3234
return true;
3335
}
3436

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ public QueueObjectPool(Func<T> factory, int capacity = DEFAULT_CAPACITY)
2424

2525
public override int Count => Pool?.Count ?? 0;
2626

27-
protected override bool GiveInternal(T item)
27+
protected override bool Receive(T item)
2828
{
29-
if (Count < MaxSize)
29+
var p = Pool;
30+
if (p!=null)
3031
{
31-
lock (Pool) Pool.Enqueue(item); // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt.
32+
// It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt.
33+
// The lock operation should be quick enough to not pile up too many items.
34+
lock (p) p.Enqueue(item);
3235
return true;
3336
}
3437

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ protected override void OnDispose(bool calledExplicitly)
115115
Nullify(ref _pool)?.Complete();
116116
}
117117

118-
protected override bool GiveInternal(T item)
118+
protected override bool Receive(T item)
119119
{
120120
return _pool.Post(item);
121121
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,14 @@ protected override void OnDispose(bool calledExplicitly)
6666

6767
protected void _OnRecycled(T item)
6868
{
69-
base.GiveInternal(item);
69+
base.Receive(item);
7070
}
7171

72-
protected override bool GiveInternal(T item)
72+
protected override bool Receive(T item)
7373
{
7474
var r = _recycleQueue;
7575
if (r == null) // No recycler? Defer to default.
76-
return base.GiveInternal(item);
76+
return base.Receive(item);
7777

7878
// recycler? check the combined max size first then queue.
7979
var p = _pool;

source/ObjectPoolBase.cs

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Open.Threading;
22
using System;
33
using System.Diagnostics;
4+
using System.Threading;
45
using System.Threading.Tasks;
56

67
namespace Open.Disposable
@@ -38,25 +39,51 @@ public T Generate()
3839
return Factory();
3940
}
4041

41-
protected abstract bool CanGive(T item);
42+
protected virtual bool CanReceive => true;
43+
44+
protected bool PrepareToReceive(T item)
45+
{
46+
if (item == null) return false;
47+
48+
if (!CanReceive) return false;
49+
var r = Recycler;
50+
if (r != null)
51+
{
52+
r(item);
53+
if (!CanReceive) return false;
54+
}
55+
56+
return true;
57+
}
4258

4359
// Contract should be that no item can be null here.
44-
protected abstract bool GiveInternal(T item);
60+
protected abstract bool Receive(T item);
61+
62+
protected virtual void OnGivenTo()
63+
{
64+
65+
}
66+
protected void OnGivenTo(bool wasGiven)
67+
{
68+
if (wasGiven) OnGivenTo();
69+
}
4570

46-
public virtual void Give(T item)
71+
public void Give(T item)
4772
{
48-
if(CanGive(item)) GiveInternal(item);
73+
if(PrepareToReceive(item) && (GaveToPocket(ref item) || Receive(item)))
74+
OnGivenTo();
4975
}
5076

5177
protected virtual Task<bool> GiveInternalAsync(T item)
5278
{
53-
return Task.Run(() => GiveInternal(item));
79+
return Task.Run(() => Receive(item));
5480
}
5581

5682
public virtual Task GiveAsync(T item)
5783
{
5884
if (item == null) return Task.FromResult(false);
59-
return GiveInternalAsync(item);
85+
return GiveInternalAsync(item)
86+
.OnFullfilled((Action<bool>)OnGivenTo);
6087
}
6188

6289
public virtual T Take()
@@ -78,17 +105,43 @@ public Task<T> TakeAsync()
78105
return TakeAsyncInternal();
79106
}
80107

81-
public virtual bool TryTake(out T item)
108+
public bool TryTake(out T item)
82109
{
83110
item = TryTake();
84111
return item != null;
85112
}
86113

114+
protected bool AllowPocket = true;
115+
116+
protected T Pocket;
117+
protected T TakeFromPocket()
118+
{
119+
if (!AllowPocket || Pocket == null) return null;
120+
return Interlocked.Exchange(ref Pocket, null);
121+
}
122+
123+
protected bool GaveToPocket(ref T item)
124+
{
125+
if (!AllowPocket || Pocket != null) return false;
126+
item = Interlocked.Exchange(ref Pocket, item);
127+
return item == null;
128+
}
129+
87130
protected abstract T TryTakeInternal();
88131

89-
public virtual T TryTake()
132+
public T TryTake()
133+
{
134+
var item = TakeFromPocket() ?? TryTakeInternal();
135+
if (item != null) OnTakenFrom();
136+
return item;
137+
}
138+
139+
protected virtual void OnTakenFrom()
140+
{
141+
}
142+
protected void OnTakenFrom(bool wasTaken)
90143
{
91-
return TryTakeInternal();
144+
if (wasTaken) OnTakenFrom();
92145
}
93146

94147
protected override void OnBeforeDispose()

0 commit comments

Comments
 (0)