Skip to content

Commit 365247a

Browse files
author
Oren (electricessence)
committed
Improved benchmarking.
1 parent e12e37f commit 365247a

5 files changed

Lines changed: 311 additions & 12 deletions

File tree

source/IObjectPool.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ public interface IObjectPool<T> : IDisposable
1313
/// </summary>
1414
int Capacity { get; }
1515

16+
/// <summary>
17+
/// Directly calls the underlying factory that generates the items. (No pool interaction.)
18+
/// </summary>
19+
T Generate();
20+
1621
/// <summary>
1722
/// Receives an item and adds it to the pool. Ignores null references.
1823
/// WARNING: The item is considered 'dead' but resurrectable so be sure not to hold on to the item's reference.

source/ObjectPoolBase.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ protected ObjectPoolBase(Func<T> factory, int capacity = DEFAULT_CAPACITY)
2626
// Allow the GC to do the final cleanup after dispose.
2727
protected readonly Func<T> Factory;
2828

29+
public T Generate()
30+
{
31+
return Factory();
32+
}
33+
2934
protected abstract bool GiveInternal(T item);
3035

3136
public virtual void Give(T item)

tester/Benchmark.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace Open.Disposable.ObjectPools
11+
{
12+
public class Benchmark<T>
13+
where T : class
14+
{
15+
public readonly uint TestSize; // Pool capacity should be at least 2x this.
16+
public readonly uint RepeatCount; // Number of times to repeat the test from scratch.
17+
public readonly Func<IObjectPool<T>> PoolFactory;
18+
19+
public Benchmark(uint size, uint repeat, Func<IObjectPool<T>> poolFactory)
20+
{
21+
TestSize = size;
22+
RepeatCount = repeat;
23+
PoolFactory = poolFactory;
24+
}
25+
26+
public IEnumerable<TimedResult> TestOnce()
27+
{
28+
var disposeTimer = new Stopwatch();
29+
using (var pool = TimedResult.Measure(out TimedResult constructionTime, "01) Pool Construction", PoolFactory))
30+
{
31+
yield return constructionTime;
32+
33+
// Indicates how long pure construction takes. The baseline by which you should measure .Take()
34+
yield return TimedResult.Measure("02) Pool.Generate() (In Parallel)", () =>
35+
{
36+
Parallel.For(0, TestSize, i => pool.Generate());
37+
});
38+
39+
var tank = new ConcurrentBag<T>(); // This will have an effect on performance measurement, but hopefully consistently.
40+
//int remaining = 0;
41+
42+
yield return TimedResult.Measure("03) Take From Empty (In Parallel)", () =>
43+
{
44+
Parallel.For(0, TestSize, i => tank.Add(pool.Take()));
45+
});
46+
47+
yield return TimedResult.Measure("04) Give To (In Parallel)", () =>
48+
{
49+
Parallel.ForEach(tank, e => pool.Give(e));
50+
});
51+
52+
yield return TimedResult.Measure("05) Empty Pool (.TryTake())", () =>
53+
{
54+
while (pool.TryTake() != null) {
55+
// remaining++;
56+
}
57+
});
58+
59+
disposeTimer.Start();
60+
}
61+
disposeTimer.Stop();
62+
63+
yield return new TimedResult("99) Pool Disposal", disposeTimer);
64+
}
65+
66+
public IEnumerable<IEnumerable<TimedResult>> TestRepeated()
67+
{
68+
for(var i=0;i<RepeatCount;i++)
69+
{
70+
yield return TestOnce();
71+
}
72+
}
73+
74+
75+
TimedResult[] _result;
76+
public TimedResult[] Result
77+
{
78+
get
79+
{
80+
return LazyInitializer.EnsureInitialized(ref _result, ()=>
81+
TestRepeated()
82+
.SelectMany(s => s)
83+
.GroupBy(k => k.Label)
84+
.Select(g => g.Sum())
85+
.OrderBy(r => r.Label)
86+
.ToArray()
87+
);
88+
}
89+
}
90+
91+
public static TimedResult[] Results(uint size, uint repeat, Func<IObjectPool<T>> poolFactory)
92+
{
93+
return (new Benchmark<T>(size, repeat, poolFactory)).Result;
94+
}
95+
96+
}
97+
}

tester/Program.cs

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,115 @@
11
using Open.Collections;
22
using Open.Disposable;
3+
using Open.Disposable.ObjectPools;
34
using System;
45
using System.Collections.Concurrent;
6+
using System.Collections.Generic;
7+
using System.Diagnostics;
8+
using System.Linq;
59
using System.Threading;
610
using System.Threading.Tasks;
711

812
class Program
913
{
10-
static void Main(string[] args)
11-
{
14+
15+
static void ConsoleSeparator()
16+
{
17+
Console.WriteLine("------------------------------------");
18+
}
19+
20+
static void ConsoleNewLine()
21+
{
22+
Console.WriteLine();
23+
}
24+
25+
const int COUNT = 200;
26+
const int REPEAT = 5000;
27+
28+
static TimedResult[] BenchmarkResults<T>(Func<IObjectPool<T>> poolFactory)
29+
where T : class
30+
{
31+
return Benchmark<T>.Results(COUNT, REPEAT, poolFactory);
32+
}
33+
34+
static TimedResult[] BenchmarkResults<T>(uint count, uint repeat, Func<IObjectPool<T>> poolFactory)
35+
where T : class
36+
{
37+
return Benchmark<T>.Results(count, repeat, poolFactory);
38+
}
39+
40+
static TimedResult[] BenchmarkResults<T>(uint repeat, Func<IObjectPool<T>> poolFactory)
41+
where T : class
42+
{
43+
return Benchmark<T>.Results(COUNT, repeat, poolFactory);
44+
}
45+
46+
static void OutputResults<T>(Func<IObjectPool<T>> poolFactory)
47+
where T : class
48+
{
49+
var results = BenchmarkResults(poolFactory);
50+
foreach (var e in BenchmarkResults(poolFactory))
51+
Console.WriteLine(e);
52+
Console.WriteLine("{0} Total", results.Select(r => r.Duration).Aggregate((r1, r2) => r1 + r2));
53+
54+
ConsoleNewLine();
55+
}
56+
57+
static void Main(string[] args)
58+
{
59+
Console.Write("Initializing...");
60+
61+
// Run once through first to scramble initial conditions.
62+
BenchmarkResults(100, () => ConcurrentBagObjectPool.Create<object>());
63+
BenchmarkResults(100, () => LinkedListObjectPool.Create<object>());
64+
BenchmarkResults(100, () => OptimisticArrayObjectPool.Create<object>());
65+
// BenchmarkResults(100, () => BufferBlockObjectPool.Create<object>());
66+
67+
Console.SetCursorPosition(0, Console.CursorTop);
68+
69+
Console.WriteLine("ConcurrentBagObjectPool.................................");
70+
OutputResults(() => ConcurrentBagObjectPool.Create<object>());
71+
72+
Console.WriteLine("LinkedListObjectPool....................................");
73+
OutputResults(() => LinkedListObjectPool.Create<object>());
74+
75+
Console.WriteLine("OptimisticArrayObjectPool...............................");
76+
OutputResults(() => OptimisticArrayObjectPool.Create<object>());
77+
78+
//Console.WriteLine("BufferBlockObjectPool...................................");
79+
//OutputResults(() => BufferBlockObjectPool.Create<object>());
80+
81+
Console.WriteLine("(press any key when finished)");
82+
Console.ReadKey();
83+
}
84+
85+
86+
static void OldTest()
87+
{
1288
var ts = new CancellationTokenSource();
1389
ts.Cancel();
1490
var task = Task.FromResult(ts.Token);
1591

1692

17-
var pool = BufferBlockObjectPool.Create(() => new object(),1024);
93+
var pool = BufferBlockObjectPool.Create(() => new object(), 1024);
1894
var trimmer = new ObjectPoolAutoTrimmer(20, pool);
1995
var clearer = new ObjectPoolAutoTrimmer(0, pool, TimeSpan.FromSeconds(5));
2096
var tank = new ConcurrentBag<object>();
2197

22-
int count = 0;
23-
while (true)
24-
{
98+
int count = 0;
99+
while (true)
100+
{
25101
Console.WriteLine("Before {0}: {1}, {2}", count, pool.Count, tank.Count);
26102

27103
count++;
28-
tank.Add(pool.Take());
29-
count++;
104+
tank.Add(pool.Take());
105+
count++;
30106
tank.Add(new object());
31107
count++;
32108
tank.Add(new object());
33-
foreach (var o in tank.TryTakeWhile(c => c.Count > 30))
34-
pool.Give(o);
109+
foreach (var o in tank.TryTakeWhile(c => c.Count > 30))
110+
pool.Give(o);
35111

36-
Console.WriteLine("After {0}: {1}, {2}",count, pool.Count, tank.Count);
112+
Console.WriteLine("After {0}: {1}, {2}", count, pool.Count, tank.Count);
37113

38114
if (count % 30 == 0)
39115
{
@@ -49,5 +125,6 @@ static void Main(string[] args)
49125
Console.WriteLine();
50126

51127
}
128+
52129
}
53-
}
130+
}

tester/TimedResult.cs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Text;
6+
7+
namespace Open.Disposable
8+
{
9+
public struct TimedResult : IComparable<TimedResult>, IEquatable<TimedResult>
10+
{
11+
public TimedResult(string label, TimeSpan duration)
12+
{
13+
Label = label;
14+
Duration = duration;
15+
}
16+
17+
public TimedResult(string label, Stopwatch stopwatch) : this(label, stopwatch.Elapsed)
18+
{
19+
20+
}
21+
public readonly string Label;
22+
public readonly TimeSpan Duration;
23+
24+
public override string ToString()
25+
{
26+
return String.Format("{1} {0}", Label, Duration);
27+
}
28+
29+
public static TimeSpan Measure(Action action)
30+
{
31+
var sw = Stopwatch.StartNew();
32+
action();
33+
sw.Stop();
34+
return sw.Elapsed;
35+
}
36+
37+
public static TimedResult Measure(string label, Action action)
38+
{
39+
return new TimedResult(label, Measure(action));
40+
}
41+
42+
public static T Measure<T>(out TimeSpan duration, Func<T> action)
43+
{
44+
var sw = Stopwatch.StartNew();
45+
var result = action();
46+
sw.Stop();
47+
duration = sw.Elapsed;
48+
return result;
49+
}
50+
51+
public static T Measure<T>(out TimedResult measurement, string label, Func<T> action)
52+
{
53+
var result = Measure(out TimeSpan duration, action);
54+
measurement = new TimedResult(label, duration);
55+
return result;
56+
}
57+
58+
public bool Equals(TimedResult other)
59+
{
60+
return base.Equals(other) || Label == other.Label && Duration == other.Duration;
61+
}
62+
63+
public int CompareTo(TimedResult other)
64+
{
65+
if (Label != other.Label) throw new InvalidOperationException("Comparing two timed results that are not the same label.");
66+
if (Duration < other.Duration) return -1;
67+
if (Duration > other.Duration) return +1;
68+
return 0;
69+
}
70+
71+
public static bool operator <(TimedResult tr1, TimedResult tr2)
72+
{
73+
return tr1.CompareTo(tr2) < 0;
74+
}
75+
76+
public static bool operator >(TimedResult tr1, TimedResult tr2)
77+
{
78+
return tr1.CompareTo(tr2) > 0;
79+
}
80+
81+
public static bool operator <=(TimedResult tr1, TimedResult tr2)
82+
{
83+
return tr1.CompareTo(tr2) <= 0;
84+
}
85+
86+
public static bool operator >=(TimedResult tr1, TimedResult tr2)
87+
{
88+
return tr1.CompareTo(tr2) >= 0;
89+
}
90+
91+
public static TimedResult operator +(TimedResult tr1, TimedResult tr2)
92+
{
93+
var label = tr1.Label;
94+
if (label != tr2.Label) throw new InvalidOperationException("Adding two timed results that are not the same label.");
95+
return new TimedResult(label, tr1.Duration + tr2.Duration);
96+
}
97+
98+
public static TimedResult operator -(TimedResult tr1, TimedResult tr2)
99+
{
100+
var label = tr1.Label;
101+
if (label != tr2.Label) throw new InvalidOperationException("Adding two timed results that are not the same label.");
102+
return new TimedResult(label, tr1.Duration - tr2.Duration);
103+
}
104+
105+
}
106+
107+
public static class TimedResultExtensions
108+
{
109+
public static TimedResult Sum(this IEnumerable<TimedResult> results)
110+
{
111+
return results.Aggregate((a, b) => a + b);
112+
}
113+
}
114+
115+
}

0 commit comments

Comments
 (0)