1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Linq ;
4+ using BenchmarkDotNet . Attributes ;
5+ using BenchmarkDotNet . Configs ;
6+ using BenchmarkDotNet . Diagnosers ;
7+ using BenchmarkDotNet . Extensions ;
8+ using BenchmarkDotNet . Jobs ;
9+ using BenchmarkDotNet . Portability ;
10+ using BenchmarkDotNet . Reports ;
11+ using BenchmarkDotNet . Tests . XUnit ;
12+ using BenchmarkDotNet . Toolchains . InProcess . Emit ;
13+ using Xunit ;
14+ using Xunit . Abstractions ;
15+
16+ namespace BenchmarkDotNet . IntegrationTests . ManualRunning
17+ {
18+ public class ExpectedBenchmarkResultsTests : BenchmarkTestExecutor
19+ {
20+ // NativeAot takes a long time to build, so not including it in these tests.
21+ // We also don't test InProcessNoEmitToolchain because it is known to be less accurate than code-gen toolchains.
22+
23+ public ExpectedBenchmarkResultsTests ( ITestOutputHelper output ) : base ( output ) { }
24+
25+ private static IEnumerable < Type > EmptyBenchmarkTypes ( ) =>
26+ new [ ]
27+ {
28+ typeof ( EmptyVoid ) ,
29+ typeof ( EmptyByte ) ,
30+ typeof ( EmptySByte ) ,
31+ typeof ( EmptyShort ) ,
32+ typeof ( EmptyUShort ) ,
33+ typeof ( EmptyChar ) ,
34+ typeof ( EmptyInt32 ) ,
35+ typeof ( EmptyUInt32 ) ,
36+ typeof ( EmptyInt64 ) ,
37+ typeof ( EmptyUInt64 ) ,
38+ typeof ( EmptyIntPtr ) ,
39+ typeof ( EmptyUIntPtr ) ,
40+ typeof ( EmptyVoidPointer ) ,
41+ typeof ( EmptyClass )
42+ } ;
43+
44+ public static IEnumerable < object [ ] > InProcessData ( )
45+ {
46+ foreach ( var type in EmptyBenchmarkTypes ( ) )
47+ {
48+ yield return new object [ ] { type } ;
49+ }
50+ }
51+
52+ public static IEnumerable < object [ ] > CoreData ( )
53+ {
54+ foreach ( var type in EmptyBenchmarkTypes ( ) )
55+ {
56+ yield return new object [ ] { type , RuntimeMoniker . Net70 } ;
57+ yield return new object [ ] { type , RuntimeMoniker . Mono70 } ;
58+ }
59+ }
60+
61+ public static IEnumerable < object [ ] > FrameworkData ( )
62+ {
63+ foreach ( var type in EmptyBenchmarkTypes ( ) )
64+ {
65+ yield return new object [ ] { type , RuntimeMoniker . Net462 } ;
66+ yield return new object [ ] { type , RuntimeMoniker . Mono } ;
67+ }
68+ }
69+
70+ [ Theory ]
71+ [ MemberData ( nameof ( InProcessData ) ) ]
72+ public void EmptyBenchmarksReportZeroTimeAndAllocated_InProcess ( Type benchmarkType )
73+ {
74+ AssertZeroResults ( benchmarkType , ManualConfig . CreateEmpty ( )
75+ . AddJob ( Job . Default
76+ . WithToolchain ( InProcessEmitToolchain . Instance )
77+ ) ) ;
78+ }
79+
80+ [ TheoryNetCoreOnly ( "To not repeat tests in both full Framework and Core" ) ]
81+ [ MemberData ( nameof ( CoreData ) ) ]
82+ public void EmptyBenchmarksReportZeroTimeAndAllocated_Core ( Type benchmarkType , RuntimeMoniker runtimeMoniker )
83+ {
84+ AssertZeroResults ( benchmarkType , ManualConfig . CreateEmpty ( )
85+ . AddJob ( Job . Default
86+ . WithRuntime ( runtimeMoniker . GetRuntime ( ) )
87+ ) ) ;
88+ }
89+
90+ [ TheoryFullFrameworkOnly ( "Can only run full Framework and Mono tests from Framework host" ) ]
91+ [ MemberData ( nameof ( FrameworkData ) ) ]
92+ public void EmptyBenchmarksReportZeroTimeAndAllocated_Framework ( Type benchmarkType , RuntimeMoniker runtimeMoniker )
93+ {
94+ AssertZeroResults ( benchmarkType , ManualConfig . CreateEmpty ( )
95+ . AddJob ( Job . Default
96+ . WithRuntime ( runtimeMoniker . GetRuntime ( ) )
97+ ) ) ;
98+ }
99+
100+ private void AssertZeroResults ( Type benchmarkType , IConfig config )
101+ {
102+ var summary = CanExecute ( benchmarkType , config
103+ . WithSummaryStyle ( SummaryStyle . Default . WithTimeUnit ( Perfolizer . Horology . TimeUnit . Nanosecond ) )
104+ . AddDiagnoser ( new MemoryDiagnoser ( new MemoryDiagnoserConfig ( false ) ) )
105+ ) ;
106+
107+ var cpuGhz = RuntimeInformation . GetCpuInfo ( ) . MaxFrequency . Value . ToGHz ( ) ;
108+
109+ foreach ( var report in summary . Reports )
110+ {
111+ Assert . True ( cpuGhz * report . ResultStatistics . Mean < 1 , $ "Actual time was greater than 1 clock cycle.") ;
112+
113+ var overheadTime = report . AllMeasurements
114+ . Where ( m => m . IsOverhead ( ) && m . IterationStage == Engines . IterationStage . Actual )
115+ . Select ( m => m . GetAverageTime ( ) . Nanoseconds )
116+ . Average ( ) ;
117+
118+ var workloadTime = report . AllMeasurements
119+ . Where ( m => m . IsWorkload ( ) && m . IterationStage == Engines . IterationStage . Actual )
120+ . Select ( m => m . GetAverageTime ( ) . Nanoseconds )
121+ . Average ( ) ;
122+
123+ // Allow for 1 cpu cycle variance
124+ Assert . True ( overheadTime * cpuGhz < workloadTime * cpuGhz + 1 , "Overhead took more time than workload." ) ;
125+
126+ Assert . True ( ( report . GcStats . GetBytesAllocatedPerOperation ( report . BenchmarkCase ) ?? 0L ) == 0L , "Memory allocations measured above 0." ) ;
127+ }
128+ }
129+
130+ [ Fact ]
131+ public void LargeStructBenchmarksReportsNonZeroTimeAndZeroAllocated_InProcess ( )
132+ {
133+ AssertLargeStructResults ( ManualConfig . CreateEmpty ( )
134+ . AddJob ( Job . Default
135+ . WithToolchain ( InProcessEmitToolchain . Instance )
136+ ) ) ;
137+ }
138+
139+ [ TheoryNetCoreOnly ( "To not repeat tests in both full Framework and Core" ) ]
140+ [ InlineData ( RuntimeMoniker . Net70 ) ]
141+ [ InlineData ( RuntimeMoniker . Mono70 ) ]
142+ public void LargeStructBenchmarksReportsNonZeroTimeAndZeroAllocated_Core ( RuntimeMoniker runtimeMoniker )
143+ {
144+ AssertLargeStructResults ( ManualConfig . CreateEmpty ( )
145+ . AddJob ( Job . Default
146+ . WithRuntime ( runtimeMoniker . GetRuntime ( ) )
147+ ) ) ;
148+ }
149+
150+ [ TheoryFullFrameworkOnly ( "Can only run full Framework and Mono tests from Framework host" ) ]
151+ [ InlineData ( RuntimeMoniker . Net462 ) ]
152+ [ InlineData ( RuntimeMoniker . Mono ) ]
153+ public void LargeStructBenchmarksReportsNonZeroTimeAndZeroAllocated_Framework ( RuntimeMoniker runtimeMoniker )
154+ {
155+ AssertLargeStructResults ( ManualConfig . CreateEmpty ( )
156+ . AddJob ( Job . Default
157+ . WithRuntime ( runtimeMoniker . GetRuntime ( ) )
158+ ) ) ;
159+ }
160+
161+ private void AssertLargeStructResults ( IConfig config )
162+ {
163+ var summary = CanExecute < LargeStruct > ( config
164+ . WithSummaryStyle ( SummaryStyle . Default . WithTimeUnit ( Perfolizer . Horology . TimeUnit . Nanosecond ) )
165+ . AddDiagnoser ( new MemoryDiagnoser ( new MemoryDiagnoserConfig ( false ) ) )
166+ ) ;
167+
168+ var cpuGhz = RuntimeInformation . GetCpuInfo ( ) . MaxFrequency . Value . ToGHz ( ) ;
169+
170+ foreach ( var report in summary . Reports )
171+ {
172+ Assert . True ( cpuGhz * report . ResultStatistics . Mean >= 1 , $ "Actual time was less than 1 clock cycle.") ;
173+
174+ var overheadTime = report . AllMeasurements
175+ . Where ( m => m . IsOverhead ( ) && m . IterationStage == Engines . IterationStage . Actual )
176+ . Select ( m => m . GetAverageTime ( ) . Nanoseconds )
177+ . Average ( ) ;
178+
179+ var workloadTime = report . AllMeasurements
180+ . Where ( m => m . IsWorkload ( ) && m . IterationStage == Engines . IterationStage . Actual )
181+ . Select ( m => m . GetAverageTime ( ) . Nanoseconds )
182+ . Average ( ) ;
183+
184+ // Allow for 1 cpu cycle variance
185+ Assert . True ( overheadTime * cpuGhz < workloadTime * cpuGhz + 1 , "Overhead took more time than workload." ) ;
186+
187+ Assert . True ( ( report . GcStats . GetBytesAllocatedPerOperation ( report . BenchmarkCase ) ?? 0L ) == 0L , "Memory allocations measured above 0." ) ;
188+ }
189+ }
190+ }
191+
192+ public class LargeStruct
193+ {
194+ public struct Struct
195+ {
196+ // 128 bits
197+ public long l1 , l2 , l3 , l4 ,
198+ l5 , l6 , l7 , l8 ,
199+ l9 , l10 , l11 , l12 ,
200+ l13 , l14 , l15 , l16 ;
201+ }
202+
203+ [ Benchmark ] public Struct Benchmark ( ) => default ;
204+ }
205+ }
206+
207+ public class EmptyVoid { [ Benchmark ] public void Benchmark ( ) { } }
208+ public class EmptyByte { [ Benchmark ] public byte Benchmark ( ) => default ; }
209+ public class EmptySByte { [ Benchmark ] public sbyte Benchmark ( ) => default ; }
210+ public class EmptyShort { [ Benchmark ] public short Benchmark ( ) => default ; }
211+ public class EmptyUShort { [ Benchmark ] public ushort Benchmark ( ) => default ; }
212+ public class EmptyChar { [ Benchmark ] public char Benchmark ( ) => default ; }
213+ public class EmptyInt32 { [ Benchmark ] public int Benchmark ( ) => default ; }
214+ public class EmptyUInt32 { [ Benchmark ] public uint Benchmark ( ) => default ; }
215+ public class EmptyInt64 { [ Benchmark ] public long Benchmark ( ) => default ; }
216+ public class EmptyUInt64 { [ Benchmark ] public ulong Benchmark ( ) => default ; }
217+ public class EmptyIntPtr { [ Benchmark ] public IntPtr Benchmark ( ) => default ; }
218+ public class EmptyUIntPtr { [ Benchmark ] public UIntPtr Benchmark ( ) => default ; }
219+ public class EmptyVoidPointer { [ Benchmark ] public unsafe void * Benchmark ( ) => default ; }
220+ public class EmptyClass { [ Benchmark ] public object Class ( ) => default ; }
0 commit comments