Skip to content

Commit 53124ab

Browse files
geeknoidMartin Taillefer
and
Martin Taillefer
authored
Reimplement the default object pool for improved performance (dotnet#45251)
* Reimplement the default object pool for improved performance * Add benchmark sources Co-authored-by: Martin Taillefer <[email protected]>
1 parent e865fbf commit 53124ab

9 files changed

+444
-76
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using BenchmarkDotNet.Attributes;
7+
8+
namespace Microsoft.Extensions.ObjectPool.Microbenchmarks;
9+
10+
[MemoryDiagnoser]
11+
public class DrainRefillMultiTheaded
12+
{
13+
private DefaultObjectPool<Foo> _pool = null!;
14+
private Foo[][] _stores = null!;
15+
private ManualResetEventSlim _terminate = null!;
16+
private Task[] _tasks = null!;
17+
18+
[Params(8, 16, 64, 256, 1024, 2048)]
19+
public int Count { get; set; }
20+
21+
[Params(1, 2, 4, 8)]
22+
public int ThreadCount { get; set; }
23+
24+
[GlobalSetup]
25+
public void GlobalSetup()
26+
{
27+
_pool = new DefaultObjectPool<Foo>(new DefaultPooledObjectPolicy<Foo>(), Count);
28+
for (int i = 0; i < Count; i++)
29+
{
30+
_pool.Return(new Foo());
31+
}
32+
33+
_stores = new Foo[ThreadCount][];
34+
for (int i = 0; i < ThreadCount; i++)
35+
{
36+
_stores[i] = new Foo[Count];
37+
}
38+
39+
_terminate = new ManualResetEventSlim();
40+
41+
_tasks = new Task[ThreadCount - 1];
42+
for (int i = 0; i < ThreadCount - 1; i++)
43+
{
44+
int threadIndex = i;
45+
_tasks[i] = Task.Run(() =>
46+
{
47+
while (!_terminate.IsSet)
48+
{
49+
BenchmarkLoop(_stores[threadIndex]);
50+
}
51+
});
52+
}
53+
54+
// give ample time to the contention tasks to start running
55+
Thread.Sleep(250);
56+
}
57+
58+
[GlobalCleanup]
59+
public void GlobalCleanup()
60+
{
61+
_terminate.Set();
62+
Task.WaitAll(_tasks);
63+
_terminate.Dispose();
64+
}
65+
66+
[Benchmark]
67+
public void DrainRefillMulti()
68+
{
69+
BenchmarkLoop(_stores[ThreadCount - 1]); // take the last slot
70+
}
71+
72+
private void BenchmarkLoop(Foo[] store)
73+
{
74+
int num = (Count / ThreadCount) - 1;
75+
76+
for (int i = 0; i < num; i++)
77+
{
78+
store[i] = _pool.Get();
79+
store[i].SimulateWork();
80+
}
81+
82+
for (int i = 0; i < num; i++)
83+
{
84+
store[i].SimulateWork();
85+
_pool.Return(store[i]);
86+
}
87+
}
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
4+
using BenchmarkDotNet.Attributes;
5+
6+
namespace Microsoft.Extensions.ObjectPool.Microbenchmarks;
7+
8+
#pragma warning disable R9A038, S109
9+
10+
[MemoryDiagnoser]
11+
public class DrainRefillSingleThreaded
12+
{
13+
private DefaultObjectPool<Foo> _pool = null!;
14+
private Foo[] _store = null!;
15+
16+
[Params(8, 16, 64, 256, 1024, 2048)]
17+
public int Count { get; set; }
18+
19+
[GlobalSetup]
20+
public void GlobalSetup()
21+
{
22+
_pool = new DefaultObjectPool<Foo>(new DefaultPooledObjectPolicy<Foo>(), Count);
23+
for (int i = 0; i < Count; i++)
24+
{
25+
_pool.Return(new Foo());
26+
}
27+
28+
_store = new Foo[Count];
29+
}
30+
31+
[Benchmark]
32+
public void DrainRefillSingle()
33+
{
34+
for (int i = 0; i < Count; i++)
35+
{
36+
_store[i] = _pool.Get();
37+
}
38+
39+
for (int i = 0; i < Count; i++)
40+
{
41+
_pool.Return(_store[i]);
42+
}
43+
}
44+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
4+
using System;
5+
6+
namespace Microsoft.Extensions.ObjectPool.Microbenchmarks;
7+
8+
#pragma warning disable S109, CPR138
9+
10+
internal sealed class Foo
11+
{
12+
public int LastRandom;
13+
14+
public void SimulateWork()
15+
{
16+
// burn some cycles to simulate work being done between get and return
17+
for (int i = 0; i <= 32; i++)
18+
{
19+
LastRandom = Random.Shared.Next();
20+
}
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using BenchmarkDotNet.Attributes;
7+
8+
namespace Microsoft.Extensions.ObjectPool.Microbenchmarks;
9+
10+
[MemoryDiagnoser]
11+
public class GetReturnMultiThreaded
12+
{
13+
private const int Count = 8;
14+
15+
private DefaultObjectPool<Foo> _pool = null!;
16+
private ManualResetEventSlim _terminate = null!;
17+
private Task[] _tasks = null!;
18+
19+
[Params(1, 2, 4, 8)]
20+
public int ThreadCount { get; set; }
21+
22+
[GlobalSetup]
23+
public void GlobalSetup()
24+
{
25+
_pool = new DefaultObjectPool<Foo>(new DefaultPooledObjectPolicy<Foo>(), Count);
26+
for (int i = 0; i < Count; i++)
27+
{
28+
_pool.Return(new Foo());
29+
}
30+
31+
_terminate = new ManualResetEventSlim();
32+
33+
_tasks = new Task[ThreadCount - 1];
34+
for (int i = 0; i < ThreadCount - 1; i++)
35+
{
36+
int threadIndex = i;
37+
_tasks[i] = Task.Run(() =>
38+
{
39+
while (!_terminate.IsSet)
40+
{
41+
BenchmarkLoop();
42+
}
43+
});
44+
}
45+
46+
// give ample time to the contention tasks to start running
47+
Thread.Sleep(250);
48+
}
49+
50+
[GlobalCleanup]
51+
public void GlobalCleanup()
52+
{
53+
_terminate.Set();
54+
Task.WaitAll(_tasks);
55+
_terminate.Dispose();
56+
}
57+
58+
[Benchmark]
59+
public void GetReturnMulti()
60+
{
61+
BenchmarkLoop();
62+
}
63+
64+
private void BenchmarkLoop()
65+
{
66+
var o = _pool.Get();
67+
68+
o.SimulateWork();
69+
70+
_pool.Return(o);
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
4+
using BenchmarkDotNet.Attributes;
5+
6+
namespace Microsoft.Extensions.ObjectPool.Microbenchmarks;
7+
8+
[MemoryDiagnoser]
9+
public class GetReturnSingleThreaded
10+
{
11+
private const int Count = 8;
12+
private DefaultObjectPool<Foo> _pool = null!;
13+
14+
[GlobalSetup]
15+
public void GlobalSetup()
16+
{
17+
_pool = new DefaultObjectPool<Foo>(new DefaultPooledObjectPolicy<Foo>(), Count);
18+
for (int i = 0; i < Count; i++)
19+
{
20+
_pool.Return(new Foo());
21+
}
22+
}
23+
24+
[Benchmark]
25+
public void GetReturnSingle()
26+
{
27+
_pool.Return(_pool.Get());
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
5+
<OutputType>Exe</OutputType>
6+
<ServerGarbageCollection>true</ServerGarbageCollection>
7+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
8+
<TieredCompilation>false</TieredCompilation>
9+
<DefineConstants>$(DefineConstants);IS_BENCHMARKS</DefineConstants>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<Reference Include="Microsoft.Extensions.ObjectPool" />
14+
<Reference Include="BenchmarkDotNet" />
15+
<Compile Include="$(SharedSourceRoot)BenchmarkRunner\*.cs" />
16+
</ItemGroup>
17+
18+
</Project>

0 commit comments

Comments
 (0)