Skip to content

Commit a27a1be

Browse files
Add the bench project
1 parent 156479c commit a27a1be

File tree

2 files changed

+363
-0
lines changed

2 files changed

+363
-0
lines changed
Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
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+
using System.Threading;
6+
using System.Collections;
7+
using System.Diagnostics;
8+
using System.Collections.Generic;
9+
using System.Runtime.CompilerServices;
10+
using System.Runtime.InteropServices;
11+
12+
public class Benchmarks
13+
{
14+
private static long s_startTimeStamp;
15+
private static TimeSpan s_currentBenchTotalTime;
16+
private static string s_currentBenchName;
17+
private static long s_currentBenchRepeatCount;
18+
19+
public static int Main(string[] args)
20+
{
21+
bool doEHBench = args.AsSpan().Contains("-e");
22+
bool doCidBench = args.AsSpan().Contains("-i");
23+
bool doDelBench = args.AsSpan().Contains("-d");
24+
bool doPIBench = args.AsSpan().Contains("-pi");
25+
bool doRpiBench = args.AsSpan().Contains("-rpi");
26+
if (doEHBench)
27+
{
28+
Bench_UnwindWithCatch();
29+
Bench_UnwindWithFault();
30+
Bench_ThrowCatch();
31+
Bench_Rethrow();
32+
}
33+
if (doCidBench)
34+
{
35+
Bench_InterfaceDispatch_Monomorphic();
36+
}
37+
if (doDelBench)
38+
{
39+
Bench_DelegateEquality_Positive();
40+
}
41+
if (doPIBench)
42+
{
43+
Bench_PInvoke();
44+
}
45+
if (doRpiBench)
46+
{
47+
Bench_ReversePInvoke();
48+
}
49+
50+
return 100;
51+
}
52+
53+
private static void Bench_UnwindWithCatch()
54+
{
55+
static void Bench_UnwindWithCatchRecursive(int depth)
56+
{
57+
if (depth == 0)
58+
{
59+
throw new BenchException();
60+
}
61+
62+
try
63+
{
64+
Bench_UnwindWithCatchRecursive(depth - 1);
65+
}
66+
catch (NullReferenceException) { }
67+
}
68+
69+
const int UnwindDepth = 2000;
70+
const int RepeatCount = 50;
71+
72+
StartBench($"Bench_UnwindWithCatch<{RepeatCount}>(depth: {UnwindDepth})");
73+
for (int i = 0; i < RepeatCount; i++)
74+
{
75+
try
76+
{
77+
Bench_UnwindWithCatchRecursive(UnwindDepth);
78+
}
79+
catch { }
80+
}
81+
EndBench();
82+
}
83+
84+
private static void Bench_UnwindWithFault()
85+
{
86+
static void Bench_UnwindWithFaultRecursive(int depth)
87+
{
88+
if (depth == 0)
89+
{
90+
throw new BenchException();
91+
}
92+
93+
try
94+
{
95+
Bench_UnwindWithFaultRecursive(depth - 1);
96+
}
97+
finally
98+
{
99+
int x = 0;
100+
Volatile.Write(ref x, 0);
101+
}
102+
}
103+
104+
const int UnwindDepth = 2000;
105+
const int RepeatCount = 50;
106+
107+
StartBench($"Bench_UnwindWithFault<{RepeatCount}>(depth: {UnwindDepth})");
108+
for (int i = 0; i < RepeatCount; i++)
109+
{
110+
try
111+
{
112+
Bench_UnwindWithFaultRecursive(UnwindDepth);
113+
}
114+
catch { }
115+
}
116+
EndBench();
117+
}
118+
119+
private static void Bench_ThrowCatch()
120+
{
121+
static void Bench_ThrowCatchRecursive(int depth)
122+
{
123+
if (depth == 0)
124+
{
125+
throw new BenchException();
126+
}
127+
128+
try
129+
{
130+
Bench_ThrowCatchRecursive(depth - 1);
131+
}
132+
catch (BenchException)
133+
{
134+
throw new BenchException();
135+
}
136+
}
137+
138+
const int UnwindDepth = 125;
139+
const int RepeatCount = 200;
140+
141+
StartBench($"Bench_ThrowCatch<{RepeatCount}>(depth: {UnwindDepth})");
142+
for (int i = 0; i < RepeatCount; i++)
143+
{
144+
try
145+
{
146+
Bench_ThrowCatchRecursive(UnwindDepth);
147+
}
148+
catch { }
149+
}
150+
EndBench();
151+
}
152+
153+
private static void Bench_Rethrow()
154+
{
155+
static void Bench_RethrowRecursive(int depth)
156+
{
157+
if (depth == 0)
158+
{
159+
throw new BenchException();
160+
}
161+
162+
try
163+
{
164+
Bench_RethrowRecursive(depth - 1);
165+
}
166+
catch { throw; }
167+
}
168+
169+
const int UnwindDepth = 125;
170+
const int RepeatCount = 200;
171+
172+
StartBench($"Bench_Rethrow<{RepeatCount}>(depth: {UnwindDepth})");
173+
for (int i = 0; i < RepeatCount; i++)
174+
{
175+
try
176+
{
177+
Bench_RethrowRecursive(UnwindDepth);
178+
}
179+
catch { }
180+
}
181+
EndBench();
182+
}
183+
184+
private static void Bench_InterfaceDispatch_Monomorphic()
185+
{
186+
const long RepeatCount = 16_000_000;
187+
188+
ICollection obj = new List<int>();
189+
190+
StartBench("Bench_InterfaceDispatch_Monomorphic");
191+
for (long i = 0; i < RepeatCount; i++)
192+
{
193+
if (Volatile.Read(ref obj).Count != 0)
194+
{
195+
break;
196+
}
197+
}
198+
EndBench();
199+
}
200+
201+
private static void Bench_DelegateEquality_Positive()
202+
{
203+
const int RepeatCount = 10_000_000;
204+
205+
void DoBench(MulticastDelegate delOne, MulticastDelegate delTwo, string kind)
206+
{
207+
StartBench($"Bench_DelegateEquality_Positive_{kind}<{RepeatCount}>()");
208+
for (int i = 0; i < RepeatCount; i++)
209+
{
210+
if (!delOne.Equals(delTwo))
211+
{
212+
break;
213+
}
214+
}
215+
EndBench();
216+
}
217+
218+
// Simple open static delegates.
219+
Action delOpenStaticOne = (Action)Delegate.CreateDelegate(typeof(Action), typeof(DelegateMethods).GetMethod("DoNothing_Static"));
220+
Action delOpenStaticTwo = (Action)Delegate.CreateDelegate(typeof(Action), typeof(DelegateMethods).GetMethod("DoNothing_Static"));
221+
DoBench(delOpenStaticOne, delOpenStaticTwo, "OpenStatic");
222+
223+
// Simple closed static delegates.
224+
DelegateMethods obj = new();
225+
Action delClosedStaticOne = Hide<Action>(obj.DoNothing_Extension);
226+
Action delClosedStaticTwo = Hide<Action>(obj.DoNothing_Extension);
227+
DoBench(delClosedStaticOne, delClosedStaticTwo, "ClosedStatic");
228+
229+
// Simple closed instance delegates.
230+
Action delClosedInstanceOne = Hide<Action>(obj.DoNothing_Instance);
231+
Action delClosedInstanceTwo = Hide<Action>(obj.DoNothing_Instance);
232+
DoBench(delClosedInstanceOne, delClosedInstanceTwo, "ClosedInstance");
233+
}
234+
235+
private static void Bench_PInvoke()
236+
{
237+
const int RepeatCount = 10_000_000;
238+
239+
StartBench("Bench_PInvoke", RepeatCount);
240+
for (int i = 0; i < RepeatCount; i++)
241+
{
242+
free(0); // No-op.
243+
}
244+
EndBench();
245+
}
246+
247+
private static void Bench_ReversePInvoke()
248+
{
249+
const int RepeatCount = 10_000_000;
250+
251+
long beginCostOfPITransitions = Stopwatch.GetTimestamp();
252+
for (int i = 0; i < RepeatCount; i++)
253+
{
254+
free(0); // No-op. Technically also measures call overhead.
255+
}
256+
TimeSpan costOfPITransitions = Stopwatch.GetElapsedTime(beginCostOfPITransitions);
257+
258+
[DllImport("*")]
259+
static extern void ReversePInvokeMethod_Empty();
260+
261+
StartBench("Bench_ReversePInvoke_Empty", RepeatCount, costOfPITransitions);
262+
for (int i = 0; i < RepeatCount; i++)
263+
{
264+
ReversePInvokeMethod_Empty(); // No-op.
265+
}
266+
EndBench();
267+
268+
[DllImport("*")]
269+
static extern void ReversePInvokeMethod_WithEH(int doThrow);
270+
271+
StartBench("Bench_ReversePInvoke_WithEH", RepeatCount, costOfPITransitions);
272+
for (int i = 0; i < RepeatCount; i++)
273+
{
274+
ReversePInvokeMethod_WithEH(doThrow: 0); // No-op.
275+
}
276+
EndBench();
277+
}
278+
279+
[UnmanagedCallersOnly(EntryPoint = "ReversePInvokeMethod_Empty")]
280+
[MethodImpl(MethodImplOptions.NoInlining)]
281+
private static void ReversePInvokeMethod_Empty_Impl()
282+
{
283+
}
284+
285+
[UnmanagedCallersOnly(EntryPoint = "ReversePInvokeMethod_WithEH")]
286+
[MethodImpl(MethodImplOptions.NoInlining)]
287+
private static void ReversePInvokeMethod_WithEH_Impl(int doThrow)
288+
{
289+
if (doThrow != 0)
290+
{
291+
throw new Exception();
292+
}
293+
}
294+
295+
[DllImport("*")] // This is vulnerable to LLVM inlining, but currently we hide everything behind an indirection so it is not a problem.
296+
private static extern void free(nint p);
297+
298+
private static void StartBench(string name, long repeatCount = 0, TimeSpan overhead = default)
299+
{
300+
StartBenchScope(name, repeatCount, overhead);
301+
StartBenchInterval();
302+
}
303+
304+
private static void EndBench()
305+
{
306+
EndBenchInterval();
307+
EndBenchScope();
308+
}
309+
310+
private static void StartBenchScope(string name, long repeatCount, TimeSpan overhead)
311+
{
312+
s_currentBenchName = name;
313+
s_currentBenchRepeatCount = repeatCount;
314+
s_currentBenchTotalTime = -overhead;
315+
}
316+
317+
private static void EndBenchScope()
318+
{
319+
string repeatCount = "";
320+
if (s_currentBenchRepeatCount != 0)
321+
{
322+
repeatCount = $" ({s_currentBenchTotalTime.TotalNanoseconds / s_currentBenchRepeatCount:0.00} ns / op)";
323+
}
324+
Console.WriteLine($"{s_currentBenchName} took: {s_currentBenchTotalTime.TotalMilliseconds:0} ms{repeatCount}");
325+
}
326+
327+
private static void StartBenchInterval()
328+
{
329+
s_startTimeStamp = Stopwatch.GetTimestamp();
330+
}
331+
332+
private static void EndBenchInterval()
333+
{
334+
s_currentBenchTotalTime += Stopwatch.GetElapsedTime(s_startTimeStamp);
335+
}
336+
337+
[MethodImpl(MethodImplOptions.NoInlining)]
338+
private static T Hide<T>(T value) => value;
339+
}
340+
341+
public class BenchException : Exception { }
342+
343+
public class DelegateMethods
344+
{
345+
public void DoNothing_Instance() { }
346+
public static void DoNothing_Static() { }
347+
}
348+
349+
public static class DelegateMethod_Extensions
350+
{
351+
public static void DoNothing_Extension(this object @this) { }
352+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<CLRTestKind>BuildAndRun</CLRTestKind>
5+
<CLRTestPriority>0</CLRTestPriority>
6+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<Compile Include="Benchmarks.cs" />
10+
</ItemGroup>
11+
</Project>

0 commit comments

Comments
 (0)