Skip to content

Commit a1bfe3d

Browse files
committed
Port 'ObjectPool<T>' type from Roslyn
1 parent 76c41aa commit a1bfe3d

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
<Compile Include="$(MSBuildThisFileDirectory)Helpers\EquatableArray{T}.cs" />
9090
<Compile Include="$(MSBuildThisFileDirectory)Helpers\HashCode.cs" />
9191
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ImmutableArrayBuilder{T}.cs" />
92+
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ObjectPool{T}.cs" />
9293
<Compile Include="$(MSBuildThisFileDirectory)Input\Models\CanExecuteExpressionType.cs" />
9394
<Compile Include="$(MSBuildThisFileDirectory)Input\Models\CommandInfo.cs" />
9495
<Compile Include="$(MSBuildThisFileDirectory)Input\RelayCommandGenerator.cs" />
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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

Comments
 (0)