88using BenchmarkDotNet . Diagnosers ;
99using BenchmarkDotNet . Engines ;
1010using BenchmarkDotNet . Extensions ;
11+ using BenchmarkDotNet . IntegrationTests . ManualRunning ;
1112using BenchmarkDotNet . Jobs ;
1213using BenchmarkDotNet . Portability ;
1314using BenchmarkDotNet . Reports ;
@@ -48,15 +49,15 @@ private static IEnumerable<Type> EmptyBenchmarkTypes() =>
4849 typeof ( EmptyClass )
4950 } ;
5051
51- public static IEnumerable < object [ ] > InProcessData ( )
52+ public static IEnumerable < object [ ] > EmptyInProcessData ( )
5253 {
5354 foreach ( var type in EmptyBenchmarkTypes ( ) )
5455 {
5556 yield return new object [ ] { type } ;
5657 }
5758 }
5859
59- public static IEnumerable < object [ ] > CoreData ( )
60+ public static IEnumerable < object [ ] > EmptyCoreData ( )
6061 {
6162 foreach ( var type in EmptyBenchmarkTypes ( ) )
6263 {
@@ -65,7 +66,7 @@ public static IEnumerable<object[]> CoreData()
6566 }
6667 }
6768
68- public static IEnumerable < object [ ] > FrameworkData ( )
69+ public static IEnumerable < object [ ] > EmptyFrameworkData ( )
6970 {
7071 foreach ( var type in EmptyBenchmarkTypes ( ) )
7172 {
@@ -75,7 +76,7 @@ public static IEnumerable<object[]> FrameworkData()
7576 }
7677
7778 [ Theory ]
78- [ MemberData ( nameof ( InProcessData ) ) ]
79+ [ MemberData ( nameof ( EmptyInProcessData ) ) ]
7980 public void EmptyBenchmarksReportZeroTimeAndAllocated_InProcess ( Type benchmarkType )
8081 {
8182 AssertZeroResults ( benchmarkType , ManualConfig . CreateEmpty ( )
@@ -85,7 +86,7 @@ public void EmptyBenchmarksReportZeroTimeAndAllocated_InProcess(Type benchmarkTy
8586 }
8687
8788 [ TheoryEnvSpecific ( "To not repeat tests in both Full .NET Framework and Core" , EnvRequirement . DotNetCoreOnly ) ]
88- [ MemberData ( nameof ( CoreData ) ) ]
89+ [ MemberData ( nameof ( EmptyCoreData ) ) ]
8990 public void EmptyBenchmarksReportZeroTimeAndAllocated_Core ( Type benchmarkType , RuntimeMoniker runtimeMoniker )
9091 {
9192 AssertZeroResults ( benchmarkType , ManualConfig . CreateEmpty ( )
@@ -95,7 +96,7 @@ public void EmptyBenchmarksReportZeroTimeAndAllocated_Core(Type benchmarkType, R
9596 }
9697
9798 [ TheoryEnvSpecific ( "Can only run Full .NET Framework and Mono tests from Framework host" , EnvRequirement . FullFrameworkOnly ) ]
98- [ MemberData ( nameof ( FrameworkData ) ) ]
99+ [ MemberData ( nameof ( EmptyFrameworkData ) ) ]
99100 public void EmptyBenchmarksReportZeroTimeAndAllocated_Framework ( Type benchmarkType , RuntimeMoniker runtimeMoniker )
100101 {
101102 AssertZeroResults ( benchmarkType , ManualConfig . CreateEmpty ( )
@@ -129,57 +130,91 @@ private void AssertZeroResults(Type benchmarkType, IConfig config)
129130 }
130131 }
131132
132- [ Fact ]
133- public void DifferentSizedStructsBenchmarksReportsNonZeroTimeAndZeroAllocated_InProcess ( )
133+ private static IEnumerable < Type > NonEmptyBenchmarkTypes ( ) =>
134+ new [ ]
135+ {
136+ typeof ( DifferentSizedStructs ) ,
137+ typeof ( ActualWork )
138+ } ;
139+
140+ public static IEnumerable < object [ ] > NonEmptyInProcessData ( )
141+ {
142+ foreach ( var type in NonEmptyBenchmarkTypes ( ) )
143+ {
144+ yield return new object [ ] { type } ;
145+ }
146+ }
147+
148+ public static IEnumerable < object [ ] > NonEmptyCoreData ( )
149+ {
150+ foreach ( var type in NonEmptyBenchmarkTypes ( ) )
151+ {
152+ yield return new object [ ] { type , RuntimeMoniker . Net70 } ;
153+ yield return new object [ ] { type , RuntimeMoniker . Mono70 } ;
154+ }
155+ }
156+
157+ public static IEnumerable < object [ ] > NonEmptyFrameworkData ( )
158+ {
159+ foreach ( var type in NonEmptyBenchmarkTypes ( ) )
160+ {
161+ yield return new object [ ] { type , RuntimeMoniker . Net462 } ;
162+ yield return new object [ ] { type , RuntimeMoniker . Mono } ;
163+ }
164+ }
165+
166+ [ Theory ]
167+ [ MemberData ( nameof ( NonEmptyInProcessData ) ) ]
168+ public void NonEmptyBenchmarksReportsNonZeroTimeAndZeroAllocated_InProcess ( Type benchmarkType )
134169 {
135- AssertDifferentSizedStructsResults ( ManualConfig . CreateEmpty ( )
170+ AssertNonZeroResults ( benchmarkType , ManualConfig . CreateEmpty ( )
136171 . AddJob ( Job . Default
137172 . WithToolchain ( InProcessEmitToolchain . Instance )
138173 ) ) ;
139174 }
140175
141176 [ TheoryEnvSpecific ( "To not repeat tests in both Full .NET Framework and Core" , EnvRequirement . DotNetCoreOnly ) ]
142- [ InlineData ( RuntimeMoniker . Net70 ) ]
143- [ InlineData ( RuntimeMoniker . Mono70 ) ]
144- public void DifferentSizedStructsBenchmarksReportsNonZeroTimeAndZeroAllocated_Core ( RuntimeMoniker runtimeMoniker )
177+ [ MemberData ( nameof ( NonEmptyCoreData ) ) ]
178+ public void NonEmptyBenchmarksReportsNonZeroTimeAndZeroAllocated_Core ( Type benchmarkType , RuntimeMoniker runtimeMoniker )
145179 {
146- AssertDifferentSizedStructsResults ( ManualConfig . CreateEmpty ( )
180+ AssertNonZeroResults ( benchmarkType , ManualConfig . CreateEmpty ( )
147181 . AddJob ( Job . Default
148182 . WithRuntime ( runtimeMoniker . GetRuntime ( ) )
149183 ) ) ;
150184 }
151185
152186 [ TheoryEnvSpecific ( "Can only run Full .NET Framework and Mono tests from Framework host" , EnvRequirement . FullFrameworkOnly ) ]
153- [ InlineData ( RuntimeMoniker . Net462 ) ]
154- [ InlineData ( RuntimeMoniker . Mono ) ]
155- public void DifferentSizedStructsBenchmarksReportsNonZeroTimeAndZeroAllocated_Framework ( RuntimeMoniker runtimeMoniker )
187+ [ MemberData ( nameof ( NonEmptyFrameworkData ) ) ]
188+ public void NonEmptyBenchmarksReportsNonZeroTimeAndZeroAllocated_Framework ( Type benchmarkType , RuntimeMoniker runtimeMoniker )
156189 {
157- AssertDifferentSizedStructsResults ( ManualConfig . CreateEmpty ( )
190+ AssertNonZeroResults ( benchmarkType , ManualConfig . CreateEmpty ( )
158191 . AddJob ( Job . Default
159192 . WithRuntime ( runtimeMoniker . GetRuntime ( ) )
160193 ) ) ;
161194 }
162195
163- private void AssertDifferentSizedStructsResults ( IConfig config )
196+ private void AssertNonZeroResults ( Type benchmarkType , IConfig config )
164197 {
165- var summary = CanExecute < DifferentSizedStructs > ( config
198+ var summary = CanExecute ( benchmarkType , config
166199 . WithSummaryStyle ( SummaryStyle . Default . WithTimeUnit ( TimeUnit . Nanosecond ) )
167200 . AddDiagnoser ( new MemoryDiagnoser ( new MemoryDiagnoserConfig ( false ) ) )
168201 ) ;
169202
170203 var cpuResolution = RuntimeInformation . GetCpuInfo ( ) . MaxFrequency ? . ToResolution ( ) ?? FallbackCpuResolutionValue ;
171- var threshold = Threshold . Create ( ThresholdUnit . Nanoseconds , cpuResolution . Nanoseconds ) ;
172204
173205 foreach ( var report in summary . Reports )
174206 {
175207 var workloadMeasurements = report . AllMeasurements . Where ( m => m . Is ( IterationMode . Workload , IterationStage . Actual ) ) . GetStatistics ( ) . WithoutOutliers ( ) ;
176208 var overheadMeasurements = report . AllMeasurements . Where ( m => m . Is ( IterationMode . Overhead , IterationStage . Actual ) ) . GetStatistics ( ) . WithoutOutliers ( ) ;
177209
178- bool isZero = ZeroMeasurementHelper . CheckZeroMeasurementTwoSamples ( workloadMeasurements , overheadMeasurements , threshold ) ;
210+ // We use the default threshold here rather than using cpu resolution,
211+ // because modern cpus can execute multiple instructions per clock cycle,
212+ // resulting in measurements greater than 0 but less than 1 clock cycle.
213+ // (example: Intel Core i9-9880H CPU 2.30GHz reports 0.2852 ns for `_field++;`)
214+ bool isZero = ZeroMeasurementHelper . CheckZeroMeasurementTwoSamples ( workloadMeasurements , overheadMeasurements ) ;
179215 Assert . False ( isZero , $ "Actual time was 0.") ;
180216
181- isZero = ZeroMeasurementHelper . CheckZeroMeasurementTwoSamples ( overheadMeasurements , workloadMeasurements , threshold ) ;
182- Assert . True ( isZero , "Overhead took more time than workload." ) ;
217+ // We don't need to check if overhead was greater. It's guaranteed less if the actual time was not zero.
183218
184219 Assert . True ( ( report . GcStats . GetBytesAllocatedPerOperation ( report . BenchmarkCase ) ?? 0L ) == 0L , "Memory allocations measured above 0." ) ;
185220 }
@@ -209,13 +244,25 @@ public struct Struct128
209244 l9 , l10 , l11 , l12 ,
210245 l13 , l14 , l15 , l16 ;
211246 }
247+ }
248+
249+ public class DifferentSizedStructs
250+ {
251+ [ Benchmark ] public Struct16 Struct16 ( ) => default ;
252+ [ Benchmark ] public Struct32 Struct32 ( ) => default ;
253+ [ Benchmark ] public Struct64 Struct64 ( ) => default ;
254+ [ Benchmark ] public Struct128 Struct128 ( ) => default ;
255+ }
256+
257+ public class ActualWork
258+ {
259+ public int _field ;
212260
213- public class DifferentSizedStructs
261+ [ Benchmark ]
262+ public void IncrementField ( )
214263 {
215- [ Benchmark ] public Struct16 Struct16 ( ) => default ;
216- [ Benchmark ] public Struct32 Struct32 ( ) => default ;
217- [ Benchmark ] public Struct64 Struct64 ( ) => default ;
218- [ Benchmark ] public Struct128 Struct128 ( ) => default ;
264+ _field ++ ;
265+ _field ++ ;
219266 }
220267}
221268
0 commit comments