Skip to content

Commit 4201887

Browse files
authored
Throughput analysis v2.0 (#519)
1 parent f853da2 commit 4201887

16 files changed

+511
-158
lines changed

BitFaster.Caching.ThroughputAnalysis/BitFaster.Caching.ThroughputAnalysis.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<OutputType>Exe</OutputType>
55
<TargetFramework>net6.0</TargetFramework>
66
<SignAssembly>False</SignAssembly>
7+
<Version>2.0.0</Version>
78
<ServerGarbageCollection>true</ServerGarbageCollection>
89
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
910
<RetainVMGarbageCollection>true</RetainVMGarbageCollection>
@@ -22,7 +23,7 @@
2223
<NoWarn>NU1701</NoWarn>
2324
</PackageReference>
2425
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
25-
<PackageReference Include="Plotly.NET.CSharp" Version="0.11.1" />
26+
<PackageReference Include="Plotly.NET" Version="4.2.0" />
2627
<PackageReference Include="Plotly.NET.ImageExport" Version="5.0.1" />
2728
</ItemGroup>
2829

BitFaster.Caching.ThroughputAnalysis/ConfigFactory.cs

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,26 @@ public class ConfigFactory
1212

1313
public static (ThroughputBenchmarkBase, IThroughputBenchConfig, int) Create(Mode mode, int cacheSize, int maxThreads)
1414
{
15-
int iterations = GetIterationCount(cacheSize);
1615
int samples = GetSampleCount(cacheSize);
1716
int n = cacheSize; // number of unique items for Zipf
1817

1918
switch (mode)
2019
{
2120
case Mode.Read:
22-
return (new ReadThroughputBenchmark(), new ZipfConfig(iterations, samples, s, n), cacheSize);
21+
return (new ReadThroughputBenchmark(), new ZipfConfig(samples, s, n), cacheSize);
2322
case Mode.ReadWrite:
2423
// cache holds 10% of all items
2524
cacheSize /= 10;
26-
return (new ReadThroughputBenchmark(), new ZipfConfig(iterations, samples, s, n), cacheSize);
25+
return (new ReadThroughputBenchmark(), new ZipfConfig(samples, s, n), cacheSize);
2726
case Mode.Update:
28-
return (new UpdateThroughputBenchmark(), new ZipfConfig(iterations, samples, s, n), cacheSize);
27+
return (new UpdateThroughputBenchmark(), new ZipfConfig(samples, s, n), cacheSize);
2928
case Mode.Evict:
30-
return (new ReadThroughputBenchmark() { Initialize = c => EvictionInit(c) }, new EvictionConfig(iterations, samples, maxThreads), cacheSize);
29+
return (new ReadThroughputBenchmark() { Initialize = c => EvictionInit(c) }, new EvictionConfig(samples, maxThreads), cacheSize);
3130
}
3231

3332
throw new InvalidOperationException();
3433
}
3534

36-
private static int GetIterationCount(int cacheSize) => cacheSize switch
37-
{
38-
< 500 => 400,
39-
< 5_000 => 200,
40-
< 10_000 => 100,
41-
< 100_000 => 50,
42-
< 1_000_000 => 25,
43-
< 10_000_000 => 5,
44-
_ => 1
45-
};
46-
4735
private static int GetSampleCount(int cacheSize) => cacheSize switch
4836
{
4937
< 5_000 => cacheSize * 4,

BitFaster.Caching.ThroughputAnalysis/Exporter.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@
99
using Plotly.NET;
1010
using Plotly.NET.ImageExport;
1111
using Plotly.NET.LayoutObjects;
12-
using Chart = Plotly.NET.CSharp.Chart;
1312

1413
namespace BitFaster.Caching.ThroughputAnalysis
1514
{
1615
public class Exporter
1716
{
1817
DataTable resultTable = new DataTable();
1918

20-
public Exporter(int maxThreads)
19+
public Exporter(int minThreads, int maxThreads)
2120
{
2221
// output:
2322
// ThreadCount 1 2 3 4 5
@@ -26,7 +25,7 @@ public Exporter(int maxThreads)
2625

2726
resultTable.Clear();
2827
resultTable.Columns.Add("ThreadCount");
29-
foreach (var tc in Enumerable.Range(1, maxThreads).ToArray())
28+
foreach (var tc in Enumerable.Range(minThreads, maxThreads - (minThreads-1)).ToArray())
3029
{
3130
resultTable.Columns.Add(tc.ToString());
3231
}
@@ -88,10 +87,12 @@ public void ExportPlot(Mode mode, int cacheSize)
8887
string name = row[0].ToString();
8988
for (var i = 1; i < resultTable.Columns.Count; i++)
9089
{
90+
// convert back to millions
9191
rowData.Add(double.Parse(row[i].ToString()) * 1_000_000);
9292
}
9393

94-
var chart = Chart.Line<int, double, string>(columns, rowData, Name: name, MarkerColor: MapColor(name));
94+
// var chart = Chart.Line<int, double, string>(columns, rowData, Name: name, MarkerColor: MapColor(name));
95+
var chart = Chart2D.Chart.Line<int, double, string>(columns, rowData, Name: name, MarkerColor: MapColor(name));
9596
charts.Add(chart);
9697

9798
var combined = Chart.Combine(charts);

BitFaster.Caching.ThroughputAnalysis/FastZipf.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace BitFaster.Caching.ThroughputAnalysis
88
/// </summary>
99
public class FastZipf
1010
{
11-
private static readonly Random srandom = new(666);
11+
private static readonly Random srandom = new(42);
1212

1313
/// <summary>
1414
/// Generate a zipf distribution.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace BitFaster.Caching.ThroughputAnalysis
2+
{
3+
internal class Format
4+
{
5+
public static string Throughput(double thru)
6+
{
7+
string dformat = "0.00;-0.00";
8+
string raw = thru.ToString(dformat);
9+
return raw.PadLeft(7, ' ');
10+
}
11+
}
12+
}

BitFaster.Caching.ThroughputAnalysis/Host.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ public class Host
88
{
99
public static void PrintInfo()
1010
{
11+
var Reference = typeof(Host).Assembly;
12+
var Version = Reference.GetName().Version;
13+
14+
Console.WriteLine($"Throughput Analysis {Version}");
15+
1116
var hostinfo = HostEnvironmentInfo.GetCurrent();
1217

1318
foreach (var segment in hostinfo.ToFormattedString())
@@ -35,8 +40,6 @@ public static void PrintInfo()
3540

3641
Console.ResetColor();
3742
}
38-
39-
Console.WriteLine();
4043
}
4144

4245
public static int GetAvailableCoreCount()
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Perfolizer.Mathematics.Common;
4+
using Perfolizer.Mathematics.OutlierDetection;
5+
6+
namespace BitFaster.Caching.ThroughputAnalysis
7+
{
8+
// https://github.com/dotnet/BenchmarkDotNet/blob/b4ac9df9f7890ca9669e2b9c8835af35c072a453/src/BenchmarkDotNet/Mathematics/MeasurementsStatistics.cs#L13
9+
internal readonly ref struct MeasurementsStatistics
10+
{
11+
/// <summary>
12+
/// Standard error.
13+
/// </summary>
14+
public double StandardError { get; }
15+
16+
/// <summary>
17+
/// Mean.
18+
/// </summary>
19+
public double Mean { get; }
20+
21+
/// <summary>
22+
/// 99.9% confidence interval.
23+
/// </summary>
24+
public ConfidenceInterval ConfidenceInterval { get; }
25+
26+
private MeasurementsStatistics(double standardError, double mean, ConfidenceInterval confidenceInterval)
27+
{
28+
StandardError = standardError;
29+
Mean = mean;
30+
ConfidenceInterval = confidenceInterval;
31+
}
32+
33+
public static MeasurementsStatistics Calculate(List<double> measurements, OutlierMode outlierMode)
34+
{
35+
int n = measurements.Count;
36+
if (n == 0)
37+
throw new InvalidOperationException("StatSummary: Sequence contains no elements");
38+
39+
double sum = Sum(measurements);
40+
double mean = sum / n;
41+
42+
double variance = Variance(measurements, n, mean);
43+
double standardDeviation = Math.Sqrt(variance);
44+
double standardError = standardDeviation / Math.Sqrt(n);
45+
var confidenceInterval = new ConfidenceInterval(mean, standardError, n);
46+
47+
if (outlierMode == OutlierMode.DontRemove) // most simple scenario is done without allocations! but this is not the default case
48+
return new MeasurementsStatistics(standardError, mean, confidenceInterval);
49+
50+
measurements.Sort(); // sort in place
51+
52+
double q1, q3;
53+
54+
if (n == 1)
55+
q1 = q3 = measurements[0];
56+
else
57+
{
58+
q1 = GetQuartile(measurements, measurements.Count / 2);
59+
q3 = GetQuartile(measurements, measurements.Count * 3 / 2);
60+
}
61+
62+
double interquartileRange = q3 - q1;
63+
double lowerFence = q1 - 1.5 * interquartileRange;
64+
double upperFence = q3 + 1.5 * interquartileRange;
65+
66+
SumWithoutOutliers(outlierMode, measurements, lowerFence, upperFence, out sum, out n); // updates sum and N
67+
mean = sum / n;
68+
69+
variance = VarianceWithoutOutliers(outlierMode, measurements, n, mean, lowerFence, upperFence);
70+
standardDeviation = Math.Sqrt(variance);
71+
standardError = standardDeviation / Math.Sqrt(n);
72+
confidenceInterval = new ConfidenceInterval(mean, standardError, n);
73+
74+
return new MeasurementsStatistics(standardError, mean, confidenceInterval);
75+
}
76+
77+
private static double Sum(List<double> measurements)
78+
{
79+
double sum = 0;
80+
foreach (var m in measurements)
81+
sum += m;
82+
return sum;
83+
}
84+
85+
private static void SumWithoutOutliers(OutlierMode outlierMode, List<double> measurements,
86+
double lowerFence, double upperFence, out double sum, out int n)
87+
{
88+
sum = 0;
89+
n = 0;
90+
91+
foreach (var m in measurements)
92+
if (!IsOutlier(outlierMode, m, lowerFence, upperFence))
93+
{
94+
sum += m;
95+
++n;
96+
}
97+
}
98+
99+
private static double Variance(List<double> measurements, int n, double mean)
100+
{
101+
if (n == 1)
102+
return 0;
103+
104+
double variance = 0;
105+
foreach (var m in measurements)
106+
variance += (m - mean) * (m - mean) / (n - 1);
107+
108+
return variance;
109+
}
110+
111+
private static double VarianceWithoutOutliers(OutlierMode outlierMode, List<double> measurements, int n, double mean, double lowerFence, double upperFence)
112+
{
113+
if (n == 1)
114+
return 0;
115+
116+
double variance = 0;
117+
foreach (var m in measurements)
118+
if (!IsOutlier(outlierMode, m, lowerFence, upperFence))
119+
variance += (m - mean) * (m - mean) / (n - 1);
120+
121+
return variance;
122+
}
123+
124+
private static double GetQuartile(List<double> measurements, int count)
125+
{
126+
if (count % 2 == 0)
127+
return (measurements[count / 2 - 1] + measurements[count / 2]) / 2;
128+
129+
return measurements[count / 2];
130+
}
131+
132+
private static bool IsOutlier(OutlierMode outlierMode, double value, double lowerFence, double upperFence)
133+
{
134+
switch (outlierMode)
135+
{
136+
case OutlierMode.DontRemove:
137+
return false;
138+
case OutlierMode.RemoveUpper:
139+
return value > upperFence;
140+
case OutlierMode.RemoveLower:
141+
return value < lowerFence;
142+
case OutlierMode.RemoveAll:
143+
return value < lowerFence || value > upperFence;
144+
default:
145+
throw new ArgumentOutOfRangeException(nameof(outlierMode), outlierMode, null);
146+
}
147+
}
148+
}
149+
}

BitFaster.Caching.ThroughputAnalysis/ParallelBenchmark.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Diagnostics;
4-
using System.Linq;
5-
using System.Reflection;
6-
using System.Text;
73
using System.Threading;
8-
using System.Threading.Tasks;
94

105
namespace BitFaster.Caching.ThroughputAnalysis
116
{
127
public class ParallelBenchmark
138
{
14-
public static TimeSpan Run(Action<int> action, int threads)
9+
public static TimeSpan Run(Action<int> action, int threadCount)
1510
{
16-
Task[] tasks = new Task[threads];
17-
ManualResetEventSlim mre = new ManualResetEventSlim();
11+
Thread[] threads = new Thread[threadCount];
12+
using ManualResetEventSlim signalStart = new ManualResetEventSlim();
1813

19-
Action<int> syncStart = taskId =>
14+
Action<int> syncStart = (taskId) =>
2015
{
21-
mre.Wait();
16+
signalStart.Wait();
2217
action(taskId);
2318
};
2419

25-
for (int i = 0; i < tasks.Length; i++)
20+
for (int i = 0; i < threads.Length; i++)
2621
{
2722
int index = i;
28-
tasks[i] = Task.Factory.StartNew(() => syncStart(index), TaskCreationOptions.LongRunning);
23+
threads[i] = new Thread(() => syncStart(index));
24+
threads[i].Start();
2925
}
3026

3127
// try to mitigate spam from MemoryCache
@@ -35,8 +31,12 @@ public static TimeSpan Run(Action<int> action, int threads)
3531
}
3632

3733
var sw = Stopwatch.StartNew();
38-
mre.Set();
39-
Task.WaitAll(tasks);
34+
signalStart.Set();
35+
for (int i = 0; i < threads.Length; i++)
36+
{
37+
threads[i].Join();
38+
}
39+
4040
return sw.Elapsed;
4141
}
4242
}

0 commit comments

Comments
 (0)