├── .gitignore ├── Categories.cs ├── DontForceGcCollectionsConfig.cs ├── LICENSE ├── Memory ├── Pooling.cs └── SmallAllocations.cs ├── MultipleRuntimesConfig.cs ├── NuGet.Config ├── Program.cs ├── README.md ├── References └── InitializingBigStructs.cs ├── Span ├── SpanForDifferentRuntimes.cs ├── SpanIndexer.cs ├── SpanVsArray_Indexer.cs └── SubstringVsSubslice.cs ├── StateOfTheDotNetPerformance.csproj ├── StateOfTheDotNetPerformance.sln ├── ValueTaskOverheadBenchmarks.cs └── ValueTypesVsReferenceTypes ├── DataLocality.cs └── NoGC.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /Categories.cs: -------------------------------------------------------------------------------- 1 | namespace StateOfTheDotNetPerformance 2 | { 3 | public class Categories 4 | { 5 | public const string ValueTypesVsReferenceTypes = "Value Types vs Reference Types"; 6 | public const string SpanVsArray = "Span"; 7 | } 8 | } -------------------------------------------------------------------------------- /DontForceGcCollectionsConfig.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Configs; 2 | using BenchmarkDotNet.Jobs; 3 | 4 | namespace StateOfTheDotNetPerformance 5 | { 6 | public class DontForceGcCollectionsConfig : ManualConfig 7 | { 8 | public DontForceGcCollectionsConfig() 9 | { 10 | Add(Job.Default 11 | .With(new GcMode() 12 | { 13 | Force = false // tell BenchmarkDotNet not to force GC collections after every iteration 14 | })); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Adam Sitnik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Memory/Pooling.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Engines; 3 | using System.Buffers; 4 | 5 | namespace StateOfTheDotNetPerformance.Memory 6 | { 7 | [MemoryDiagnoser] 8 | [Config(typeof(DontForceGcCollectionsConfig))] // we don't want to interfere with GC, we want to include it's impact 9 | public class Pooling 10 | { 11 | [Params((int)1E+2, // 100 bytes 12 | (int)1E+3, // 1 000 bytes = 1 KB 13 | (int)1E+4, // 10 000 bytes = 10 KB 14 | (int)1E+5, // 100 000 bytes = 100 KB 15 | (int)1E+6, // 1 000 000 bytes = 1 MB 16 | (int)1E+7)] // 10 000 000 bytes = 10 MB 17 | public int SizeInBytes { get; set; } 18 | 19 | private ArrayPool sizeAwarePool; 20 | 21 | [GlobalSetup] 22 | public void GlobalSetup() => sizeAwarePool = ArrayPool.Create(SizeInBytes + 1, 10); // let's create the pool that knows the real max size 23 | 24 | [Benchmark] 25 | public void Allocate() => DeadCodeEliminationHelper.KeepAliveWithoutBoxing(new byte[SizeInBytes]); 26 | 27 | [Benchmark] 28 | public void RentAndReturn_Shared() 29 | { 30 | var pool = ArrayPool.Shared; 31 | byte[] array = pool.Rent(SizeInBytes); 32 | pool.Return(array); 33 | } 34 | 35 | [Benchmark] 36 | public void RentAndReturn_Aware() 37 | { 38 | var pool = sizeAwarePool; 39 | byte[] array = pool.Rent(SizeInBytes); 40 | pool.Return(array); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Memory/SmallAllocations.cs: -------------------------------------------------------------------------------- 1 | #if NET46 // .NET Core 1.1 does not support GC.TryStartNoGCRegion, .NET Core 2.0 fails with exception on my box 2 | using System; 3 | using BenchmarkDotNet.Attributes; 4 | using BenchmarkDotNet.Attributes.Exporters; 5 | using System.Runtime; 6 | using BenchmarkDotNet.Configs; 7 | using BenchmarkDotNet.Toolchains.CsProj; 8 | using BenchmarkDotNet.Jobs; 9 | using BenchmarkDotNet.Environments; 10 | using System.Runtime.CompilerServices; 11 | using System.Runtime.InteropServices; 12 | using BenchmarkDotNet.Engines; 13 | 14 | namespace StateOfTheDotNetPerformance 15 | { 16 | [Config(typeof(AllocationsConfig))] 17 | [RPlotExporter] // uncomment to get nice charts! 18 | [CsvMeasurementsExporter] // uncomment to get nice charts! 19 | public class SmallAllocations 20 | { 21 | const long MaxNoGcRegion = (64 * 1024L * 1024L); // http://mattwarren.org/2016/08/16/Preventing-dotNET-Garbage-Collections-with-the-TryStartNoGCRegion-API/ 22 | 23 | [Params(8, 24 | 16, 25 | 32, 26 | 64, 27 | 128, 28 | 256, 29 | 512, 30 | 1024)] 31 | public int SizeInBytes { get; set; } 32 | 33 | [IterationSetup] 34 | public void IterationSetup() 35 | { 36 | // the goal of this benchmark is to measure how fast allocation is, so we tell GC to rest for a while 37 | // to have clean results, without GC side-effects 38 | 39 | try 40 | { 41 | GC.TryStartNoGCRegion(MaxNoGcRegion, disallowFullBlockingGC: true); 42 | } 43 | catch // for some F... reason it fails for the first time, but works for the 2nd... 44 | { 45 | GC.TryStartNoGCRegion(MaxNoGcRegion, disallowFullBlockingGC: true); 46 | } 47 | } 48 | 49 | [IterationCleanup] 50 | public void IterationCleanup() 51 | { 52 | if (GCSettings.LatencyMode == GCLatencyMode.NoGCRegion) 53 | GC.EndNoGCRegion(); 54 | } 55 | 56 | [Benchmark(Description = "new", Baseline = true)] 57 | public void Allocate() => DeadCodeEliminationHelper.KeepAliveWithoutBoxing(new byte[SizeInBytes]); 58 | 59 | [Benchmark(Description = "stackalloc")] 60 | public unsafe void AllocateWithStackalloc() 61 | { 62 | var array = stackalloc byte[SizeInBytes]; 63 | Blackhole(array); 64 | } 65 | 66 | // [Benchmark(Description = "Marshal")] it blows up the whole system! 67 | public void AllocateWithMarshal() 68 | { 69 | var arrayPointer = Marshal.AllocHGlobal(SizeInBytes); 70 | DeadCodeEliminationHelper.KeepAliveWithoutBoxing(arrayPointer); 71 | 72 | // I am NOT freeing the memory on Purpose 73 | // why? because otherwise every other benchmark run will get the same block of memory 74 | // that was returned for the warmup run, and it would show that Marshall is 100x faster than new 75 | // Marshal.FreeHGlobal(arrayPointer); 76 | } 77 | 78 | [MethodImpl(MethodImplOptions.NoInlining)] // no-inlining prevents from dead code elimination 79 | private unsafe void Blackhole(byte* input) { } 80 | } 81 | 82 | public class AllocationsConfig : ManualConfig 83 | { 84 | public AllocationsConfig() 85 | { 86 | var gcSettings = new GcMode() 87 | { 88 | Force = true, // tell BenchmarkDotNet to force GC collections after every iteration 89 | Server = true // we want to have the biggest Largest No GC Region possible 90 | }; 91 | Jit jit = Jit.RyuJit; // we want to run for x64 only, again to have the biggest Largest No GC Region possible 92 | 93 | Add(Job.Default 94 | .With(CsProjNet46Toolchain.Instance) 95 | .With(gcSettings.UnfreezeCopy()) 96 | .With(jit) 97 | .WithId(".NET 4.6")); 98 | 99 | // .NET Core 1.1 does not support GC.TryStartNoGCRegion method so we don't try it 100 | // .NET Core 2.0 fails when trying to call GC.TryStartNoGCRegion 101 | } 102 | } 103 | } 104 | #endif -------------------------------------------------------------------------------- /MultipleRuntimesConfig.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Configs; 2 | using BenchmarkDotNet.Jobs; 3 | using BenchmarkDotNet.Toolchains.CsProj; 4 | 5 | namespace StateOfTheDotNetPerformance 6 | { 7 | public class MultipleRuntimesConfig : ManualConfig 8 | { 9 | public MultipleRuntimesConfig() 10 | { 11 | // watch and learn how to use full power of BenchmarkDotNet! 12 | 13 | Add(Job.Default 14 | .With(CsProjNet46Toolchain.Instance) // Span NOT supported by Runtime 15 | .WithId(".NET 4.6")); 16 | 17 | Add(Job.Default 18 | .With(CsProjCoreToolchain.NetCoreApp11) // Span NOT supported by Runtime 19 | .WithId(".NET Core 1.1")); 20 | 21 | /// !!! warning !!! NetCoreApp20 toolchain simply sets TargetFramework = netcoreapp2.0 in generated .csproj 22 | /// // so you need Visual Studio 2017 Preview 15.3 to be able to run it! 23 | Add(Job.Default 24 | .With(CsProjCoreToolchain.NetCoreApp20) // Span SUPPORTED by Runtime 25 | .WithId(".NET Core 2.0")); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BenchmarkDotNet.Running; 3 | using System.Reflection; 4 | 5 | namespace StateOfTheDotNetPerformance 6 | { 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | #if !NET46 12 | Console.WriteLine("If you want to see the hardware counters you need to run as .NET 4.6"); 13 | Console.WriteLine("You can do this by running: dotnet run -f net46 -c Release"); 14 | Console.WriteLine(); 15 | #endif 16 | 17 | BenchmarkSwitcher 18 | .FromAssembly(typeof(Program).GetTypeInfo().Assembly) 19 | .Run(args); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # State of the .NET Performance 2 | 3 | Code for my talk "State of the .NET Performance" 4 | 5 | # Caution! 6 | 7 | This repository contains examples for both classic .NET and the new .NET Core. You need latest Visual Studio to be able to run it. 8 | 9 | # Building 10 | 11 | The following elements are required for building the source code: 12 | 13 | * [Visual Studio 2017 Preview version **15.3**+](https://www.visualstudio.com/vs/preview/) 14 | * [.NET Core SDK](https://go.microsoft.com/fwlink/?linkid=848740) 15 | * Internet connection and disk space to download all the required packages 16 | 17 | # Running from console 18 | 19 | * .NET Core 2.0: `dotnet run -c Release -f netcoreapp2.0` 20 | * .NET Core 1.1: `dotnet run -c Release -f netcoreapp1.1` 21 | * .NET 4.6: `dotnet run -c Release -f net46` 22 | -------------------------------------------------------------------------------- /References/InitializingBigStructs.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Attributes.Exporters; 3 | using BenchmarkDotNet.Attributes.Jobs; 4 | 5 | namespace StateOfTheDotNetPerformance.References 6 | { 7 | [LegacyJitX86Job, LegacyJitX64Job, RyuJitX64Job] 8 | //[RPlotExporter] // uncomment to get nice charts! 9 | //[CsvMeasurementsExporter] // uncomment to get nice charts! 10 | public class InitializingBigStructs 11 | { 12 | struct BigStruct 13 | { 14 | public int Int1, Int2, Int3, Int4, Int5; 15 | } 16 | 17 | private BigStruct[] array; 18 | 19 | [GlobalSetup] 20 | public void Setup() 21 | { 22 | array = new BigStruct[1000]; 23 | } 24 | 25 | [Benchmark] 26 | public void ByValue() 27 | { 28 | for (int i = 0; i < array.Length; i++) 29 | { 30 | BigStruct value = array[i]; 31 | 32 | value.Int1 = 1; 33 | value.Int2 = 2; 34 | value.Int3 = 3; 35 | value.Int4 = 4; 36 | value.Int5 = 5; 37 | 38 | array[i] = value; 39 | } 40 | } 41 | 42 | [Benchmark(Baseline = true)] 43 | public void ByReference() 44 | { 45 | for (int i = 0; i < array.Length; i++) 46 | { 47 | ref BigStruct reference = ref array[i]; 48 | 49 | reference.Int1 = 1; 50 | reference.Int2 = 2; 51 | reference.Int3 = 3; 52 | reference.Int4 = 4; 53 | reference.Int5 = 5; 54 | } 55 | } 56 | 57 | [Benchmark] 58 | public void ByReferenceOldWay() 59 | { 60 | for (int i = 0; i < array.Length; i++) 61 | { 62 | Init(ref array[i]); 63 | } 64 | } 65 | 66 | // try it with: [MethodImpl(MethodImplOptions.NoInlining)] 67 | private void Init(ref BigStruct reference) 68 | { 69 | reference.Int1 = 1; 70 | reference.Int2 = 2; 71 | reference.Int3 = 3; 72 | reference.Int4 = 4; 73 | reference.Int5 = 5; 74 | } 75 | 76 | [Benchmark] 77 | public void ByReferenceUnsafeImplicit() 78 | { 79 | unsafe 80 | { 81 | fixed (BigStruct* pinned = array) 82 | { 83 | for (int i = 0; i < array.Length; i++) 84 | { 85 | pinned[i].Int1 = 1; 86 | pinned[i].Int2 = 2; 87 | pinned[i].Int3 = 3; 88 | pinned[i].Int4 = 4; 89 | pinned[i].Int5 = 5; 90 | } 91 | } 92 | } 93 | } 94 | 95 | [Benchmark] 96 | public void ByReferenceUnsafeExplicit() 97 | { 98 | unsafe 99 | { 100 | fixed (BigStruct* pinned = array) 101 | { 102 | for (int i = 0; i < array.Length; i++) 103 | { 104 | (*(&pinned[i])).Int1 = 1; 105 | (*(&pinned[i])).Int2 = 2; 106 | (*(&pinned[i])).Int3 = 3; 107 | (*(&pinned[i])).Int4 = 4; 108 | (*(&pinned[i])).Int5 = 5; 109 | } 110 | } 111 | } 112 | } 113 | 114 | [Benchmark] 115 | public void ByReferenceUnsafeExplicitExtraMethod() 116 | { 117 | unsafe 118 | { 119 | fixed (BigStruct* pinned = array) 120 | { 121 | for (int i = 0; i < array.Length; i++) 122 | { 123 | Init(&pinned[i]); 124 | } 125 | } 126 | } 127 | } 128 | 129 | // try it with: [MethodImpl(MethodImplOptions.NoInlining)] 130 | private unsafe void Init(BigStruct* pointer) 131 | { 132 | (*pointer).Int1 = 1; 133 | (*pointer).Int2 = 2; 134 | (*pointer).Int3 = 3; 135 | (*pointer).Int4 = 4; 136 | (*pointer).Int5 = 5; 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /Span/SpanForDifferentRuntimes.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using System; 3 | using BenchmarkDotNet.Configs; 4 | using BenchmarkDotNet.Jobs; 5 | using BenchmarkDotNet.Toolchains.CsProj; 6 | using BenchmarkDotNet.Order; 7 | 8 | namespace StateOfTheDotNetPerformance.Span 9 | { 10 | [Config(typeof(MultipleRuntimesConfig))] 11 | public class SpanForDifferentRuntimes 12 | { 13 | [Benchmark] 14 | public unsafe int StackallocAndInterate() 15 | { 16 | // this benchmark is going to be executed with Span-aware Runtime: .NET Core 2.0 17 | // so we can't cheat anymore and store Span in a field 18 | // that's why I stackallocate the memory for the benchmark (it's smallest overhead + NO GC) 19 | 20 | int* pointerToStack = stackalloc int[256]; 21 | Span stackMemory = new Span(pointerToStack, 256); 22 | 23 | int sum = 0; 24 | for (int i = 0; i < stackMemory.Length; i++) 25 | { 26 | sum += stackMemory[i]; 27 | } 28 | 29 | return sum; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Span/SpanIndexer.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using System; 3 | using System.Linq; 4 | 5 | namespace StateOfTheDotNetPerformance.Span 6 | { 7 | [Config(typeof(MultipleRuntimesConfig))] 8 | public class SpanIndexer 9 | { 10 | protected const int Loops = 100; 11 | 12 | protected const int Count = 1000; 13 | 14 | protected byte[] arrayField; 15 | 16 | [GlobalSetup] 17 | public void Setup() 18 | { 19 | arrayField = Enumerable.Repeat(1, Count).Select((val, index) => (byte)index).ToArray(); 20 | } 21 | 22 | [Benchmark(OperationsPerInvoke = Loops * Count)] 23 | public byte SpanIndexer_Get() 24 | { 25 | Span local = arrayField; // implicit cast to Span, we can't have Span as a field! 26 | byte result = 0; 27 | for (int _ = 0; _ < Loops; _++) 28 | { 29 | for (int j = 0; j < local.Length; j++) 30 | { 31 | result = local[j]; 32 | } 33 | } 34 | return result; 35 | } 36 | 37 | [Benchmark(OperationsPerInvoke = Loops * Count)] 38 | public void SpanIndexer_Set() 39 | { 40 | Span local = arrayField; // implicit cast to Span, we can't have Span as a field! 41 | for (int _ = 0; _ < Loops; _++) 42 | { 43 | for (int j = 0; j < local.Length; j++) 44 | { 45 | local[j] = byte.MaxValue; 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Span/SpanVsArray_Indexer.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | 3 | namespace StateOfTheDotNetPerformance.Span 4 | { 5 | public class SpanVsArray_Indexer : SpanIndexer 6 | { 7 | [Benchmark(OperationsPerInvoke = Loops * Count)] 8 | public byte ArrayIndexer_Get() 9 | { 10 | var local = arrayField; 11 | byte result = 0; 12 | for (int _ = 0; _ < Loops; _++) 13 | { 14 | for (int j = 0; j < local.Length; j++) 15 | { 16 | result = local[j]; 17 | } 18 | } 19 | return result; 20 | } 21 | 22 | [Benchmark(OperationsPerInvoke = Loops * Count)] 23 | public void ArrayIndexer_Set() 24 | { 25 | var local = arrayField; 26 | for (int _ = 0; _ < Loops; _++) 27 | { 28 | for (int j = 0; j < local.Length; j++) 29 | { 30 | local[j] = byte.MaxValue; 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Span/SubstringVsSubslice.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using System; 3 | 4 | namespace StateOfTheDotNetPerformance.Span 5 | { 6 | // don't run this benchmark for .NET Core 2.0 it will fail due to safety limitations 7 | // (BenchmarkDotNet is using Func and Span can't be generic argument) 8 | [MemoryDiagnoser] 9 | [Config(typeof(DontForceGcCollectionsConfig))] 10 | public class SubstringVsSubslice 11 | { 12 | public const string Text = ".NET Core: Performance Storm"; 13 | 14 | [Benchmark] 15 | public string Substring() => Text.Substring(0, 9); 16 | 17 | [Benchmark(Baseline = true)] 18 | public ReadOnlySpan Slice() => Text.AsSpan().Slice(0, 9); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /StateOfTheDotNetPerformance.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.0;netcoreapp1.1;net46 6 | True 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /StateOfTheDotNetPerformance.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26510.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StateOfTheDotNetPerformance", "StateOfTheDotNetPerformance.csproj", "{83F5A55F-1D2B-49F1-8E88-6E3A33A55000}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {83F5A55F-1D2B-49F1-8E88-6E3A33A55000}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {83F5A55F-1D2B-49F1-8E88-6E3A33A55000}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {83F5A55F-1D2B-49F1-8E88-6E3A33A55000}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {83F5A55F-1D2B-49F1-8E88-6E3A33A55000}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /ValueTaskOverheadBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Threading.Tasks; 3 | using BenchmarkDotNet.Attributes; 4 | 5 | namespace StateOfTheDotNetPerformance 6 | { 7 | [MemoryDiagnoser] 8 | public class ValueTaskOverheadBenchmarks 9 | { 10 | [Params(100, 1000)] 11 | public int Repeats { get; set; } 12 | 13 | [Benchmark] 14 | public Task ConsumeTask() => ConsumeTask(Repeats); 15 | 16 | [Benchmark] 17 | public ValueTask ConsumeValueTaskWrong() => ConsumeWrong(Repeats); 18 | 19 | [Benchmark(Baseline = true)] 20 | public ValueTask ConsumeValueTaskProperly() => ConsumeProperly(Repeats); 21 | 22 | [Benchmark] 23 | public ValueTask ConsumeValueTaskCrazy() => ConsumeCrazy(Repeats); 24 | 25 | async Task ConsumeTask(int repeats) 26 | { 27 | int total = 0; 28 | while (repeats-- > 0) 29 | total += await SampleUsageAsync(); 30 | 31 | return total; 32 | } 33 | 34 | Task SampleUsageAsync() => Task.FromResult(1); 35 | 36 | async ValueTask ConsumeWrong(int repeats) 37 | { 38 | int total = 0; 39 | while (repeats-- > 0) 40 | total += await SampleUsage(); 41 | 42 | return total; 43 | } 44 | 45 | async ValueTask ConsumeProperly(int repeats) 46 | { 47 | int total = 0; 48 | while (repeats-- > 0) 49 | { 50 | ValueTask valueTask = SampleUsage(); // INLINEABLE 51 | 52 | total += valueTask.IsCompleted 53 | ? valueTask.Result 54 | : await valueTask.AsTask(); 55 | } 56 | 57 | return total; 58 | } 59 | 60 | ValueTask ConsumeCrazy(int repeats) 61 | { 62 | int total = 0; 63 | while (repeats-- > 0) 64 | { 65 | ValueTask valueTask = SampleUsage(); // INLINEABLE 66 | 67 | if (valueTask.IsCompleted) 68 | total += valueTask.Result; 69 | else 70 | return ContinueAsync(valueTask, repeats, total); 71 | } 72 | 73 | return new ValueTask(total); 74 | } 75 | 76 | async ValueTask ContinueAsync(ValueTask valueTask, int repeats, int total) 77 | { 78 | total += await valueTask; 79 | 80 | while (repeats-- > 0) 81 | { 82 | valueTask = SampleUsage(); 83 | 84 | if (valueTask.IsCompleted) 85 | total += valueTask.Result; 86 | else 87 | total += await valueTask; 88 | } 89 | 90 | return total; 91 | } 92 | 93 | [MethodImpl(MethodImplOptions.AggressiveInlining)] // super important! 94 | ValueTask SampleUsage() 95 | => IsFastSynchronousExecutionPossible() 96 | ? new ValueTask( 97 | 98 | result: ExecuteSynchronous()) // INLINEABLE!!! 99 | : new ValueTask( 100 | task: ExecuteAsync()); 101 | 102 | [MethodImpl(MethodImplOptions.NoInlining)] 103 | bool IsFastSynchronousExecutionPossible() => true; 104 | 105 | int ExecuteSynchronous() => 1; 106 | Task ExecuteAsync() => Task.FromResult(1); 107 | } 108 | } -------------------------------------------------------------------------------- /ValueTypesVsReferenceTypes/DataLocality.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using BenchmarkDotNet.Attributes; 4 | using BenchmarkDotNet.Attributes.Jobs; 5 | 6 | namespace StateOfTheDotNetPerformance.ValueTypesVsReferenceTypes 7 | { 8 | // the host process must be .NET 4.6 to get the hardware counters, run it from console: "dotnet run -f net46 -c Release" 9 | [BenchmarkCategory(Categories.ValueTypesVsReferenceTypes)] 10 | [HardwareCounters(BenchmarkDotNet.Diagnosers.HardwareCounter.CacheMisses)] 11 | [CoreJob, ClrJob] 12 | public class DataLocality 13 | { 14 | [Params( 15 | 1000000, 16 | 10000000, 17 | 100000000)] 18 | public int Count { get; set; } // for smaller arrays we don't get enough of Cache Miss events 19 | 20 | Tuple[] arrayOfRef; 21 | ValueTuple[] arrayOfVal; 22 | 23 | [GlobalSetup] 24 | public void Setup() 25 | { 26 | arrayOfRef = Enumerable.Repeat(1, Count).Select((val, index) => Tuple.Create(val, index)).ToArray(); 27 | arrayOfVal = Enumerable.Repeat(1, Count).Select((val, index) => new ValueTuple(val, index)).ToArray(); 28 | } 29 | 30 | [Benchmark(Baseline = true)] 31 | public int IterateValueTypes() 32 | { 33 | int item1Sum = 0, item2Sum = 0; 34 | 35 | var array = arrayOfVal; 36 | for (int i = 0; i < array.Length; i++) 37 | { 38 | item1Sum += array[i].Item1; 39 | item2Sum += array[i].Item2; 40 | } 41 | 42 | return item1Sum + item2Sum; 43 | } 44 | 45 | [Benchmark] 46 | public int IterateReferenceTypes() 47 | { 48 | int item1Sum = 0, item2Sum = 0; 49 | 50 | var array = arrayOfRef; 51 | for (int i = 0; i < array.Length; i++) 52 | { 53 | item1Sum += array[i].Item1; 54 | item2Sum += array[i].Item2; 55 | } 56 | 57 | return item1Sum + item2Sum; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ValueTypesVsReferenceTypes/NoGC.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using System; 3 | using BenchmarkDotNet.Attributes.Jobs; 4 | 5 | namespace StateOfTheDotNetPerformance 6 | { 7 | [BenchmarkCategory(Categories.ValueTypesVsReferenceTypes)] 8 | [RyuJitX64Job, LegacyJitX86Job] 9 | [MemoryDiagnoser] 10 | public class NoGC 11 | { 12 | [Benchmark(Baseline = true)] 13 | public ValueTuple CreateValueTuple() => ValueTuple.Create(0, 0); 14 | 15 | [Benchmark] 16 | public Tuple CreateTuple() => Tuple.Create(0, 0); 17 | } 18 | } 19 | --------------------------------------------------------------------------------