├── .gitignore ├── Course.FullNet ├── App.config ├── Course.FullNet.csproj ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── Course.sln ├── Course ├── Bits.cs ├── BranchPrediction.cs ├── ConstantPropagation.cs ├── Course.csproj ├── CustomDictionary.cs ├── Dictionary.cs ├── Inlining.cs ├── ObjectPool.cs ├── Program.cs ├── RefAllocation.cs ├── StructDeadCode.cs ├── SwitchIf.cs └── VirtualCall.cs ├── Workshop.sln └── Workshop ├── App.config ├── Dictionary.Base.cs ├── Dictionary.cs ├── LZ4.Compression.cs ├── LZ4.Decompression.cs ├── LZ4.Support.cs ├── LZ4.cs ├── PageLocator.Benchmark.cs ├── PageLocator.Stubs.cs ├── PageLocator.cs ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── Workshop.csproj └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | *.user 2 | .vs/ 3 | Course.FullNet/bin/ 4 | Course.FullNet/obj/ 5 | Course/BenchmarkDotNet.Artifacts/ 6 | Course/bin/ 7 | Course/obj/ 8 | packages/ 9 | Workshop/Workshop/bin/ 10 | Workshop/Workshop/obj/ 11 | Workshop/bin/ 12 | Workshop/obj/ 13 | -------------------------------------------------------------------------------- /Course.FullNet/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Course.FullNet/Course.FullNet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FF660BBC-7D2D-45C5-9306-607CA97E4D1D} 8 | Exe 9 | Course.FullNet 10 | Course.FullNet 11 | v4.7 12 | 512 13 | true 14 | 15 | 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE;FULL 34 | prompt 35 | 4 36 | false 37 | true 38 | 39 | 40 | 41 | ..\packages\BenchmarkDotNet.0.10.9\lib\net46\BenchmarkDotNet.dll 42 | 43 | 44 | ..\packages\BenchmarkDotNet.Core.0.10.9\lib\net46\BenchmarkDotNet.Core.dll 45 | 46 | 47 | ..\packages\BenchmarkDotNet.Diagnostics.Windows.0.10.9\lib\net46\BenchmarkDotNet.Diagnostics.Windows.dll 48 | 49 | 50 | ..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.9\lib\net46\BenchmarkDotNet.Toolchains.Roslyn.dll 51 | 52 | 53 | ..\packages\Microsoft.CodeAnalysis.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll 54 | 55 | 56 | ..\packages\Microsoft.CodeAnalysis.CSharp.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll 57 | 58 | 59 | ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll 60 | 61 | 62 | ..\packages\Microsoft.DotNet.InternalAbstractions.1.0.0\lib\net451\Microsoft.DotNet.InternalAbstractions.dll 63 | 64 | 65 | ..\packages\Microsoft.DotNet.PlatformAbstractions.1.1.1\lib\net451\Microsoft.DotNet.PlatformAbstractions.dll 66 | 67 | 68 | ..\packages\Microsoft.Win32.Registry.4.3.0\lib\net46\Microsoft.Win32.Registry.dll 69 | 70 | 71 | 72 | ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll 73 | 74 | 75 | ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll 76 | True 77 | 78 | 79 | 80 | ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll 81 | 82 | 83 | 84 | ..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll 85 | 86 | 87 | ..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll 88 | 89 | 90 | ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll 91 | 92 | 93 | ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll 94 | 95 | 96 | ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll 97 | 98 | 99 | ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll 100 | 101 | 102 | 103 | ..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll 104 | 105 | 106 | ..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll 107 | 108 | 109 | ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll 110 | 111 | 112 | ..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll 113 | 114 | 115 | ..\packages\System.Runtime.InteropServices.4.3.0\lib\net462\System.Runtime.InteropServices.dll 116 | 117 | 118 | ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll 119 | 120 | 121 | ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll 122 | 123 | 124 | ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll 125 | 126 | 127 | ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll 128 | 129 | 130 | ..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll 131 | 132 | 133 | ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll 134 | 135 | 136 | ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll 137 | 138 | 139 | ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll 149 | 150 | 151 | ..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll 152 | 153 | 154 | ..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll 155 | 156 | 157 | ..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll 158 | 159 | 160 | 161 | 162 | Bits.cs 163 | 164 | 165 | BranchPrediction.cs 166 | 167 | 168 | ConstantPropagation.cs 169 | 170 | 171 | CustomDictionary.cs 172 | 173 | 174 | Dictionary.cs 175 | 176 | 177 | Inlining.cs 178 | 179 | 180 | ObjectPool.cs 181 | 182 | 183 | Program.cs 184 | 185 | 186 | RefAllocation.cs 187 | 188 | 189 | StructDeadCode.cs 190 | 191 | 192 | SwitchIf.cs 193 | 194 | 195 | VirtualCall.cs 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /Course.FullNet/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Course.FullNet")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Course.FullNet")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("ff660bbc-7d2d-45c5-9306-607ca97e4d1d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Course.FullNet/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Course.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.10 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Course", "Course\Course.csproj", "{38739819-F549-4B22-9145-E0E3BBCF5BA6}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Course.FullNet", "Course.FullNet\Course.FullNet.csproj", "{FF660BBC-7D2D-45C5-9306-607CA97E4D1D}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {38739819-F549-4B22-9145-E0E3BBCF5BA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {38739819-F549-4B22-9145-E0E3BBCF5BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {38739819-F549-4B22-9145-E0E3BBCF5BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {38739819-F549-4B22-9145-E0E3BBCF5BA6}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {FF660BBC-7D2D-45C5-9306-607CA97E4D1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {FF660BBC-7D2D-45C5-9306-607CA97E4D1D}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {FF660BBC-7D2D-45C5-9306-607CA97E4D1D}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {FF660BBC-7D2D-45C5-9306-607CA97E4D1D}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(CodealikeProperties) = postSolution 29 | SolutionGuid = fd9763bc-dc03-40d4-aef9-a31f9c0d3e2e 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Course/Bits.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Text; 5 | 6 | namespace Course 7 | { 8 | public static class Bits 9 | { 10 | private static readonly int[] nextPowerOf2Table = 11 | { 12 | 0, 1, 2, 4, 4, 8, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 13 | 16, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 14 | 32, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 15 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 16 | 64, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 17 | 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 18 | 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 19 | 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 20 | 128, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 21 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 22 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 23 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 24 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 25 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 26 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 27 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256 28 | }; 29 | 30 | 31 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 32 | public static int NextPowerOf2(int v) 33 | { 34 | if (v < nextPowerOf2Table.Length) 35 | { 36 | return nextPowerOf2Table[v]; 37 | } 38 | else 39 | { 40 | return NextPowerOf2Internal(v); 41 | } 42 | } 43 | 44 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 45 | private static int NextPowerOf2Internal(int v) 46 | { 47 | v--; 48 | v |= v >> 1; 49 | v |= v >> 2; 50 | v |= v >> 4; 51 | v |= v >> 8; 52 | v |= v >> 16; 53 | v++; 54 | 55 | return v; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Course/BranchPrediction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using BenchmarkDotNet.Attributes; 5 | using BenchmarkDotNet.Diagnosers; 6 | 7 | namespace Course 8 | { 9 | // See http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array/11227902 10 | // From BenchmarkDotNet samples. 11 | [HardwareCounters(HardwareCounter.BranchMispredictions, HardwareCounter.BranchInstructions)] 12 | public class Cpu_BranchPredictor 13 | { 14 | private const int N = 32767; 15 | private readonly int[] sorted, unsorted; 16 | 17 | public Cpu_BranchPredictor() 18 | { 19 | var random = new Random(0); 20 | unsorted = new int[N]; 21 | sorted = new int[N]; 22 | for (int i = 0; i < N; i++) 23 | sorted[i] = unsorted[i] = random.Next(256); 24 | Array.Sort(sorted); 25 | } 26 | 27 | [Benchmark] 28 | public int SortedBranch() 29 | { 30 | return Branch(sorted); 31 | } 32 | 33 | [Benchmark] 34 | public int UnsortedBranch() 35 | { 36 | return Branch(unsorted); 37 | } 38 | 39 | [Benchmark] 40 | public int SortedBranchless() 41 | { 42 | return Branchless(sorted); 43 | } 44 | 45 | [Benchmark] 46 | public int UnsortedBranchless() 47 | { 48 | return Branchless(unsorted); 49 | } 50 | 51 | private static int Branch(int[] data) 52 | { 53 | int sum = 0; 54 | for (int i = 0; i < N; i++) 55 | if (data[i] >= 128) 56 | sum += data[i]; 57 | return sum; 58 | } 59 | 60 | private static int Branchless(int[] data) 61 | { 62 | int sum = 0; 63 | for (int i = 0; i < N; i++) 64 | { 65 | int t = (data[i] - 128) >> 31; 66 | sum += ~t & data[i]; 67 | } 68 | return sum; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Course/ConstantPropagation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Text; 5 | using BenchmarkDotNet.Attributes; 6 | using BenchmarkDotNet.Diagnosers; 7 | 8 | #if FULL 9 | using BenchmarkDotNet.Diagnostics.Windows.Configs; 10 | #endif 11 | 12 | namespace Course 13 | { 14 | #if FULL 15 | [InliningDiagnoser] 16 | #endif 17 | [HardwareCounters(HardwareCounter.InstructionRetired)] 18 | public class ConstantPropagation 19 | { 20 | 21 | const ulong m1 = 0x5555555555555555; 22 | const ulong m2 = 0x3333333333333333; 23 | const ulong m4 = 0x0f0f0f0f0f0f0f0f; 24 | const ulong h01 = 0x0101010101010101; 25 | 26 | //This uses fewer arithmetic operations than any other known 27 | //implementation on machines with fast multiplication. 28 | //It uses 12 arithmetic operations, one of which is a multiply. 29 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 30 | private static int PopCountInlined(ulong x) 31 | { 32 | x -= (x >> 1) & m1; //put count of each 2 bits into those 2 bits 33 | x = (x & m2) + ((x >> 2) & m2); //put count of each 4 bits into those 4 bits 34 | x = (x + (x >> 4)) & m4; //put count of each 8 bits into those 8 bits 35 | return (int)((x * h01) >> 56); //returns left 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ... 36 | } 37 | 38 | //This uses fewer arithmetic operations than any other known 39 | //implementation on machines with fast multiplication. 40 | //It uses 12 arithmetic operations, one of which is a multiply. 41 | [MethodImpl(MethodImplOptions.NoInlining)] 42 | private static int PopCount(ulong x) 43 | { 44 | x -= (x >> 1) & m1; //put count of each 2 bits into those 2 bits 45 | x = (x & m2) + ((x >> 2) & m2); //put count of each 4 bits into those 4 bits 46 | x = (x + (x >> 4)) & m4; //put count of each 8 bits into those 8 bits 47 | return (int)((x * h01) >> 56); //returns left 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ... 48 | } 49 | 50 | 51 | [Benchmark] 52 | public int SumInlined() 53 | { 54 | ulong x = 0x0f0f0f0f0f0f0f0f; 55 | int result = 0; 56 | for (int i = 0; i < 1000; i++) 57 | result += PopCountInlined(x); 58 | return result; 59 | } 60 | 61 | [Benchmark] 62 | public int SumNonInlined() 63 | { 64 | ulong x = 0x0f0f0f0f0f0f0f0f; 65 | int result = 0; 66 | for (int i = 0; i < 1000; i++) 67 | result += PopCount(x); 68 | return result; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Course/Course.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | True 10 | 11 | 12 | 13 | True 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Course/CustomDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.Contracts; 5 | using System.Runtime.CompilerServices; 6 | using BenchmarkDotNet.Attributes; 7 | using BenchmarkDotNet.Diagnosers; 8 | 9 | namespace Course 10 | { 11 | [HardwareCounters(HardwareCounter.InstructionRetired)] 12 | public class DictionaryBenchmark 13 | { 14 | private readonly Dictionary _dictionary = new Dictionary(default(NumericEqualityComparer)); 15 | private readonly FastDictionary _specificFastDictionary = new FastDictionary(default(NumericEqualityComparer)); 16 | private readonly FastDictionary _comparerFastDictionary = new FastDictionary(default(NumericEqualityComparer)); 17 | private readonly long[] knownValues = new long[1000]; 18 | 19 | [GlobalSetup] 20 | public void Setup() 21 | { 22 | Random rnd = new Random(1337); 23 | 24 | for (int i = 0; i < knownValues.Length; i++) 25 | { 26 | long value = rnd.Next(3 * knownValues.Length); 27 | 28 | knownValues[i] = value; 29 | _dictionary[value] = value; 30 | _specificFastDictionary[value] = value; 31 | _comparerFastDictionary[value] = value; 32 | } 33 | 34 | } 35 | 36 | [Benchmark] 37 | public long FastDictionaryIComparer() 38 | { 39 | long result = 0; 40 | var dictionary = _comparerFastDictionary; 41 | long[] kv = knownValues; 42 | 43 | for (int i = 0; i < kv.Length; i++) 44 | { 45 | // Avoid the JIT to remove all the code in cases where it can figure out there are no side effects. 46 | result += dictionary[kv[i]]; 47 | } 48 | 49 | return result; 50 | } 51 | 52 | [Benchmark] 53 | public long FastDictionarySpecific() 54 | { 55 | long result = 0; 56 | var dictionary = _specificFastDictionary; 57 | long[] kv = knownValues; 58 | 59 | for (int i = 0; i < kv.Length; i++) 60 | { 61 | // Avoid the JIT to remove all the code in cases where it can figure out there are no side effects. 62 | result += dictionary[kv[i]]; 63 | } 64 | 65 | return result; 66 | } 67 | 68 | [Benchmark] 69 | public long Dictionary() 70 | { 71 | long result = 0; 72 | var dictionary = _dictionary; 73 | long[] kv = knownValues; 74 | 75 | for (int i = 0; i < kv.Length; i++) 76 | { 77 | // Avoid the JIT to remove all the code in cases where it can figure out there are no side effects. 78 | result += dictionary[kv[i]]; 79 | } 80 | 81 | return result; 82 | } 83 | } 84 | 85 | internal static class DictionaryHelper 86 | { 87 | /// 88 | /// Minimum size we're willing to let hashtables be. 89 | /// Must be a power of two, and at least 4. 90 | /// Note, however, that for a given hashtable, the initial size is a function of the first constructor arg, and may be > kMinBuckets. 91 | /// 92 | internal const int KMinBuckets = 4; 93 | 94 | /// 95 | /// By default, if you don't specify a hashtable size at construction-time, we use this size. Must be a power of two, and at least kMinBuckets. 96 | /// 97 | internal const int KInitialCapacity = 32; 98 | } 99 | 100 | // PERF: CoreCLR 2.0 JIT will pickup this is a sealed class and will try to devirtualize all method calls to comparers. 101 | public sealed class FastDictionary : FastDictionaryBase> 102 | { 103 | public FastDictionary(int initialBucketCount, IEnumerable> src, IEqualityComparer comparer) : base(initialBucketCount, src, comparer ?? EqualityComparer.Default) 104 | { } 105 | 106 | public FastDictionary(FastDictionary src, IEqualityComparer comparer) : base(src, comparer ?? EqualityComparer.Default) 107 | { } 108 | 109 | public FastDictionary(FastDictionary src) : base(src) 110 | { } 111 | 112 | public FastDictionary(int initialBucketCount, FastDictionary src, IEqualityComparer comparer) : base(initialBucketCount, src, comparer ?? EqualityComparer.Default) 113 | { } 114 | 115 | public FastDictionary(IEqualityComparer comparer) : base(comparer ?? EqualityComparer.Default) 116 | { } 117 | 118 | public FastDictionary(int initialBucketCount, IEqualityComparer comparer) : base(initialBucketCount, comparer ?? EqualityComparer.Default) 119 | { } 120 | 121 | public FastDictionary(int initialBucketCount = DictionaryHelper.KInitialCapacity) : base(initialBucketCount, EqualityComparer.Default) 122 | { } 123 | } 124 | 125 | // PERF: CoreCLR 2.0 JIT will pickup this is a sealed class and will try to devirtualize all method calls to comparers. 126 | public sealed class FastDictionary : FastDictionaryBase 127 | where TComparer : IEqualityComparer 128 | { 129 | public FastDictionary(int initialBucketCount, IEnumerable> src, TComparer comparer) : base(initialBucketCount, src, comparer) 130 | { } 131 | 132 | public FastDictionary(FastDictionary src, TComparer comparer) : base(src, comparer) 133 | { } 134 | 135 | public FastDictionary(FastDictionary src) : base(src) 136 | { } 137 | 138 | public FastDictionary(int initialBucketCount, FastDictionary src, TComparer comparer) : base(initialBucketCount, src, comparer) 139 | { } 140 | 141 | public FastDictionary(TComparer comparer) : base(comparer) 142 | { } 143 | 144 | public FastDictionary(int initialBucketCount, TComparer comparer) : base(initialBucketCount, comparer) 145 | { } 146 | } 147 | 148 | // PERF: This base class is introduced in order to allow generic specialization. 149 | public abstract class FastDictionaryBase : IEnumerable> 150 | where TComparer : IEqualityComparer 151 | { 152 | const int InvalidNodePosition = -1; 153 | 154 | public const uint KUnusedHash = 0xFFFFFFFF; 155 | public const uint KDeletedHash = 0xFFFFFFFE; 156 | 157 | // TLoadFactor4 - controls hash map load. 4 means 100% load, ie. hashmap will grow 158 | // when number of items == capacity. Default value of 6 means it grows when 159 | // number of items == capacity * 3/2 (6/4). Higher load == tighter maps, but bigger 160 | // risk of collisions. 161 | private const int KLoadFactor = 6; 162 | 163 | private struct Entry 164 | { 165 | public uint Hash; 166 | public TKey Key; 167 | public TValue Value; 168 | 169 | public Entry(uint hash, TKey key, TValue value) 170 | { 171 | this.Hash = hash; 172 | this.Key = key; 173 | this.Value = value; 174 | } 175 | } 176 | 177 | private Entry[] _entries; 178 | 179 | private int[] _usedEntries; 180 | private int _usedEntriesIndex; 181 | 182 | private readonly int _initialCapacity; // This is the initial capacity of the dictionary, we will never shrink beyond this point. 183 | private int _capacity; 184 | private int _capacityMask; 185 | 186 | private int _size; // This is the real counter of how many items are in the hash-table (regardless of buckets) 187 | private int _numberOfUsed; // How many used buckets. 188 | private int _numberOfDeleted; // how many occupied buckets are marked deleted 189 | private int _nextGrowthThreshold; 190 | 191 | 192 | // PERF: Comparer is readonly to allow devirtualization on sealed classes to kick in (JIT 2.0). 193 | private readonly TComparer _comparer; 194 | public TComparer Comparer => _comparer; 195 | 196 | public int Capacity => _capacity; 197 | 198 | public int Count => _size; 199 | 200 | public bool IsEmpty => Count == 0; 201 | 202 | protected FastDictionaryBase(int initialBucketCount, IEnumerable> src, TComparer comparer) 203 | : this(initialBucketCount, comparer) 204 | { 205 | Contract.Requires(src != null); 206 | Contract.Ensures(_capacity >= initialBucketCount); 207 | 208 | foreach (var item in src) 209 | this[item.Key] = item.Value; 210 | } 211 | 212 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 213 | protected FastDictionaryBase(FastDictionaryBase src, TComparer comparer) 214 | : this(src._capacity, src, comparer) 215 | { } 216 | 217 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 218 | protected FastDictionaryBase(FastDictionaryBase src) 219 | : this(src._capacity, src, src._comparer) 220 | { } 221 | 222 | protected FastDictionaryBase(int initialBucketCount, FastDictionaryBase src, TComparer comparer) 223 | { 224 | Contract.Requires(src != null); 225 | Contract.Requires(comparer != null); 226 | Contract.Ensures(_capacity >= initialBucketCount); 227 | Contract.Ensures(_capacity >= src._capacity); 228 | 229 | this._comparer = comparer; 230 | 231 | this._initialCapacity = Bits.NextPowerOf2(initialBucketCount); 232 | this._capacity = Math.Max(src._capacity, initialBucketCount); 233 | this._capacityMask = this._capacity - 1; 234 | this._size = src._size; 235 | this._numberOfUsed = src._numberOfUsed; 236 | this._numberOfDeleted = src._numberOfDeleted; 237 | this._nextGrowthThreshold = src._nextGrowthThreshold; 238 | 239 | if (comparer.Equals(src._comparer)) 240 | { 241 | // Initialization through copy (very efficient) because the comparer is the same. 242 | this._entries = new Entry[_capacity]; 243 | Array.Copy(src._entries, _entries, _capacity); 244 | 245 | _usedEntries = new int[src._usedEntries.Length]; 246 | Array.Copy(src._usedEntries, _usedEntries, src._usedEntries.Length); 247 | _usedEntriesIndex = src._usedEntriesIndex; 248 | } 249 | else 250 | { 251 | // Initialization through rehashing because the comparer is not the same. 252 | var entries = new Entry[_capacity]; 253 | BlockCopyMemoryHelper.Memset(entries, new Entry(KUnusedHash, default(TKey), default(TValue))); 254 | 255 | // Creating a temporary alias to use for rehashing. 256 | this._entries = src._entries; 257 | _usedEntries = new int[_capacity]; 258 | 259 | // This call will rewrite the aliases 260 | Rehash(entries); 261 | } 262 | } 263 | 264 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 265 | protected FastDictionaryBase(TComparer comparer) 266 | : this(DictionaryHelper.KInitialCapacity, comparer) 267 | { } 268 | 269 | protected FastDictionaryBase(int initialBucketCount, TComparer comparer) 270 | { 271 | Contract.Requires(comparer != null); 272 | Contract.Ensures(_capacity >= initialBucketCount); 273 | 274 | this._comparer = comparer; 275 | 276 | // Calculate the next power of 2. 277 | int newCapacity = initialBucketCount >= DictionaryHelper.KMinBuckets ? initialBucketCount : DictionaryHelper.KMinBuckets; 278 | newCapacity = Bits.NextPowerOf2(newCapacity); 279 | 280 | this._initialCapacity = newCapacity; 281 | 282 | // Initialization 283 | this._entries = new Entry[newCapacity]; 284 | BlockCopyMemoryHelper.Memset(this._entries, new Entry(KUnusedHash, default(TKey), default(TValue))); 285 | 286 | _usedEntries = new int[newCapacity]; 287 | _usedEntriesIndex = 0; 288 | 289 | this._capacity = newCapacity; 290 | this._capacityMask = this._capacity - 1; 291 | 292 | this._numberOfUsed = 0; 293 | this._numberOfDeleted = 0; 294 | this._size = 0; 295 | 296 | this._nextGrowthThreshold = _capacity * 4 / KLoadFactor; 297 | } 298 | 299 | public void Add(TKey key, TValue value) 300 | { 301 | Contract.Requires(key != null); 302 | Contract.Ensures(this._numberOfUsed <= this._capacity); 303 | 304 | ResizeIfNeeded(); 305 | 306 | int hash = GetInternalHashCode(key); // PERF: This goes first because it can consume lots of registers. 307 | uint uhash = (uint)hash; 308 | 309 | var entries = _entries; 310 | 311 | int numProbes = 1; 312 | int capacity = _capacity; 313 | int bucket = hash & _capacityMask; 314 | 315 | Loop: 316 | do 317 | { 318 | uint nHash = entries[bucket].Hash; 319 | if (nHash == KUnusedHash) 320 | { 321 | _numberOfUsed++; 322 | _size++; 323 | goto Set; 324 | } 325 | if (nHash == KDeletedHash) 326 | { 327 | _numberOfDeleted--; 328 | _size++; 329 | goto Set; 330 | } 331 | 332 | if (nHash == uhash) 333 | goto PartialHit; 334 | 335 | bucket = (bucket + numProbes) & _capacityMask; 336 | numProbes++; 337 | 338 | #if DEBUG 339 | if ( numProbes >= 100 ) 340 | throw new InvalidOperationException("The hash function used for this object is not good enough. The distribution is causing clusters and causing huge slowdowns."); 341 | #else 342 | if (numProbes >= capacity) 343 | break; 344 | #endif 345 | } 346 | while (true); 347 | 348 | // PERF: If it happens, it should be rare therefore outside of the critical path. 349 | PartialHit: 350 | if (!_comparer.Equals(entries[bucket].Key, key)) 351 | { 352 | // PERF: This can happen with a very^3 low probability (assuming your hash function is good enough) 353 | bucket = (bucket + numProbes) & _capacityMask; 354 | numProbes++; 355 | goto Loop; 356 | } 357 | ThrowWhenDuplicatedKey(key); // We throw here. 358 | 359 | Set: 360 | 361 | this._entries[bucket].Hash = uhash; 362 | this._entries[bucket].Key = key; 363 | this._entries[bucket].Value = value; 364 | 365 | if (_usedEntriesIndex >= _usedEntries.Length) 366 | Array.Resize(ref _usedEntries, _usedEntries.Length * 2); // deleted entries considered in _usedEntries for later ClearUsedPortion so we may have bigger _usedEntries size 367 | this._usedEntries[_usedEntriesIndex++] = bucket; 368 | } 369 | 370 | private void ThrowWhenKeyIsNull(TKey key) 371 | { 372 | throw new ArgumentNullException(nameof(key)); 373 | } 374 | 375 | private void ThrowWhenDuplicatedKey(TKey key) 376 | { 377 | throw new ArgumentException("Cannot add duplicated key.", nameof(key)); 378 | } 379 | 380 | public bool Remove(TKey key) 381 | { 382 | throw new NotSupportedException("Current implementation has a bug, do not use."); 383 | 384 | //Contract.Ensures(this._numberOfUsed < this._capacity); 385 | 386 | //if (key == null) 387 | // throw new ArgumentNullException(nameof(key)); 388 | 389 | //int bucket = Lookup(key); 390 | //if (bucket == InvalidNodePosition) 391 | // return false; 392 | 393 | //SetDeleted(bucket); 394 | 395 | //return true; 396 | } 397 | 398 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 399 | private void SetDeleted(int node) 400 | { 401 | Contract.Ensures(_size <= Contract.OldValue(_size)); 402 | 403 | if (_entries[node].Hash < KDeletedHash) 404 | { 405 | _entries[node].Hash = KDeletedHash; 406 | _entries[node].Key = default(TKey); 407 | _entries[node].Value = default(TValue); 408 | 409 | _numberOfDeleted++; 410 | _size--; 411 | } 412 | 413 | Contract.Assert(_numberOfDeleted >= Contract.OldValue(_numberOfDeleted)); 414 | Contract.Assert(_entries[node].Hash == KDeletedHash); 415 | 416 | if (3 * this._numberOfDeleted / 2 > this._capacity - this._numberOfUsed) 417 | { 418 | // We will force a rehash with the growth factor based on the current size. 419 | Shrink(Math.Max(_initialCapacity, _size * 2)); 420 | } 421 | } 422 | 423 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 424 | private void ResizeIfNeeded() 425 | { 426 | if (_size >= _nextGrowthThreshold) 427 | { 428 | Grow(_capacity * 2); 429 | } 430 | } 431 | 432 | private void Shrink(int newCapacity) 433 | { 434 | Contract.Requires(newCapacity > _size); 435 | Contract.Ensures(this._numberOfUsed < this._capacity); 436 | 437 | // Calculate the next power of 2. 438 | newCapacity = Math.Max(Bits.NextPowerOf2(newCapacity), _initialCapacity); 439 | 440 | var entries = new Entry[newCapacity]; 441 | BlockCopyMemoryHelper.Memset(entries, new Entry(KUnusedHash, default(TKey), default(TValue))); 442 | 443 | Array.Resize(ref _usedEntries, newCapacity); 444 | 445 | Rehash(entries); 446 | } 447 | 448 | public TValue this[TKey key] 449 | { 450 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 451 | get 452 | { 453 | Contract.Requires(key != null); 454 | Contract.Ensures(this._numberOfUsed <= this._capacity); 455 | 456 | int hash = GetInternalHashCode(key); // PERF: This goes first because it can consume lots of registers. 457 | 458 | var entries = _entries; 459 | 460 | int numProbes = 1; 461 | int capacity = _capacity; 462 | int bucket = hash & _capacityMask; 463 | 464 | Loop: 465 | do 466 | { 467 | uint nHash = entries[bucket].Hash; 468 | if (nHash == KUnusedHash) 469 | goto ReturnFalse; 470 | if (nHash == hash) 471 | goto PartialHit; 472 | 473 | bucket = (bucket + numProbes) & _capacityMask; 474 | numProbes++; 475 | 476 | #if DEBUG 477 | if ( numProbes >= 100 ) 478 | throw new InvalidOperationException("The hash function used for this object is not good enough. The distribution is causing clusters and causing huge slowdowns."); 479 | #else 480 | if (numProbes >= capacity) 481 | break; 482 | #endif 483 | } 484 | while (true); 485 | 486 | ReturnFalse: 487 | return ThrowWhenKeyNotFound(); 488 | 489 | PartialHit: 490 | if (!_comparer.Equals(entries[bucket].Key, key)) 491 | { 492 | // PERF: This can happen with a very^3 low probability (assuming your hash function is good enough) 493 | bucket = (bucket + numProbes) & _capacityMask; 494 | numProbes++; 495 | goto Loop; 496 | } 497 | 498 | return entries[bucket].Value; 499 | } 500 | 501 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 502 | set 503 | { 504 | Contract.Requires(key != null); 505 | Contract.Ensures(this._numberOfUsed <= this._capacity); 506 | 507 | ResizeIfNeeded(); 508 | 509 | int hash = GetInternalHashCode(key); // PERF: This goes first because it can consume lots of registers. 510 | uint uhash = (uint)hash; 511 | 512 | var entries = _entries; 513 | 514 | int numProbes = 1; 515 | int capacity = _capacity; 516 | int bucket = hash & _capacityMask; 517 | 518 | Loop: 519 | do 520 | { 521 | uint nHash = entries[bucket].Hash; 522 | if (nHash == KUnusedHash) 523 | { 524 | _numberOfUsed++; 525 | _size++; 526 | goto Set; 527 | } 528 | if (nHash == KDeletedHash) 529 | { 530 | _numberOfDeleted--; 531 | _size++; 532 | goto Set; 533 | } 534 | 535 | if (nHash == uhash) 536 | goto PartialHit; 537 | 538 | bucket = (bucket + numProbes) & _capacityMask; 539 | numProbes++; 540 | 541 | #if DEBUG 542 | if ( numProbes >= 100 ) 543 | throw new InvalidOperationException("The hash function used for this object is not good enough. The distribution is causing clusters and causing huge slowdowns."); 544 | #else 545 | if (numProbes >= capacity) 546 | break; 547 | #endif 548 | } 549 | while (true); 550 | 551 | // PERF: If it happens, it should be rare therefore outside of the critical path. 552 | PartialHit: 553 | if (!_comparer.Equals(entries[bucket].Key, key)) 554 | { 555 | // PERF: This can happen with a very^3 low probability (assuming your hash function is good enough) 556 | bucket = (bucket + numProbes) & _capacityMask; 557 | numProbes++; 558 | goto Loop; 559 | } 560 | 561 | Set: 562 | 563 | this._entries[bucket].Hash = uhash; 564 | this._entries[bucket].Key = key; 565 | this._entries[bucket].Value = value; 566 | 567 | if (_usedEntriesIndex >= _usedEntries.Length) 568 | Array.Resize(ref _usedEntries, _usedEntries.Length * 2); // deleted entries considered in _usedEntries for later ClearUsedPortion so we may have bigger _usedEntries size 569 | _usedEntries[_usedEntriesIndex++] = bucket; 570 | } 571 | } 572 | 573 | private static TValue ThrowWhenKeyNotFound() 574 | { 575 | throw new KeyNotFoundException(); 576 | } 577 | 578 | public void Clear() 579 | { 580 | var entry = new Entry(KUnusedHash, default(TKey), default(TValue)); 581 | 582 | for (int i = 0; i < _usedEntriesIndex; i++) 583 | this._entries[_usedEntries[i]] = entry; 584 | 585 | this._usedEntriesIndex = 0; 586 | this._numberOfUsed = 0; 587 | this._numberOfDeleted = 0; 588 | this._size = 0; 589 | } 590 | 591 | public bool Contains(TKey key) 592 | { 593 | Contract.Ensures(this._numberOfUsed <= this._capacity); 594 | 595 | if (key == null) 596 | throw new ArgumentNullException(nameof(key)); 597 | 598 | return (Lookup(key) != InvalidNodePosition); 599 | } 600 | 601 | private void Grow(int newCapacity) 602 | { 603 | Contract.Requires(newCapacity >= _capacity); 604 | Contract.Ensures((_capacity & (_capacity - 1)) == 0); 605 | 606 | var entries = new Entry[newCapacity]; 607 | BlockCopyMemoryHelper.Memset(entries, new Entry(KUnusedHash, default(TKey), default(TValue))); 608 | 609 | Array.Resize(ref _usedEntries, newCapacity); 610 | 611 | Rehash(entries); 612 | } 613 | 614 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 615 | public bool TryGetValue(TKey key, out TValue value) 616 | { 617 | Contract.Requires(key != null); 618 | Contract.Ensures(this._numberOfUsed <= this._capacity); 619 | 620 | int hash = GetInternalHashCode(key); // PERF: This goes first because it can consume lots of registers. 621 | 622 | var entries = _entries; 623 | 624 | int numProbes = 1; 625 | int capacity = _capacity; 626 | int bucket = hash & _capacityMask; 627 | 628 | Loop: 629 | do 630 | { 631 | uint nHash = entries[bucket].Hash; 632 | if (nHash == KUnusedHash) 633 | goto ReturnFalse; 634 | if (nHash == hash) 635 | goto PartialHit; 636 | 637 | bucket = (bucket + numProbes) & _capacityMask; 638 | numProbes++; 639 | 640 | #if DEBUG 641 | if ( numProbes >= 100 ) 642 | throw new InvalidOperationException("The hash function used for this object is not good enough. The distribution is causing clusters and causing huge slowdowns."); 643 | #else 644 | if (numProbes >= capacity) 645 | break; 646 | #endif 647 | } 648 | while (true); 649 | 650 | ReturnFalse: 651 | value = default(TValue); 652 | return false; 653 | 654 | PartialHit: 655 | if (!_comparer.Equals(entries[bucket].Key, key)) 656 | { 657 | // PERF: This can happen with a very^3 low probability (assuming your hash function is good enough) 658 | bucket = (bucket + numProbes) & _capacityMask; 659 | numProbes++; 660 | goto Loop; 661 | } 662 | 663 | value = entries[bucket].Value; 664 | return true; 665 | } 666 | 667 | /// 668 | /// 669 | /// 670 | /// 671 | /// Position of the node in the array 672 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 673 | private int Lookup(TKey key) 674 | { 675 | int hash = GetInternalHashCode(key); // PERF: This goes first because it can consume lots of registers. 676 | 677 | var entries = _entries; 678 | 679 | int numProbes = 1; 680 | int capacity = _capacity; 681 | int bucket = hash & _capacityMask; 682 | 683 | Loop: 684 | do 685 | { 686 | uint nHash = entries[bucket].Hash; 687 | if (nHash == KUnusedHash) 688 | goto ReturnFalse; 689 | if (nHash == hash) 690 | goto PartialHit; 691 | 692 | bucket = (bucket + numProbes) & _capacityMask; 693 | numProbes++; 694 | 695 | #if DEBUG 696 | if ( numProbes >= 100 ) 697 | throw new InvalidOperationException("The hash function used for this object is not good enough. The distribution is causing clusters and causing huge slowdowns."); 698 | #else 699 | if (numProbes >= capacity) 700 | break; 701 | #endif 702 | } 703 | while (true); 704 | 705 | ReturnFalse: return InvalidNodePosition; 706 | 707 | PartialHit: 708 | if (!_comparer.Equals(entries[bucket].Key, key)) 709 | { 710 | // PERF: This can happen with a very^3 low probability (assuming your hash function is good enough) 711 | bucket = (bucket + numProbes) & _capacityMask; 712 | numProbes++; 713 | goto Loop; 714 | } 715 | 716 | return bucket; 717 | } 718 | 719 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 720 | private int GetInternalHashCode(TKey key) 721 | { 722 | return _comparer.GetHashCode(key) & 0x7FFFFFFF; 723 | } 724 | 725 | private void Rehash(Entry[] entries) 726 | { 727 | var size = 0; 728 | _usedEntriesIndex = 0; 729 | 730 | int newCapacityMask = entries.Length - 1; 731 | for (int it = 0; it < _entries.Length; it++) 732 | { 733 | uint hash = _entries[it].Hash; 734 | if (hash >= KDeletedHash) // No interest for the process of rehashing, we are skipping it. 735 | continue; 736 | 737 | int bucket = (int)hash & newCapacityMask; 738 | 739 | int numProbes = 0; 740 | while (entries[bucket].Hash != KUnusedHash) 741 | { 742 | numProbes++; 743 | bucket = (bucket + numProbes) & newCapacityMask; 744 | } 745 | 746 | entries[bucket].Hash = hash; 747 | entries[bucket].Key = _entries[it].Key; 748 | entries[bucket].Value = _entries[it].Value; 749 | 750 | if (_usedEntriesIndex >= _usedEntries.Length) 751 | Array.Resize(ref _usedEntries, _usedEntries.Length * 2); // deleted entries considered in _usedEntries for later ClearUsedPortion so we may have bigger _usedEntries size 752 | _usedEntries[_usedEntriesIndex++] = bucket; 753 | 754 | size++; 755 | } 756 | 757 | this._capacity = entries.Length; 758 | this._capacityMask = newCapacityMask; 759 | this._size = size; 760 | this._entries = entries; 761 | 762 | this._numberOfUsed = size; 763 | this._numberOfDeleted = 0; 764 | 765 | this._nextGrowthThreshold = _capacity * 4 / KLoadFactor; 766 | } 767 | 768 | 769 | public void CopyTo(KeyValuePair[] array, int index) 770 | { 771 | if (array == null) 772 | throw new ArgumentNullException(nameof(array), "The array cannot be null"); 773 | 774 | if (array.Rank != 1) 775 | throw new ArgumentException("Multiple dimensions array are not supporter", nameof(array)); 776 | 777 | if (index < 0 || index > array.Length) 778 | throw new ArgumentOutOfRangeException(nameof(index)); 779 | 780 | if (array.Length - index < Count) 781 | throw new ArgumentException("The array plus the offset is too small."); 782 | 783 | int count = _capacity; 784 | 785 | var entries = _entries; 786 | 787 | for (int i = 0; i < count; i++) 788 | { 789 | if (entries[i].Hash < KDeletedHash) 790 | array[index++] = new KeyValuePair(entries[i].Key, entries[i].Value); 791 | } 792 | } 793 | 794 | IEnumerator IEnumerable.GetEnumerator() 795 | { 796 | return new Enumerator(this); 797 | } 798 | 799 | public IEnumerator> GetEnumerator() 800 | { 801 | return new Enumerator(this); 802 | } 803 | 804 | 805 | public struct Enumerator : IEnumerator> 806 | { 807 | private readonly FastDictionaryBase _dictionary; 808 | private int _index; 809 | private KeyValuePair _current; 810 | 811 | internal const int DictEntry = 1; 812 | internal const int KeyValuePair = 2; 813 | 814 | internal Enumerator(FastDictionaryBase dictionary) 815 | { 816 | this._dictionary = dictionary; 817 | this._index = 0; 818 | this._current = new KeyValuePair(); 819 | } 820 | 821 | public bool MoveNext() 822 | { 823 | var count = _dictionary._capacity; 824 | var entries = _dictionary._entries; 825 | 826 | // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends. 827 | // dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue 828 | while (_index < count) 829 | { 830 | if (entries[_index].Hash < KDeletedHash) 831 | { 832 | _current = new KeyValuePair(entries[_index].Key, entries[_index].Value); 833 | _index++; 834 | return true; 835 | } 836 | _index++; 837 | } 838 | 839 | _index = count + 1; 840 | _current = new KeyValuePair(); 841 | return false; 842 | } 843 | 844 | public KeyValuePair Current => _current; 845 | 846 | public void Dispose() 847 | { 848 | } 849 | 850 | object IEnumerator.Current 851 | { 852 | get 853 | { 854 | if (_index == 0 || (_index == _dictionary._capacity + 1)) 855 | throw new InvalidOperationException("Can't happen."); 856 | 857 | return new KeyValuePair(_current.Key, _current.Value); 858 | } 859 | } 860 | 861 | void IEnumerator.Reset() 862 | { 863 | _index = 0; 864 | _current = new KeyValuePair(); 865 | } 866 | } 867 | 868 | public KeyCollection Keys => new KeyCollection(this); 869 | 870 | public ValueCollection Values => new ValueCollection(this); 871 | 872 | public bool ContainsKey(TKey key) 873 | { 874 | if (key == null) 875 | throw new ArgumentNullException(nameof(key)); 876 | 877 | return (Lookup(key) != InvalidNodePosition); 878 | } 879 | 880 | public bool ContainsValue(TValue value) 881 | { 882 | var entries = _entries; 883 | int count = _capacity; 884 | 885 | if (value == null) 886 | { 887 | for (int i = 0; i < count; i++) 888 | { 889 | if (entries[i].Hash < KDeletedHash && entries[i].Value == null) 890 | return true; 891 | } 892 | } 893 | else 894 | { 895 | EqualityComparer c = EqualityComparer.Default; 896 | for (int i = 0; i < count; i++) 897 | { 898 | if (entries[i].Hash < KDeletedHash && c.Equals(entries[i].Value, value)) 899 | return true; 900 | } 901 | } 902 | return false; 903 | } 904 | 905 | 906 | public sealed class KeyCollection : IEnumerable 907 | { 908 | private readonly FastDictionaryBase _dictionary; 909 | 910 | public KeyCollection(FastDictionaryBase dictionary) 911 | { 912 | Contract.Requires(dictionary != null); 913 | 914 | this._dictionary = dictionary; 915 | } 916 | 917 | public Enumerator GetEnumerator() 918 | { 919 | return new Enumerator(_dictionary); 920 | } 921 | 922 | public void CopyTo(TKey[] array, int index) 923 | { 924 | if (array == null) 925 | throw new ArgumentNullException("The array cannot be null", "array"); 926 | 927 | if (index < 0 || index > array.Length) 928 | throw new ArgumentOutOfRangeException("index"); 929 | 930 | if (array.Length - index < _dictionary.Count) 931 | throw new ArgumentException("The array plus the offset is too small."); 932 | 933 | int count = _dictionary._capacity; 934 | var entries = _dictionary._entries; 935 | 936 | for (int i = 0; i < count; i++) 937 | { 938 | if (entries[i].Hash < KDeletedHash) 939 | array[index++] = entries[i].Key; 940 | } 941 | } 942 | 943 | public int Count => _dictionary.Count; 944 | 945 | 946 | IEnumerator IEnumerable.GetEnumerator() 947 | { 948 | return new Enumerator(_dictionary); 949 | } 950 | 951 | IEnumerator IEnumerable.GetEnumerator() 952 | { 953 | return new Enumerator(_dictionary); 954 | } 955 | 956 | 957 | public struct Enumerator : IEnumerator 958 | { 959 | private readonly FastDictionaryBase _dictionary; 960 | private int _index; 961 | private TKey _currentKey; 962 | 963 | internal Enumerator(FastDictionaryBase dictionary) 964 | { 965 | this._dictionary = dictionary; 966 | _index = 0; 967 | _currentKey = default(TKey); 968 | } 969 | 970 | public void Dispose() 971 | { 972 | } 973 | 974 | public bool MoveNext() 975 | { 976 | var count = _dictionary._capacity; 977 | 978 | var entries = _dictionary._entries; 979 | while (_index < count) 980 | { 981 | if (entries[_index].Hash < KDeletedHash) 982 | { 983 | _currentKey = entries[_index].Key; 984 | _index++; 985 | return true; 986 | } 987 | _index++; 988 | } 989 | 990 | _index = count + 1; 991 | _currentKey = default(TKey); 992 | return false; 993 | } 994 | 995 | public TKey Current => _currentKey; 996 | 997 | Object System.Collections.IEnumerator.Current 998 | { 999 | get 1000 | { 1001 | if (_index == 0 || (_index == _dictionary.Count + 1)) 1002 | throw new InvalidOperationException("Cant happen."); 1003 | 1004 | return _currentKey; 1005 | } 1006 | } 1007 | 1008 | void System.Collections.IEnumerator.Reset() 1009 | { 1010 | _index = 0; 1011 | _currentKey = default(TKey); 1012 | } 1013 | } 1014 | } 1015 | 1016 | 1017 | 1018 | public sealed class ValueCollection : IEnumerable 1019 | { 1020 | private readonly FastDictionaryBase _dictionary; 1021 | 1022 | public ValueCollection(FastDictionaryBase dictionary) 1023 | { 1024 | Contract.Requires(dictionary != null); 1025 | 1026 | this._dictionary = dictionary; 1027 | } 1028 | 1029 | public Enumerator GetEnumerator() 1030 | { 1031 | return new Enumerator(_dictionary); 1032 | } 1033 | 1034 | public void CopyTo(TValue[] array, int index) 1035 | { 1036 | if (array == null) 1037 | throw new ArgumentNullException(nameof(array), "The array cannot be null"); 1038 | 1039 | if (index < 0 || index > array.Length) 1040 | throw new ArgumentOutOfRangeException(nameof(index)); 1041 | 1042 | if (array.Length - index < _dictionary.Count) 1043 | throw new ArgumentException("The array plus the offset is too small."); 1044 | 1045 | int count = _dictionary._capacity; 1046 | 1047 | var entries = _dictionary._entries; 1048 | for (int i = 0; i < count; i++) 1049 | { 1050 | if (entries[i].Hash < KDeletedHash) 1051 | array[index++] = entries[i].Value; 1052 | } 1053 | } 1054 | 1055 | public int Count => _dictionary.Count; 1056 | 1057 | IEnumerator IEnumerable.GetEnumerator() 1058 | { 1059 | return new Enumerator(_dictionary); 1060 | } 1061 | 1062 | IEnumerator IEnumerable.GetEnumerator() 1063 | { 1064 | return new Enumerator(_dictionary); 1065 | } 1066 | 1067 | 1068 | public struct Enumerator : IEnumerator 1069 | { 1070 | private readonly FastDictionaryBase _dictionary; 1071 | private int _index; 1072 | private TValue _currentValue; 1073 | 1074 | internal Enumerator(FastDictionaryBase dictionary) 1075 | { 1076 | _dictionary = dictionary; 1077 | _index = 0; 1078 | _currentValue = default(TValue); 1079 | } 1080 | 1081 | public void Dispose() 1082 | { 1083 | } 1084 | 1085 | public bool MoveNext() 1086 | { 1087 | var count = _dictionary._capacity; 1088 | 1089 | var entries = _dictionary._entries; 1090 | while (_index < count) 1091 | { 1092 | if (entries[_index].Hash < KDeletedHash) 1093 | { 1094 | _currentValue = entries[_index].Value; 1095 | _index++; 1096 | return true; 1097 | } 1098 | _index++; 1099 | } 1100 | 1101 | _index = count + 1; 1102 | _currentValue = default(TValue); 1103 | return false; 1104 | } 1105 | 1106 | public TValue Current => _currentValue; 1107 | 1108 | Object IEnumerator.Current 1109 | { 1110 | get 1111 | { 1112 | if (_index == 0 || (_index == _dictionary.Count + 1)) 1113 | throw new InvalidOperationException("Cant happen."); 1114 | 1115 | return _currentValue; 1116 | } 1117 | } 1118 | 1119 | void IEnumerator.Reset() 1120 | { 1121 | _index = 0; 1122 | _currentValue = default(TValue); 1123 | } 1124 | } 1125 | } 1126 | 1127 | private static class BlockCopyMemoryHelper 1128 | { 1129 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 1130 | public static void Memset(Entry[] array, Entry value) 1131 | { 1132 | int block = 64, index = 0; 1133 | int length = Math.Min(block, array.Length); 1134 | 1135 | //Fill the initial array 1136 | while (index < length) 1137 | { 1138 | array[index++] = value; 1139 | } 1140 | 1141 | length = array.Length; 1142 | while (index < length) 1143 | { 1144 | Array.Copy(array, 0, array, index, Math.Min(block, (length - index))); 1145 | index += block; 1146 | 1147 | block *= 2; 1148 | } 1149 | } 1150 | } 1151 | } 1152 | 1153 | public struct NumericEqualityComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, IEqualityComparer 1154 | { 1155 | public static readonly NumericEqualityComparer Instance = new NumericEqualityComparer(); 1156 | 1157 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 1158 | public bool Equals(long x, long y) 1159 | { 1160 | return x == y; 1161 | } 1162 | 1163 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 1164 | public int GetHashCode(long obj) 1165 | { 1166 | return Mix(obj); 1167 | } 1168 | 1169 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 1170 | private static int Mix(long key) 1171 | { 1172 | key = (~key) + (key << 18); // key = (key << 18) - key - 1; 1173 | key = key ^ (key >> 31); 1174 | key = key * 21; // key = (key + (key << 2)) + (key << 4); 1175 | key = key ^ (key >> 11); 1176 | key = key + (key << 6); 1177 | key = key ^ (key >> 22); 1178 | return (int)key; 1179 | } 1180 | 1181 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 1182 | public bool Equals(int x, int y) 1183 | { 1184 | return x == y; 1185 | } 1186 | 1187 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 1188 | public int GetHashCode(int obj) 1189 | { 1190 | return Mix(obj); 1191 | } 1192 | 1193 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 1194 | public bool Equals(ulong x, ulong y) 1195 | { 1196 | return x == y; 1197 | } 1198 | 1199 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 1200 | public int GetHashCode(ulong obj) 1201 | { 1202 | return Mix((long)obj); 1203 | } 1204 | 1205 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 1206 | public bool Equals(uint x, uint y) 1207 | { 1208 | return x == y; 1209 | } 1210 | 1211 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 1212 | public int GetHashCode(uint obj) 1213 | { 1214 | return (int)Mix(obj); 1215 | } 1216 | 1217 | 1218 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 1219 | private static uint Mix(uint key) 1220 | { 1221 | key = ~key + (key << 15); // key = (key << 15) - key - 1; 1222 | key = key ^ (key >> 12); 1223 | key = key + (key << 2); 1224 | key = key ^ (key >> 4); 1225 | key = key * 2057; // key = (key + (key << 3)) + (key << 11); 1226 | key = key ^ (key >> 16); 1227 | return key; 1228 | } 1229 | 1230 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 1231 | private static int Mix(int key) 1232 | { 1233 | return (int)Mix((uint)key); 1234 | } 1235 | } 1236 | } 1237 | -------------------------------------------------------------------------------- /Course/Dictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using BenchmarkDotNet.Attributes; 5 | 6 | namespace Course 7 | { 8 | // Taken from the BenchmarkDotNet samples. 9 | // From https://github.com/dotnet/coreclr/issues/1579 10 | [MemoryDiagnoser] 11 | public class Framework_DictionaryVsIDictionary 12 | { 13 | Dictionary dict; 14 | IDictionary idict; 15 | 16 | [Setup] 17 | public void Setup() 18 | { 19 | dict = new Dictionary(); 20 | idict = (IDictionary)dict; 21 | } 22 | 23 | [Benchmark] 24 | public Dictionary DictionaryEnumeration() 25 | { 26 | // Doesn't allocate 27 | foreach (var item in dict) 28 | { 29 | ; 30 | } 31 | return dict; 32 | } 33 | 34 | [Benchmark] 35 | public IDictionary IDictionaryEnumeration() 36 | { 37 | // Allocates 998k 38 | foreach (var item in idict) 39 | { 40 | ; 41 | } 42 | return idict; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Course/Inlining.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Text; 5 | using BenchmarkDotNet.Attributes; 6 | using BenchmarkDotNet.Diagnosers; 7 | 8 | #if FULL 9 | using BenchmarkDotNet.Diagnostics.Windows.Configs; 10 | #endif 11 | 12 | 13 | namespace Course 14 | { 15 | #if FULL 16 | [InliningDiagnoser] 17 | #endif 18 | [HardwareCounters(HardwareCounter.InstructionRetired)] 19 | public class Inlining 20 | { 21 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 22 | private int Inlined(int i) 23 | { 24 | return i; 25 | } 26 | 27 | [Benchmark] 28 | public int SumInlined() 29 | { 30 | int result = 0; 31 | for (int i = 0; i < 1000; i++) 32 | result += Inlined(i); 33 | return result; 34 | } 35 | 36 | [MethodImpl(MethodImplOptions.NoInlining)] 37 | private int NonInlined(int i) 38 | { 39 | return i; 40 | } 41 | 42 | [Benchmark] 43 | public int SumNonInlined() 44 | { 45 | int result = 0; 46 | for (int i = 0; i < 1000; i++) 47 | result += NonInlined(i); 48 | return result; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Course/ObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Threading; 6 | using BenchmarkDotNet.Attributes; 7 | using BenchmarkDotNet.Diagnosers; 8 | 9 | namespace Course 10 | { 11 | public sealed class ObjectPool : ObjectPool, NonThreadAwareBehavior> 12 | where T : class, new() 13 | { 14 | public ObjectPool(NoResetFactorySupport.Factory factory, int size = 16) 15 | : base(size, new NoResetFactorySupport(factory)) 16 | { } 17 | } 18 | 19 | public sealed class ObjectPool : ObjectPool 20 | where T : class 21 | where TObjectLifecycle : struct, IObjectLifecycle 22 | { 23 | public ObjectPool(int size = 16, TObjectLifecycle factory = default(TObjectLifecycle)) : base(size, factory) 24 | { } 25 | } 26 | 27 | public class ObjectPool 28 | where T : class 29 | where TObjectLifecycle : struct, IObjectLifecycle 30 | where TProcessAwareBehavior : struct, IProcessAwareBehavior 31 | { 32 | private static readonly TObjectLifecycle Lifecycle = new TObjectLifecycle(); 33 | 34 | private struct Element 35 | { 36 | internal T Value; 37 | } 38 | 39 | [StructLayout(LayoutKind.Sequential, Size = 128)] 40 | private struct CacheAwareElement 41 | { 42 | private readonly long _pad1; 43 | private T Value1; 44 | private T Value2; 45 | private T Value3; 46 | private T Value4; 47 | 48 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 49 | public static void Clear(ref CacheAwareElement bucket, ref TEvictionStrategy policy) 50 | where TEvictionStrategy : struct, IEvictionStrategy 51 | { 52 | // Note that the initial read is optimistically not synchronized. That is intentional. 53 | // We will interlock only when we have a candidate. in a worst case we may miss some 54 | // recently returned objects. Not a big deal. 55 | T inst = bucket.Value1; 56 | if (inst != null && policy.CanEvict(inst)) 57 | { 58 | Interlocked.CompareExchange(ref bucket.Value1, null, inst); 59 | } 60 | 61 | inst = bucket.Value2; 62 | if (inst != null && policy.CanEvict(inst)) 63 | { 64 | Interlocked.CompareExchange(ref bucket.Value2, null, inst); 65 | } 66 | 67 | inst = bucket.Value3; 68 | if (inst != null && policy.CanEvict(inst)) 69 | { 70 | Interlocked.CompareExchange(ref bucket.Value3, null, inst); 71 | } 72 | 73 | inst = bucket.Value4; 74 | if (inst != null && policy.CanEvict(inst)) 75 | { 76 | Interlocked.CompareExchange(ref bucket.Value4, null, inst); 77 | } 78 | } 79 | 80 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 81 | public static void ClearAndDispose(ref CacheAwareElement bucket, ref TEvictionStrategy policy) 82 | where TEvictionStrategy : struct, IEvictionStrategy 83 | { 84 | // Note that the initial read is optimistically not synchronized. That is intentional. 85 | // We will interlock only when we have a candidate. in a worst case we may miss some 86 | // recently returned objects. Not a big deal. 87 | T inst = bucket.Value1; 88 | if (inst != null && policy.CanEvict(inst)) 89 | { 90 | if (inst == Interlocked.CompareExchange(ref bucket.Value1, null, inst)) 91 | ((IDisposable)inst).Dispose(); 92 | } 93 | 94 | inst = bucket.Value2; 95 | if (inst != null && policy.CanEvict(inst)) 96 | { 97 | if (inst == Interlocked.CompareExchange(ref bucket.Value2, null, inst)) 98 | ((IDisposable)inst).Dispose(); 99 | } 100 | 101 | inst = bucket.Value3; 102 | if (inst != null && policy.CanEvict(inst)) 103 | { 104 | if (inst == Interlocked.CompareExchange(ref bucket.Value3, null, inst)) 105 | ((IDisposable)inst).Dispose(); 106 | } 107 | 108 | inst = bucket.Value4; 109 | if (inst != null && policy.CanEvict(inst)) 110 | { 111 | if (inst == Interlocked.CompareExchange(ref bucket.Value4, null, inst)) 112 | ((IDisposable)inst).Dispose(); 113 | } 114 | } 115 | 116 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 117 | public static bool TryClaim(ref CacheAwareElement bucket, out T item) 118 | { 119 | // Note that the initial read is optimistically not synchronized. That is intentional. 120 | // We will interlock only when we have a candidate. in a worst case we may miss some 121 | // recently returned objects. Not a big deal. 122 | T inst = bucket.Value1; 123 | if (inst != null && inst == Interlocked.CompareExchange(ref bucket.Value1, null, inst)) 124 | goto Done; 125 | 126 | inst = bucket.Value2; 127 | if (inst != null && inst == Interlocked.CompareExchange(ref bucket.Value2, null, inst)) 128 | goto Done; 129 | 130 | inst = bucket.Value3; 131 | if (inst != null && inst == Interlocked.CompareExchange(ref bucket.Value3, null, inst)) 132 | goto Done; 133 | 134 | inst = bucket.Value4; 135 | if (inst != null && inst == Interlocked.CompareExchange(ref bucket.Value4, null, inst)) 136 | goto Done; 137 | 138 | item = null; 139 | return false; 140 | 141 | Done: 142 | item = inst; 143 | return true; 144 | } 145 | 146 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 147 | public static bool TryRelease(ref CacheAwareElement bucket, T value) 148 | { 149 | if (null == Interlocked.CompareExchange(ref bucket.Value1, value, null)) 150 | goto Done; 151 | 152 | if (null == Interlocked.CompareExchange(ref bucket.Value2, value, null)) 153 | goto Done; 154 | 155 | if (null == Interlocked.CompareExchange(ref bucket.Value3, value, null)) 156 | goto Done; 157 | 158 | if (null == Interlocked.CompareExchange(ref bucket.Value4, value, null)) 159 | goto Done; 160 | 161 | return false; 162 | 163 | Done: return true; 164 | } 165 | } 166 | 167 | // Storage for the pool objects. The first item is stored in a dedicated field because we 168 | // expect to be able to satisfy most requests from it. 169 | private readonly CacheAwareElement[] _firstItems; 170 | private readonly int _bucketsMask; 171 | 172 | private T _firstElement; 173 | private int _itemTopCounter; 174 | private int _itemBottomCounter; 175 | private readonly uint _itemsMask; 176 | private readonly Element[] _items; 177 | 178 | private readonly TObjectLifecycle _factory; 179 | 180 | public ObjectPool(int size = 16, TObjectLifecycle factory = default(TObjectLifecycle), TProcessAwareBehavior behavior = default(TProcessAwareBehavior)) 181 | { 182 | Debug.Assert(size >= 1); 183 | _factory = factory; 184 | 185 | if (typeof(TProcessAwareBehavior) == typeof(ThreadAwareBehavior)) 186 | { 187 | int buckets = Bits.NextPowerOf2(behavior.Buckets); 188 | _bucketsMask = buckets - 1; 189 | _firstItems = new CacheAwareElement[buckets]; 190 | } 191 | 192 | // PERF: We will always have power of two pools to make operations a lot faster. 193 | size = Bits.NextPowerOf2(size); 194 | size = Math.Max(16, size); 195 | 196 | _items = new Element[size]; 197 | _itemsMask = (uint)size - 1; 198 | } 199 | 200 | /// 201 | /// Produces an instance. 202 | /// 203 | /// 204 | /// Search strategy is a simple linear probing which is chosen for it cache-friendliness. 205 | /// Note that Free will try to store recycled objects close to the start thus statistically 206 | /// reducing how far we will typically search. 207 | /// 208 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 209 | public T Allocate() 210 | { 211 | T inst; 212 | if (typeof(TProcessAwareBehavior) == typeof(ThreadAwareBehavior)) 213 | { 214 | int threadIndex = Environment.CurrentManagedThreadId & _bucketsMask; 215 | ref var firstItem = ref _firstItems[threadIndex]; 216 | if (!CacheAwareElement.TryClaim(ref firstItem, out inst)) 217 | { 218 | inst = AllocateSlow(); 219 | } 220 | } 221 | else 222 | { 223 | // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements. 224 | // Note that the initial read is optimistically not synchronized. That is intentional. 225 | // We will interlock only when we have a candidate. in a worst case we may miss some 226 | // recently returned objects. Not a big deal. 227 | 228 | inst = _firstElement; 229 | if (inst == null || inst != Interlocked.CompareExchange(ref _firstElement, null, inst)) 230 | { 231 | inst = AllocateSlow(); 232 | } 233 | } 234 | 235 | return inst; 236 | } 237 | 238 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 239 | private T AllocateSlow() 240 | { 241 | var items = _items; 242 | 243 | if (_itemBottomCounter < _itemTopCounter) 244 | { 245 | uint claim = ((uint)Interlocked.Increment(ref _itemBottomCounter) - 1) & _itemsMask; 246 | 247 | T inst = items[claim].Value; 248 | if (inst != null) 249 | { 250 | // WARNING: In a absurdly fast loop this can still fail to get a proper, that is why 251 | // we still use a compare exchange operation instead of using the reference. 252 | if (inst == Interlocked.CompareExchange(ref items[claim].Value, null, inst)) 253 | return inst; 254 | } 255 | } 256 | 257 | return _factory.New(); 258 | } 259 | 260 | /// 261 | /// Returns objects to the pool. 262 | /// 263 | /// 264 | /// Search strategy is a simple linear probing which is chosen for it cache-friendliness. 265 | /// Note that Free will try to store recycled objects close to the start thus statistically 266 | /// reducing how far we will typically search in Allocate. 267 | /// 268 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 269 | public void Free(T obj) 270 | { 271 | Lifecycle.Reset(obj); 272 | 273 | if (typeof(TProcessAwareBehavior) == typeof(ThreadAwareBehavior)) 274 | { 275 | int threadIndex = Environment.CurrentManagedThreadId & _bucketsMask; 276 | ref var firstItem = ref _firstItems[threadIndex]; 277 | 278 | if (CacheAwareElement.TryRelease(ref firstItem, obj)) 279 | return; 280 | } 281 | else 282 | { 283 | if (_firstElement == null) 284 | { 285 | if (null == Interlocked.CompareExchange(ref _firstElement, obj, null)) 286 | return; 287 | } 288 | } 289 | 290 | FreeSlow(obj); 291 | } 292 | 293 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 294 | private void FreeSlow(T obj) 295 | { 296 | var items = _items; 297 | 298 | uint current = (uint)Interlocked.Increment(ref _itemTopCounter); 299 | uint claim = (current - 1) & _itemsMask; 300 | 301 | ref var item = ref items[claim]; 302 | if (item.Value == null) 303 | { 304 | // Intentionally not using interlocked here. 305 | // In a worst case scenario two objects may be stored into same slot. 306 | // It is very unlikely to happen and will only mean that one of the objects will get collected. 307 | item.Value = obj; 308 | } 309 | } 310 | 311 | public void Clear(bool partial = true) 312 | { 313 | bool doDispose = typeof(IDisposable).IsAssignableFrom(typeof(T)); 314 | 315 | if (typeof(TProcessAwareBehavior) == typeof(ThreadAwareBehavior) && !partial) 316 | { 317 | for (int i = 0; i < _firstItems.Length; i++) 318 | { 319 | ref var bucket = ref _firstItems[i]; 320 | while (CacheAwareElement.TryClaim(ref bucket, out var obj)) 321 | { 322 | if (doDispose) 323 | ((IDisposable)obj).Dispose(); 324 | } 325 | } 326 | } 327 | 328 | uint current; 329 | do 330 | { 331 | current = (uint)Interlocked.Increment(ref _itemBottomCounter); 332 | if (current < _itemTopCounter) 333 | { 334 | uint claim = (current - 1) & _itemsMask; 335 | 336 | T inst = _items[claim].Value; 337 | if (inst != null) 338 | { 339 | // WARNING: In a absurdly fast loop this can still fail to get a proper, that is why 340 | // we still use a compare exchange operation instead of using the reference. 341 | if (inst == Interlocked.CompareExchange(ref _items[claim].Value, null, inst) && doDispose) 342 | { 343 | ((IDisposable)inst).Dispose(); 344 | } 345 | } 346 | } 347 | } 348 | while (current < _itemTopCounter); 349 | } 350 | 351 | public void Clear(TEvictionStrategy evictionStrategy = default(TEvictionStrategy)) 352 | where TEvictionStrategy : struct, IEvictionStrategy 353 | { 354 | bool doDispose = typeof(IDisposable).IsAssignableFrom(typeof(T)); 355 | 356 | if (typeof(TProcessAwareBehavior) == typeof(ThreadAwareBehavior)) 357 | { 358 | for (int i = 0; i < _firstItems.Length; i++) 359 | { 360 | ref var bucket = ref _firstItems[i]; 361 | if (doDispose) 362 | CacheAwareElement.ClearAndDispose(ref bucket, ref evictionStrategy); 363 | else 364 | CacheAwareElement.Clear(ref bucket, ref evictionStrategy); 365 | } 366 | } 367 | 368 | uint current = (uint)_itemBottomCounter; 369 | do 370 | { 371 | if (current < _itemTopCounter) 372 | { 373 | uint claim = current & _itemsMask; 374 | 375 | T inst = _items[claim].Value; 376 | if (inst != null && evictionStrategy.CanEvict(inst)) 377 | { 378 | // WARNING: In a absurdly fast loop this can still fail to get a proper, that is why 379 | // we still use a compare exchange operation instead of using the reference. 380 | if (inst == Interlocked.CompareExchange(ref _items[claim].Value, null, inst) && doDispose) 381 | { 382 | ((IDisposable)inst).Dispose(); 383 | } 384 | } 385 | } 386 | current++; 387 | } 388 | while (current < _itemTopCounter); 389 | } 390 | } 391 | 392 | public interface IEvictionStrategy where T : class 393 | { 394 | bool CanEvict(T item); 395 | } 396 | 397 | public struct AlwaysEvictStrategy : IEvictionStrategy where T : class 398 | { 399 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 400 | public bool CanEvict(T item) 401 | { 402 | return true; 403 | } 404 | } 405 | 406 | public struct NeverEvictStrategy : IEvictionStrategy where T : class 407 | { 408 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 409 | public bool CanEvict(T item) 410 | { 411 | return false; 412 | } 413 | } 414 | 415 | public interface IObjectLifecycle where T : class 416 | { 417 | T New(); 418 | void Reset(T value); 419 | } 420 | 421 | public struct NoResetSupport : IObjectLifecycle where T : class, new() 422 | { 423 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 424 | public T New() 425 | { 426 | return new T(); 427 | } 428 | 429 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 430 | public void Reset(T value) { } 431 | } 432 | 433 | public struct NoResetFactorySupport : IObjectLifecycle where T : class, new() 434 | { 435 | public delegate T Factory(); 436 | 437 | // factory is stored for the lifetime of the pool. We will call this only when pool needs to 438 | // expand. compared to "new T()", Func gives more flexibility to implementers and faster 439 | // than "new T()". 440 | private readonly Factory _factory; 441 | 442 | public NoResetFactorySupport(Factory factory) 443 | { 444 | this._factory = factory; 445 | } 446 | 447 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 448 | public T New() 449 | { 450 | return _factory(); 451 | } 452 | 453 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 454 | public void Reset(T value) { } 455 | } 456 | 457 | 458 | public interface IProcessAwareBehavior 459 | { 460 | int Buckets { get; } 461 | } 462 | 463 | public struct ThreadAwareBehavior : IProcessAwareBehavior 464 | { 465 | private readonly int _buckets; 466 | 467 | public int Buckets 468 | { 469 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 470 | get { return _buckets == 0 ? 128 : _buckets; } 471 | } 472 | 473 | public ThreadAwareBehavior(int buckets = 128) 474 | { 475 | _buckets = buckets; 476 | } 477 | } 478 | 479 | public struct NonThreadAwareBehavior : IProcessAwareBehavior 480 | { 481 | public int Buckets 482 | { 483 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 484 | get { return 0; } 485 | } 486 | } 487 | 488 | [HardwareCounters(HardwareCounter.InstructionRetired)] 489 | public class ObjectPoolBenchmark 490 | { 491 | public class ObjectToPool 492 | { 493 | public struct Behavior : IObjectLifecycle 494 | { 495 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 496 | public ObjectToPool New() 497 | { 498 | return new ObjectToPool(); 499 | } 500 | 501 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 502 | public void Reset(ObjectToPool value) { } 503 | } 504 | } 505 | 506 | private static readonly ObjectPool> _withoutFactory = new ObjectPool>(); 507 | private static readonly ObjectPool _withFactory = new ObjectPool(() => new ObjectToPool()); 508 | private static readonly ObjectPool _withSpecificNew = new ObjectPool(); 509 | 510 | [Benchmark] 511 | public ObjectToPool UsingFactory() 512 | { 513 | return _withFactory.Allocate(); 514 | } 515 | 516 | [Benchmark] 517 | public ObjectToPool UsingGenericNew() 518 | { 519 | return _withoutFactory.Allocate(); 520 | } 521 | 522 | [Benchmark] 523 | public ObjectToPool UsingSpecificNew() 524 | { 525 | return _withSpecificNew.Allocate(); 526 | } 527 | } 528 | 529 | } 530 | -------------------------------------------------------------------------------- /Course/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using BenchmarkDotNet.Running; 4 | 5 | namespace Course 6 | { 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | //new Inlining().SumNonInlined(); 12 | //new RefAllocation().StackByValue(); 13 | //new RefAllocation().StackByRef(); 14 | //new RefAllocation().HeapByReuse(); 15 | //new ConstantPropagation().SumNonInlined(); 16 | //new ConstantPropagation().SumInlined(); 17 | //new SwitchIf().If(); 18 | //new SwitchIf().WithGaps(); 19 | //new SwitchIf().WithoutGaps(); 20 | //new VirtualCall().NonVirtual(); 21 | //new VirtualCall().VirtualThis(); 22 | //new VirtualCall().VirtualDerived(); 23 | //new VirtualCall().VirtualInterface(); 24 | //new StructDeadCode().WithClass(); 25 | //new StructDeadCode().WithStruct(); 26 | //new ObjectPoolBenchmark().UsingFactory(); 27 | //new ObjectPoolBenchmark().UsingGenericNew(); 28 | //new ObjectPoolBenchmark().UsingSpecificNew(); 29 | 30 | BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly).Run(args); 31 | Console.WriteLine(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Course/RefAllocation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using BenchmarkDotNet.Attributes; 5 | using BenchmarkDotNet.Configs; 6 | using BenchmarkDotNet.Diagnosers; 7 | using BenchmarkDotNet.Environments; 8 | using BenchmarkDotNet.Jobs; 9 | 10 | namespace Course 11 | { 12 | [Config(typeof(Config))] 13 | [MemoryDiagnoser] 14 | [HardwareCounters(HardwareCounter.InstructionRetired)] 15 | public class RefAllocation 16 | { 17 | private class Config : ManualConfig 18 | { 19 | public Config() 20 | { 21 | // The same, using the .With() factory methods: 22 | Add( 23 | Job.Default 24 | .With(Platform.X64) 25 | .With(Jit.RyuJit) 26 | ); 27 | } 28 | } 29 | 30 | public struct Value 31 | { 32 | public long A; 33 | public long B; 34 | public long C; 35 | public long D; 36 | } 37 | 38 | public class Reference 39 | { 40 | public long A; 41 | public long B; 42 | public long C; 43 | public long D; 44 | } 45 | 46 | [MethodImpl(MethodImplOptions.NoInlining)] 47 | private void WorkByRef(long i, ref Value output) 48 | { 49 | output.A = i; 50 | output.B = i; 51 | output.C = i; 52 | output.D = i; 53 | } 54 | 55 | [Benchmark] 56 | public long StackByRef() 57 | { 58 | long result = 0; 59 | Value output = default(Value); 60 | for (long i = 0; i < 100000; i++) 61 | { 62 | WorkByRef(i, ref output); 63 | result += output.A; 64 | } 65 | return result; 66 | } 67 | 68 | [MethodImpl(MethodImplOptions.NoInlining)] 69 | private Value WorkByValue(int i) 70 | { 71 | return new Value { A = i, B = i, C = i, D = i }; 72 | } 73 | 74 | [Benchmark] 75 | public long StackByValue() 76 | { 77 | long result = 0; 78 | for (int i = 0; i < 100000; i++) 79 | { 80 | result += WorkByValue(i).A; 81 | } 82 | return result; 83 | } 84 | 85 | [MethodImpl(MethodImplOptions.NoInlining)] 86 | private Reference WorkByHeapConstruction(int i) 87 | { 88 | return new Reference { A = i, B = i, C = i, D = i }; 89 | } 90 | 91 | [Benchmark] 92 | public long HeapByConstruction() 93 | { 94 | long result = 0; 95 | for (int i = 0; i < 100000; i++) 96 | { 97 | result += WorkByHeapConstruction(i).A; 98 | } 99 | return result; 100 | } 101 | 102 | [MethodImpl(MethodImplOptions.NoInlining)] 103 | private void WorkByHeapReuse(long i, Reference output) 104 | { 105 | output.A = i; 106 | output.B = i; 107 | output.C = i; 108 | output.D = i; 109 | } 110 | 111 | [Benchmark] 112 | public long HeapByReuse() 113 | { 114 | long result = 0; 115 | var output = new Reference(); 116 | for (int i = 0; i < 100000; i++) 117 | { 118 | WorkByHeapReuse(i, output); 119 | result += output.A; 120 | } 121 | return result; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Course/StructDeadCode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Text; 5 | using BenchmarkDotNet.Attributes; 6 | 7 | namespace Course 8 | { 9 | public class StructDeadCode 10 | { 11 | public interface IMarker { } 12 | 13 | public struct AsStruct : IMarker { } 14 | public class AsClass : IMarker { } 15 | 16 | public StructDeadCode() 17 | { 18 | var rnd = new Random(); 19 | value = rnd.Next(); 20 | value2 = rnd.Next(); 21 | } 22 | 23 | public static class Generic 24 | { 25 | public static int Method(int i, int j) where T : IMarker 26 | { 27 | // This will be eliminated when T is an struct. 28 | if (typeof(T) == typeof(AsClass)) 29 | { 30 | return i; 31 | } 32 | 33 | return j; 34 | } 35 | } 36 | 37 | private readonly int value; 38 | private readonly int value2; 39 | 40 | [Benchmark] 41 | [MethodImpl(MethodImplOptions.NoInlining)] 42 | public int WithStruct() 43 | { 44 | return Generic.Method(value, value2); 45 | } 46 | 47 | [Benchmark] 48 | [MethodImpl(MethodImplOptions.NoInlining)] 49 | public int WithClass() 50 | { 51 | return Generic.Method(value, value2); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Course/SwitchIf.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using BenchmarkDotNet.Attributes; 5 | using BenchmarkDotNet.Diagnosers; 6 | 7 | namespace Course 8 | { 9 | [HardwareCounters(HardwareCounter.InstructionRetired)] 10 | public class SwitchIf 11 | { 12 | private int x = 7, y = 7000; 13 | 14 | [Benchmark] 15 | public int If() 16 | { 17 | if (x == 0) 18 | return 0; 19 | if (x == 1) 20 | return 1; 21 | if (x == 2) 22 | return 2; 23 | if (x == 3) 24 | return 3; 25 | if (x == 4) 26 | return 4; 27 | if (x == 5) 28 | return 5; 29 | if (x == 6) 30 | return 6; 31 | if (x == 7) 32 | return 7; 33 | return -1; 34 | } 35 | 36 | [Benchmark] 37 | public int WithoutGaps() 38 | { 39 | switch (x) 40 | { 41 | case 0: 42 | return 0; 43 | case 1: 44 | return 1; 45 | case 2: 46 | return 2; 47 | case 3: 48 | return 3; 49 | case 4: 50 | return 4; 51 | case 5: 52 | return 5; 53 | case 6: 54 | return 6; 55 | case 7: 56 | return 7; 57 | default: 58 | return -1; 59 | } 60 | } 61 | 62 | [Benchmark] 63 | public int WithGaps() 64 | { 65 | switch (y) 66 | { 67 | case 0: 68 | return 0; 69 | case 1000: 70 | return 1000; 71 | case 2000: 72 | return 2000; 73 | case 3000: 74 | return 3000; 75 | case 4000: 76 | return 4000; 77 | case 5000: 78 | return 5000; 79 | case 6000: 80 | return 6000; 81 | case 7000: 82 | return 7000; 83 | default: 84 | return -1; 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Course/VirtualCall.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Text; 5 | using BenchmarkDotNet.Attributes; 6 | 7 | namespace Course 8 | { 9 | public interface IInterface 10 | { 11 | int Virtual(int i); 12 | } 13 | 14 | public class Parent : IInterface 15 | { 16 | public int NonVirtual(int i) 17 | { 18 | return i; 19 | } 20 | 21 | public virtual int Virtual(int i) 22 | { 23 | return i; 24 | } 25 | } 26 | 27 | public class Child : Parent 28 | { 29 | public override int Virtual(int i) 30 | { 31 | return i; 32 | } 33 | } 34 | 35 | public class VirtualCall 36 | { 37 | private readonly Parent child = new Child(); 38 | private readonly Parent parent = new Parent(); 39 | private readonly IInterface @interface = new Child(); 40 | 41 | [Benchmark] 42 | public int NonVirtual() 43 | { 44 | int result = 0; 45 | for (int i = 0; i < 1000; i++) 46 | result += parent.NonVirtual(i); 47 | return result; 48 | } 49 | 50 | [Benchmark] 51 | public int VirtualThis() 52 | { 53 | int result = 0; 54 | for (int i = 0; i < 1000; i++) 55 | result += parent.Virtual(i); 56 | return result; 57 | } 58 | 59 | [Benchmark] 60 | public int VirtualDerived() 61 | { 62 | int result = 0; 63 | for (int i = 0; i < 1000; i++) 64 | result += child.Virtual(i); 65 | return result; 66 | } 67 | 68 | [Benchmark] 69 | public int VirtualInterface() 70 | { 71 | int result = 0; 72 | for (int i = 0; i < 1000; i++) 73 | result += @interface.Virtual(i); 74 | return result; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Workshop.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Workshop", "Workshop\Workshop.csproj", "{8EDEDF2A-B804-461C-BB24-A6FA0BAC2E7F}" 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 | {8EDEDF2A-B804-461C-BB24-A6FA0BAC2E7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {8EDEDF2A-B804-461C-BB24-A6FA0BAC2E7F}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {8EDEDF2A-B804-461C-BB24-A6FA0BAC2E7F}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {8EDEDF2A-B804-461C-BB24-A6FA0BAC2E7F}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(CodealikeProperties) = postSolution 23 | SolutionGuid = 8a6cba7a-8a67-42f8-9e55-b298ad98d532 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Workshop/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Workshop/Dictionary.Base.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Workshop 8 | { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /Workshop/Dictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Workshop 8 | { 9 | public class DictionaryBenchmark 10 | { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Workshop/LZ4.Compression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using BenchmarkDotNet.Analysers; 7 | using BenchmarkDotNet.Attributes; 8 | using BenchmarkDotNet.Columns; 9 | using BenchmarkDotNet.Configs; 10 | using BenchmarkDotNet.Environments; 11 | using BenchmarkDotNet.Exporters; 12 | using BenchmarkDotNet.Jobs; 13 | using BenchmarkDotNet.Validators; 14 | 15 | namespace Workshop 16 | { 17 | [Config(typeof(Config))] 18 | public unsafe class LZ4CompressionBenchmark 19 | { 20 | private class Config : ManualConfig 21 | { 22 | public Config() 23 | { 24 | Add(new Job(RunMode.Dry) 25 | { 26 | Env = 27 | { 28 | Runtime = Runtime.Core, 29 | Platform = Platform.X64, 30 | Jit = Jit.RyuJit 31 | } 32 | }); 33 | 34 | // Exporters for data 35 | Add(GetExporters().ToArray()); 36 | // Generate plots using R if %R_HOME% is correctly set 37 | Add(RPlotExporter.Default); 38 | 39 | Add(StatisticColumn.AllStatistics); 40 | 41 | Add(BaselineValidator.FailOnError); 42 | Add(JitOptimizationsValidator.FailOnError); 43 | // TODO: Uncomment next line. See https://github.com/PerfDotNet/BenchmarkDotNet/issues/272 44 | //Add(ExecutionValidator.FailOnError); 45 | Add(EnvironmentAnalyser.Default); 46 | } 47 | } 48 | 49 | 50 | private byte[] lowBitRandomInput = new byte[Constants.Size.Megabyte]; 51 | private byte[] lowBitRandomOutput = new byte[Constants.Size.Megabyte]; 52 | private byte[] lowBitEncodedOutput; 53 | 54 | private byte[] highRepeatRandomInput = new byte[Constants.Size.Megabyte]; 55 | private byte[] highRepeatRandomOutput = new byte[Constants.Size.Megabyte]; 56 | private byte[] highRepeatEncodedOutput; 57 | 58 | [Setup] 59 | public void Setup() 60 | { 61 | { 62 | int threshold = 1 << 4; 63 | var rnd = new Random(1000); 64 | for (int i = 0; i < lowBitRandomInput.Length; i++) 65 | lowBitRandomInput[i] = (byte)(rnd.Next() % threshold); 66 | 67 | lowBitEncodedOutput = new byte[LZ4.MaximumOutputLength(lowBitRandomInput.Length)]; 68 | } 69 | 70 | { 71 | var main = new Random(1000); 72 | 73 | int i = 0; 74 | while (i < highRepeatRandomInput.Length) 75 | { 76 | int sequenceNumber = main.Next(20); 77 | int sequenceLength = Math.Min(main.Next(128), highRepeatRandomInput.Length - i); 78 | 79 | var rnd = new Random(sequenceNumber); 80 | for (int j = 0; j < sequenceLength; j++, i++) 81 | highRepeatRandomInput[i] = (byte)(rnd.Next() % 255); 82 | } 83 | 84 | highRepeatEncodedOutput = new byte[LZ4.MaximumOutputLength(highRepeatRandomInput.Length)]; 85 | } 86 | } 87 | 88 | [Benchmark] 89 | public void LowBitRandom() 90 | { 91 | fixed (byte* inputPtr = highRepeatRandomInput) 92 | fixed (byte* encodedOutputPtr = highRepeatEncodedOutput) 93 | fixed (byte* outputPtr = highRepeatRandomOutput) 94 | { 95 | int compressedSize = LZ4.Encode64(inputPtr, encodedOutputPtr, highRepeatRandomInput.Length, highRepeatEncodedOutput.Length); 96 | int uncompressedSize = LZ4.Decode64(encodedOutputPtr, compressedSize, outputPtr, highRepeatRandomInput.Length, true); 97 | } 98 | } 99 | 100 | [Benchmark] 101 | public void HighRepetition() 102 | { 103 | fixed (byte* inputPtr = lowBitRandomInput) 104 | fixed (byte* encodedOutputPtr = lowBitEncodedOutput) 105 | fixed (byte* outputPtr = lowBitRandomOutput) 106 | { 107 | int compressedSize = LZ4.Encode64(inputPtr, encodedOutputPtr, lowBitRandomInput.Length, lowBitEncodedOutput.Length); 108 | int uncompressedSize = LZ4.Decode64(encodedOutputPtr, compressedSize, outputPtr, lowBitRandomInput.Length, true); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Workshop/LZ4.Decompression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using BenchmarkDotNet.Analysers; 5 | using BenchmarkDotNet.Attributes; 6 | using BenchmarkDotNet.Columns; 7 | using BenchmarkDotNet.Configs; 8 | using BenchmarkDotNet.Environments; 9 | using BenchmarkDotNet.Exporters; 10 | using BenchmarkDotNet.Jobs; 11 | using BenchmarkDotNet.Validators; 12 | using DotNetCross.Memory; 13 | 14 | namespace Workshop 15 | { 16 | [Config(typeof(Config))] 17 | public unsafe class LZ4DecompressionBenchmark 18 | { 19 | private class Config : ManualConfig 20 | { 21 | public Config() 22 | { 23 | Add(new Job(RunMode.Dry) 24 | { 25 | Env = 26 | { 27 | Runtime = Runtime.Core, 28 | Platform = Platform.X64, 29 | Jit = Jit.RyuJit 30 | } 31 | }); 32 | 33 | // Exporters for data 34 | Add(GetExporters().ToArray()); 35 | // Generate plots using R if %R_HOME% is correctly set 36 | Add(RPlotExporter.Default); 37 | 38 | Add(StatisticColumn.AllStatistics); 39 | 40 | Add(BaselineValidator.FailOnError); 41 | Add(JitOptimizationsValidator.FailOnError); 42 | // TODO: Uncomment next line. See https://github.com/PerfDotNet/BenchmarkDotNet/issues/272 43 | //Add(ExecutionValidator.FailOnError); 44 | Add(EnvironmentAnalyser.Default); 45 | } 46 | } 47 | 48 | private const int NumberOfOperations = 10000; 49 | 50 | [Params(0)] 51 | public int RandomSeed { get; set; } = 0; 52 | 53 | /// 54 | /// Number of precomputed sequences to use when generating data 55 | /// 56 | [Params(100)] 57 | public int NumberOfSequences { get; set; } = 30; 58 | 59 | /// 60 | /// Maximum length of a sequence piece. Sequence length will be 61 | /// uniformly distributed between 1 and this length. 62 | /// 63 | [Params(8, 16, 32, 64)] 64 | public int GeneratedSequenceMaximumLength { get; set; } = 64; 65 | 66 | 67 | // Notice that length of actual values will be uniformly distributed 68 | // between 0 and MaximumLength. Hence, we have that the average array 69 | // length will be MaximumLength/2. 70 | [Params(1024, 2048)] 71 | public int DataMaximumLength { get; set; } = 1024; 72 | 73 | /// 74 | /// Probability of using a predefined sequence when generating data. 75 | /// 76 | [Params(0.5)] 77 | public double SequenceUsageProbability { get; set; } = 0.5; 78 | 79 | /// 80 | /// Probability of repeating any given sequence when generating data 81 | /// 82 | [Params(0.5, 1.0)] 83 | public double SequenceRepetitionProbability { get; set; } = 0.7; 84 | 85 | /// 86 | /// Probability of flipping one or more bytes in a sequence when 87 | /// generating data 88 | /// 89 | [Params(0.8)] 90 | public double DataFlipProbability { get; set; } = 0.8; 91 | 92 | /// 93 | /// Probability that current byte will be flipped when flipping 94 | /// a sequence in data generation 95 | /// 96 | [Params(0.1)] 97 | public double DataByteFlipProbability { get; set; } = 0.1; 98 | 99 | private List> _buffers; 100 | private byte[] _lz4Buffer; 101 | 102 | [Setup] 103 | public void Setup() 104 | { 105 | var generator = new Random(RandomSeed); 106 | _buffers = new List>(); 107 | 108 | // Generate the precomputed sequences to be used when generating data. 109 | var sequences = new List(); 110 | for (int i = 0; i < NumberOfSequences; i++) 111 | { 112 | int length = generator.Next(1, GeneratedSequenceMaximumLength); 113 | var sequence = new byte[length]; 114 | generator.NextBytes(sequence); 115 | sequences.Add(sequence); 116 | } 117 | 118 | // Compute the length of the maximum output data. This is an upper bound 119 | // to be able to always use the same buffer for decompression. 120 | int maximumOutputLength = (int)LZ4.MaximumOutputLength(DataMaximumLength); 121 | _lz4Buffer = new byte[maximumOutputLength]; 122 | 123 | var buffer = new byte[DataMaximumLength]; 124 | for (int i = 0; i < NumberOfOperations; i++) 125 | { 126 | var generatedDataLength = generator.Next(DataMaximumLength); 127 | List usedSequences = new List(); 128 | for (var j = 0; j < generatedDataLength; j++) 129 | { 130 | bool useSequence = generator.NextDouble() < SequenceUsageProbability; 131 | if (sequences.Count > 0 && useSequence) 132 | { 133 | byte[] sequence; 134 | bool repeatSequence = generator.NextDouble() < SequenceRepetitionProbability; 135 | if (repeatSequence && usedSequences.Count > 0) 136 | { 137 | int index = generator.Next(usedSequences.Count); 138 | sequence = sequences[usedSequences[index]]; 139 | } 140 | else 141 | { 142 | int index = generator.Next(sequences.Count); 143 | sequence = sequences[index]; 144 | usedSequences.Add(index); 145 | } 146 | 147 | fixed (byte* bufferPtr = &buffer[j]) 148 | fixed (byte* sequencePtr = sequence) 149 | { 150 | int amount = Math.Min(sequence.Length, generatedDataLength - j); 151 | 152 | Unsafe.CopyBlock(bufferPtr, sequencePtr, (uint)amount); 153 | j += amount; 154 | } 155 | } 156 | else 157 | { 158 | var spontaneousSequenceLength = Math.Min(generator.Next(GeneratedSequenceMaximumLength), generatedDataLength - j); 159 | for (int k = 0; k < spontaneousSequenceLength; k++, j++) 160 | { 161 | buffer[j] = (byte)generator.Next(256); 162 | } 163 | } 164 | } 165 | 166 | // Flip bytes on the generated sequence, as required 167 | bool flipGeneratedSequence = generator.NextDouble() < DataFlipProbability; 168 | if (flipGeneratedSequence) 169 | { 170 | for (var j = 0; j < generatedDataLength; j++) 171 | { 172 | bool flipGeneratedByte = generator.NextDouble() < DataByteFlipProbability; 173 | if (flipGeneratedByte) 174 | buffer[j] ^= (byte)generator.Next(256); 175 | } 176 | } 177 | 178 | // Calculate compression size and store the generated data 179 | fixed (byte* bufferPtr = buffer) 180 | fixed (byte* lz4buffer = _lz4Buffer) 181 | { 182 | int compressedSize = LZ4.Encode64(bufferPtr, lz4buffer, generatedDataLength, _lz4Buffer.Length); 183 | 184 | byte[] unmanagedBuffer = new byte[compressedSize]; 185 | fixed (byte* ptr = unmanagedBuffer) 186 | { 187 | Unsafe.CopyBlock(ptr, lz4buffer, (uint) compressedSize); 188 | } 189 | 190 | 191 | _buffers.Add(new Tuple(unmanagedBuffer, generatedDataLength)); 192 | } 193 | } 194 | } 195 | 196 | [Cleanup] 197 | public void Cleanup() 198 | { 199 | _buffers.Clear(); 200 | } 201 | 202 | [Benchmark(OperationsPerInvoke = NumberOfOperations)] 203 | public void DecompressionBenchmark() 204 | { 205 | foreach (var tuple in _buffers) 206 | { 207 | fixed (byte* ptr = tuple.Item1) 208 | fixed (byte* buffer = _lz4Buffer) 209 | { 210 | LZ4.Decode64(ptr, tuple.Item1.Length, buffer, tuple.Item2, true); 211 | } 212 | } 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /Workshop/LZ4.Support.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Workshop 8 | { 9 | internal static class Constants 10 | { 11 | internal static class Size 12 | { 13 | public const int Kilobyte = 1024; 14 | public const int Megabyte = 1024 * Kilobyte; 15 | public const int Gigabyte = 1024 * Megabyte; 16 | public const long Terabyte = 1024 * (long)Gigabyte; 17 | } 18 | } 19 | 20 | public static class Bits 21 | { 22 | 23 | private static readonly byte[] DeBruijnBytePos64 = 24 | { 25 | 0, 0, 0, 0, 0, 1, 1, 2, 0, 3, 1, 3, 1, 4, 2, 7, 0, 2, 3, 6, 1, 5, 3, 5, 1, 3, 4, 4, 2, 5, 6, 7, 26 | 7, 0, 1, 2, 3, 3, 4, 6, 2, 6, 5, 5, 3, 4, 5, 6, 7, 1, 2, 4, 6, 4, 4, 5, 7, 2, 6, 5, 7, 6, 7, 7 27 | }; 28 | 29 | private static readonly byte[] DeBruijnBytePos32 = 30 | { 31 | 0, 0, 3, 0, 3, 1, 3, 0, 3, 2, 2, 1, 3, 2, 0, 1, 3, 3, 1, 2, 2, 2, 2, 0, 3, 1, 2, 0, 1, 0, 1, 1 32 | }; 33 | 34 | 35 | public static int TrailingZeroes(ulong value) 36 | { 37 | return DeBruijnBytePos64[((value & (ulong)(-(long)value)) * 0x0218A392CDABBD3FUL) >> 58]; 38 | } 39 | 40 | 41 | public static int TrailingZeroes(long value) 42 | { 43 | return DeBruijnBytePos64[((ulong)(value & -value) * 0x0218A392CDABBD3FUL) >> 58]; 44 | } 45 | 46 | public static int TrailingZeroes(uint value) 47 | { 48 | return DeBruijnBytePos32[((value & (uint)(-(int)value)) * 0x077CB531U) >> 27]; 49 | } 50 | 51 | 52 | public static int TrailingZeroes(int value) 53 | { 54 | return DeBruijnBytePos32[((uint)(value & -value) * 0x077CB531U) >> 27]; 55 | } 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Workshop/LZ4.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using DotNetCross; 8 | using DotNetCross.Memory; 9 | 10 | namespace Workshop 11 | { 12 | public unsafe class LZ4 : IDisposable 13 | { 14 | public const int ACCELERATION_DEFAULT = 1; 15 | 16 | private const int COPYLENGTH = 8; 17 | private const int LASTLITERALS = 5; 18 | private const int MINMATCH = 4; 19 | private const int MFLIMIT = COPYLENGTH + MINMATCH; 20 | private const int LZ4_minLength = MFLIMIT + 1; 21 | 22 | private const int MAXD_LOG = 16; 23 | private const int MAX_DISTANCE = ((1 << MAXD_LOG) - 1); 24 | 25 | private const int LZ4_64Klimit = (64 * 1024) + (MFLIMIT - 1); 26 | private const int LZ4_skipTrigger = 6; // Increase this value ==> compression run slower on incompressible data 27 | 28 | private const byte ML_BITS = 4; 29 | private const byte ML_MASK = ((1 << ML_BITS) - 1); 30 | private const byte RUN_BITS = (8 - ML_BITS); 31 | private const byte RUN_MASK = ((1 << RUN_BITS) - 1); 32 | 33 | private const uint LZ4_MAX_INPUT_SIZE = 0x7E000000; /* 2 113 929 216 bytes */ 34 | 35 | /// 36 | /// LZ4_MEMORY_USAGE : 37 | /// Memory usage formula : N->2^N Bytes(examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.) 38 | /// Increasing memory usage improves compression ratio 39 | /// Reduced memory usage can improve speed, due to cache effect 40 | /// Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache 41 | /// 42 | private const int LZ4_MEMORY_USAGE = 14; 43 | private const int LZ4_HASHLOG = LZ4_MEMORY_USAGE - 2; 44 | private const int HASH_SIZE_U32 = 1 << LZ4_HASHLOG; 45 | 46 | private enum LimitedOutput { NotLimited = 0, LimitedOutput = 1 }; 47 | private enum TableType { ByU32, ByU16 }; 48 | private enum DictionaryType { NoDict = 0, WithPrefix64K, UsingExtDict }; 49 | private enum DictionaryIssue { NoDictIssue = 0, DictSmall }; 50 | private enum EndCondition { EndOnOutputSize = 0, EndOnInputSize = 1 }; 51 | private enum EarlyEnd { Full = 0, Partial = 1 }; 52 | 53 | [StructLayout(LayoutKind.Sequential)] 54 | protected struct LZ4_stream_t_internal 55 | { 56 | public fixed uint hashTable[HASH_SIZE_U32]; 57 | public uint currentOffset; 58 | public uint initCheck; 59 | public byte* dictionary; 60 | public uint dictSize; 61 | } 62 | 63 | public static int Encode64( 64 | byte* input, 65 | byte* output, 66 | int inputLength, 67 | int outputLength, 68 | int acceleration = ACCELERATION_DEFAULT) 69 | { 70 | if (acceleration < 1) 71 | acceleration = ACCELERATION_DEFAULT; 72 | 73 | LZ4_stream_t_internal ctx; 74 | 75 | Unsafe.InitBlock((byte*)&ctx, 0, HASH_SIZE_U32 * sizeof(uint)); 76 | 77 | if (outputLength >= MaximumOutputLength(inputLength)) 78 | { 79 | if (inputLength < LZ4_64Klimit) 80 | return LZ4_compress_generic(&ctx, input, output, inputLength, 0, LimitedOutput.NotLimited, TableType.ByU16, DictionaryType.NoDict, DictionaryIssue.NoDictIssue, acceleration); 81 | else 82 | return LZ4_compress_generic(&ctx, input, output, inputLength, 0, LimitedOutput.NotLimited, TableType.ByU32, DictionaryType.NoDict, DictionaryIssue.NoDictIssue, acceleration); 83 | } 84 | else 85 | { 86 | if (inputLength < LZ4_64Klimit) 87 | return LZ4_compress_generic(&ctx, input, output, inputLength, outputLength, LimitedOutput.LimitedOutput, TableType.ByU16, DictionaryType.NoDict, DictionaryIssue.NoDictIssue, acceleration); 88 | else 89 | return LZ4_compress_generic(&ctx, input, output, inputLength, outputLength, LimitedOutput.LimitedOutput, TableType.ByU32, DictionaryType.NoDict, DictionaryIssue.NoDictIssue, acceleration); 90 | } 91 | } 92 | 93 | /// Gets maximum the length of the output. 94 | /// Length of the input. 95 | /// Maximum number of bytes needed for compressed buffer. 96 | public static int MaximumOutputLength(int size) 97 | { 98 | return size > LZ4_MAX_INPUT_SIZE ? 0 : size + (size / 255) + 16; 99 | } 100 | 101 | private static int LZ4_compress_generic(LZ4_stream_t_internal* dictPtr, byte* source, byte* dest, int inputSize, int maxOutputSize, LimitedOutput outputLimited, TableType tableType, DictionaryType dict, DictionaryIssue dictIssue, int acceleration) 102 | { 103 | byte* ip = source; 104 | byte* lowRefLimit = ip - dictPtr->dictSize; 105 | byte* dictionary = dictPtr->dictionary; 106 | byte* dictEnd = dictionary + dictPtr->dictSize; 107 | 108 | long dictDelta = (long)dictEnd - (long)source; 109 | 110 | byte* anchor = source; 111 | byte* iend = ip + inputSize; 112 | byte* mflimit = iend - MFLIMIT; 113 | byte* matchlimit = iend - LASTLITERALS; 114 | 115 | byte* op = (byte*)dest; 116 | byte* olimit = op + maxOutputSize; 117 | 118 | uint forwardH; 119 | long refDelta = 0; 120 | 121 | // Init conditions 122 | if (inputSize > LZ4_MAX_INPUT_SIZE) return 0; // Unsupported input size, too large (or negative) 123 | 124 | byte* @base; 125 | byte* lowLimit; 126 | switch (dict) 127 | { 128 | default: 129 | case DictionaryType.NoDict: 130 | @base = source; 131 | lowLimit = source; 132 | break; 133 | case DictionaryType.WithPrefix64K: 134 | @base = source - dictPtr->currentOffset; 135 | lowLimit = source - dictPtr->dictSize; 136 | break; 137 | case DictionaryType.UsingExtDict: 138 | @base = source - dictPtr->currentOffset; 139 | lowLimit = source; 140 | break; 141 | } 142 | 143 | if ((tableType == TableType.ByU16) && (inputSize >= LZ4_64Klimit)) // Size too large (not within 64K limit) 144 | return 0; 145 | 146 | if (inputSize < LZ4_minLength) // Input too small, no compression (all literals) 147 | goto _last_literals; 148 | 149 | // First Byte 150 | LZ4_putPosition(ip, dictPtr, tableType, @base); 151 | ip++; 152 | forwardH = LZ4_hashPosition(ip, tableType); 153 | 154 | // Main Loop 155 | 156 | for (;;) 157 | { 158 | byte* match; 159 | { 160 | byte* forwardIp = ip; 161 | 162 | int step = 1; 163 | int searchMatchNb = acceleration << LZ4_skipTrigger; 164 | 165 | do 166 | { 167 | uint h = forwardH; 168 | ip = forwardIp; 169 | forwardIp += step; 170 | step = (searchMatchNb++ >> LZ4_skipTrigger); 171 | 172 | if (forwardIp > mflimit) 173 | goto _last_literals; 174 | 175 | match = LZ4_getPositionOnHash(h, dictPtr, tableType, @base); 176 | if (dict == DictionaryType.UsingExtDict) 177 | { 178 | if (match < source) 179 | { 180 | refDelta = dictDelta; 181 | lowLimit = dictionary; 182 | } 183 | else 184 | { 185 | refDelta = 0; 186 | lowLimit = source; 187 | } 188 | } 189 | 190 | forwardH = LZ4_hashPosition(forwardIp, tableType); 191 | LZ4_putPositionOnHash(ip, h, dictPtr, tableType, @base); 192 | } 193 | while (((dictIssue == DictionaryIssue.DictSmall) ? (match < lowRefLimit) : false) || 194 | ((tableType == TableType.ByU16) ? false : (match + MAX_DISTANCE < ip)) || 195 | (*(uint*)(match + refDelta) != *((uint*)ip))); 196 | } 197 | 198 | // Catch up 199 | while ((ip > anchor) && (match + refDelta > lowLimit) && (ip[-1] == match[refDelta - 1])) 200 | { 201 | ip--; 202 | match--; 203 | } 204 | 205 | 206 | // Encode Literal length 207 | byte* token; 208 | { 209 | int litLength = (int)(ip - anchor); 210 | token = op++; 211 | 212 | if ((outputLimited == LimitedOutput.LimitedOutput) && (op + litLength + (2 + 1 + LASTLITERALS) + (litLength / 255) > olimit)) 213 | return 0; /* Check output limit */ 214 | 215 | if (litLength >= RUN_MASK) 216 | { 217 | int len = litLength - RUN_MASK; 218 | *token = RUN_MASK << ML_BITS; 219 | 220 | for (; len >= 255; len -= 255) 221 | *op++ = 255; 222 | 223 | *op++ = (byte)len; 224 | } 225 | else 226 | { 227 | *token = (byte)(litLength << ML_BITS); 228 | } 229 | 230 | /* Copy Literals */ 231 | WildCopy(op, anchor, op + litLength); 232 | op += litLength; 233 | } 234 | 235 | _next_match: 236 | 237 | // Encode Offset 238 | *((ushort*)op) = (ushort)(ip - match); 239 | op += sizeof(ushort); 240 | 241 | // Encode MatchLength 242 | { 243 | int matchLength; 244 | 245 | if ((dict == DictionaryType.UsingExtDict) && (lowLimit == dictionary)) 246 | { 247 | match += refDelta; 248 | 249 | byte* limit = ip + (dictEnd - match); 250 | if (limit > matchlimit) limit = matchlimit; 251 | matchLength = LZ4_count(ip + MINMATCH, match + MINMATCH, limit); 252 | ip += MINMATCH + matchLength; 253 | if (ip == limit) 254 | { 255 | int more = LZ4_count(ip, source, matchlimit); 256 | matchLength += more; 257 | ip += more; 258 | } 259 | } 260 | else 261 | { 262 | matchLength = LZ4_count(ip + MINMATCH, match + MINMATCH, matchlimit); 263 | ip += MINMATCH + matchLength; 264 | } 265 | 266 | if ((outputLimited == LimitedOutput.LimitedOutput) && ((op + (1 + LASTLITERALS) + (matchLength >> 8)) > olimit)) 267 | return 0; /* Check output limit */ 268 | 269 | if (matchLength >= ML_MASK) 270 | { 271 | *token += ML_MASK; 272 | matchLength -= ML_MASK; 273 | 274 | for (; matchLength >= 510; matchLength -= 510) 275 | { 276 | *op++ = 255; 277 | *op++ = 255; 278 | } 279 | 280 | if (matchLength >= 255) 281 | { 282 | matchLength -= 255; 283 | *op++ = 255; 284 | } 285 | 286 | *op++ = (byte)matchLength; 287 | } 288 | else 289 | { 290 | *token += (byte)(matchLength); 291 | } 292 | } 293 | 294 | 295 | anchor = ip; 296 | 297 | // Test end of chunk 298 | if (ip > mflimit) break; 299 | 300 | // Fill table 301 | LZ4_putPosition(ip - 2, dictPtr, tableType, @base); 302 | 303 | /* Test next position */ 304 | match = LZ4_getPosition(ip, dictPtr, tableType, @base); 305 | if (dict == DictionaryType.UsingExtDict) 306 | { 307 | if (match < source) 308 | { 309 | refDelta = dictDelta; 310 | lowLimit = dictionary; 311 | } 312 | else 313 | { 314 | refDelta = 0; 315 | lowLimit = source; 316 | } 317 | } 318 | 319 | LZ4_putPosition(ip, dictPtr, tableType, @base); 320 | if (((dictIssue == DictionaryIssue.DictSmall) ? (match >= lowRefLimit) : true) && (match + MAX_DISTANCE >= ip) && (*(uint*)(match + refDelta) == *(uint*)(ip))) 321 | { 322 | token = op++; *token = 0; 323 | goto _next_match; 324 | } 325 | 326 | /* Prepare next loop */ 327 | forwardH = LZ4_hashPosition(++ip, tableType); 328 | } 329 | 330 | _last_literals: 331 | 332 | /* Encode Last Literals */ 333 | { 334 | int lastRun = (int)(iend - anchor); 335 | if ((outputLimited == LimitedOutput.LimitedOutput) && ((op - dest) + lastRun + 1 + ((lastRun + 255 - RUN_MASK) / 255) > maxOutputSize)) 336 | return 0; // Check output limit; 337 | 338 | if (lastRun >= RUN_MASK) 339 | { 340 | int accumulator = lastRun - RUN_MASK; 341 | *op++ = RUN_MASK << ML_BITS; 342 | 343 | for (; accumulator >= 255; accumulator -= 255) 344 | *op++ = 255; 345 | 346 | *op++ = (byte)accumulator; 347 | } 348 | else 349 | { 350 | *op++ = (byte)(lastRun << ML_BITS); 351 | } 352 | 353 | Unsafe.CopyBlock(op, anchor, (uint)lastRun); 354 | op += lastRun; 355 | } 356 | 357 | return (int)(op - dest); 358 | } 359 | 360 | 361 | 362 | private static int LZ4_count(byte* pIn, byte* pMatch, byte* pInLimit) 363 | { 364 | byte* pStart = pIn; 365 | 366 | int i = 0; 367 | while (pIn < pInLimit - (sizeof(ulong) - 1)) 368 | { 369 | ulong diff = *((ulong*)pMatch) ^ *((ulong*)pIn); 370 | if (diff == 0) 371 | { 372 | pIn += sizeof(ulong); 373 | pMatch += sizeof(ulong); 374 | i++; 375 | continue; 376 | } 377 | 378 | pIn += Bits.TrailingZeroes(diff); 379 | return (int)(pIn - pStart); 380 | } 381 | 382 | if ((pIn < (pInLimit - 3)) && (*((uint*)pMatch) == *((uint*)(pIn)))) { pIn += sizeof(uint); pMatch += sizeof(uint); } 383 | if ((pIn < (pInLimit - 1)) && (*((ushort*)pMatch) == *((ushort*)pIn))) { pIn += sizeof(ushort); pMatch += sizeof(ushort); } 384 | if ((pIn < pInLimit) && (*pMatch == *pIn)) pIn++; 385 | 386 | return (int)(pIn - pStart); 387 | } 388 | 389 | private static void LZ4_putPosition(byte* p, LZ4_stream_t_internal* ctx, TableType tableType, byte* srcBase) 390 | { 391 | uint h = LZ4_hashPosition(p, tableType); 392 | LZ4_putPositionOnHash(p, h, ctx, tableType, srcBase); 393 | } 394 | 395 | private static byte* LZ4_getPosition(byte* p, LZ4_stream_t_internal* ctx, TableType tableType, byte* srcBase) 396 | { 397 | uint h = LZ4_hashPosition(p, tableType); 398 | return LZ4_getPositionOnHash(h, ctx, tableType, srcBase); 399 | } 400 | 401 | private static void LZ4_putPositionOnHash(byte* p, uint h, LZ4_stream_t_internal* ctx, TableType tableType, byte* srcBase) 402 | { 403 | if (tableType == TableType.ByU32) 404 | ctx->hashTable[h] = (uint)(p - srcBase); 405 | else 406 | ((ushort*)ctx->hashTable)[h] = (ushort)(p - srcBase); 407 | } 408 | 409 | private static byte* LZ4_getPositionOnHash(uint h, LZ4_stream_t_internal* ctx, TableType tableType, byte* srcBase) 410 | { 411 | if (tableType == TableType.ByU32) 412 | return srcBase + ctx->hashTable[h]; 413 | else 414 | return srcBase + ((ushort*)ctx->hashTable)[h]; 415 | } 416 | 417 | private static uint LZ4_hashPosition(byte* sequence, TableType tableType) 418 | { 419 | ulong value = *((ulong*)sequence); 420 | return LZ4_hashSequence64(value, tableType); 421 | } 422 | 423 | //private static uint LZ4_hashPosition(byte* sequence, TableType tableType) 424 | //{ 425 | // uint value = *((uint*)sequence); 426 | // return LZ4_hashSequence32(value, tableType); 427 | //} 428 | 429 | private const ulong prime5bytes = 889523592379UL; 430 | private static uint LZ4_hashSequence64(ulong sequence, TableType tableType) 431 | { 432 | int hashLog = (tableType == TableType.ByU16) ? LZ4_HASHLOG + 1 : LZ4_HASHLOG; 433 | int hashMask = (1 << hashLog) - 1; 434 | 435 | int value = (int)(sequence * prime5bytes >> (40 - hashLog)); 436 | return (uint)(value & hashMask); 437 | } 438 | 439 | private static uint LZ4_hashSequence32(uint sequence, TableType tableType) 440 | { 441 | if (tableType == TableType.ByU16) 442 | return (((sequence) * 2654435761U) >> ((MINMATCH * 8) - (LZ4_HASHLOG + 1))); 443 | else 444 | return (((sequence) * 2654435761U) >> ((MINMATCH * 8) - LZ4_HASHLOG)); 445 | } 446 | 447 | public static int Decode64( 448 | byte* input, 449 | int inputLength, 450 | byte* output, 451 | int outputLength, 452 | bool knownOutputLength) 453 | { 454 | if (knownOutputLength) 455 | { 456 | var length = LZ4_decompress_generic(input, output, inputLength, outputLength, EndCondition.EndOnInputSize, EarlyEnd.Full, 0, DictionaryType.NoDict, output, null, 0); 457 | if (length != outputLength) 458 | throw new ArgumentException("LZ4 block is corrupted, or invalid length has been given."); 459 | return outputLength; 460 | } 461 | else 462 | { 463 | var length = LZ4_decompress_generic(input, output, inputLength, outputLength, EndCondition.EndOnOutputSize, EarlyEnd.Full, 0, DictionaryType.WithPrefix64K, output - (64 * Constants.Size.Kilobyte), null, 64 * Constants.Size.Kilobyte); 464 | if (length < 0) 465 | throw new ArgumentException("LZ4 block is corrupted, or invalid length has been given."); 466 | return length; 467 | } 468 | } 469 | 470 | private static readonly int[] dec32table = new int[] { 4, 1, 2, 1, 4, 4, 4, 4 }; 471 | private static readonly int[] dec64table = new int[] { 0, 0, 0, -1, 0, 1, 2, 3 }; 472 | 473 | private static int LZ4_decompress_generic(byte* source, byte* dest, int inputSize, int outputSize, EndCondition endOnInput, EarlyEnd partialDecoding, int targetOutputSize, DictionaryType dict, byte* lowPrefix, byte* dictStart, int dictSize) 474 | { 475 | /* Local Variables */ 476 | byte* ip = source; 477 | byte* iend = ip + inputSize; 478 | 479 | byte* op = dest; 480 | byte* oend = op + outputSize; 481 | 482 | byte* oexit = op + targetOutputSize; 483 | byte* lowLimit = lowPrefix - dictSize; 484 | 485 | byte* dictEnd = dictStart + dictSize; 486 | 487 | bool safeDecode = (endOnInput == EndCondition.EndOnInputSize); 488 | bool checkOffset = ((safeDecode) && (dictSize < 64 * Constants.Size.Kilobyte)); 489 | 490 | // Special Cases 491 | if ((partialDecoding == EarlyEnd.Partial) && (oexit > oend - MFLIMIT)) oexit = oend - MFLIMIT; // targetOutputSize too high => decode everything 492 | if ((endOnInput == EndCondition.EndOnInputSize) && (outputSize == 0)) 493 | return ((inputSize == 1) && (*ip == 0)) ? 0 : -1; // Empty output buffer 494 | if ((endOnInput == EndCondition.EndOnOutputSize) && (outputSize == 0)) 495 | return (*ip == 0 ? 1 : -1); 496 | 497 | // Main Loop 498 | while (true) 499 | { 500 | int length; 501 | 502 | /* get literal length */ 503 | byte token = *ip++; 504 | if ((length = (token >> ML_BITS)) == RUN_MASK) 505 | { 506 | byte s; 507 | do 508 | { 509 | s = *ip++; 510 | length += s; 511 | } 512 | while (((endOnInput == EndCondition.EndOnInputSize) ? ip < iend - RUN_MASK : true) && (s == 255)); 513 | 514 | if ((safeDecode) && (op + length) < op) goto _output_error; /* overflow detection */ 515 | if ((safeDecode) && (ip + length) < ip) goto _output_error; /* overflow detection */ 516 | } 517 | 518 | // copy literals 519 | byte* cpy = op + length; 520 | if (((endOnInput == EndCondition.EndOnInputSize) && ((cpy > (partialDecoding == EarlyEnd.Partial ? oexit : oend - MFLIMIT)) || (ip + length > iend - (2 + 1 + LASTLITERALS)))) 521 | || ((endOnInput == EndCondition.EndOnOutputSize) && (cpy > oend - COPYLENGTH))) 522 | { 523 | if (partialDecoding == EarlyEnd.Partial) 524 | { 525 | if (cpy > oend) 526 | goto _output_error; /* Error : write attempt beyond end of output buffer */ 527 | 528 | if ((endOnInput == EndCondition.EndOnInputSize) && (ip + length > iend)) 529 | goto _output_error; /* Error : read attempt beyond end of input buffer */ 530 | } 531 | else 532 | { 533 | if ((endOnInput == EndCondition.EndOnOutputSize) && (cpy != oend)) 534 | goto _output_error; /* Error : block decoding must stop exactly there */ 535 | 536 | if ((endOnInput == EndCondition.EndOnInputSize) && ((ip + length != iend) || (cpy > oend))) 537 | goto _output_error; /* Error : input must be consumed */ 538 | } 539 | 540 | Unsafe.CopyBlock(op, ip, (uint)length); 541 | ip += length; 542 | op += length; 543 | break; /* Necessarily EOF, due to parsing restrictions */ 544 | } 545 | 546 | WildCopy(op, ip, cpy); 547 | ip += length; op = cpy; 548 | 549 | /* get offset */ 550 | byte* match = cpy - *((ushort*)ip); ip += sizeof(ushort); 551 | if ((checkOffset) && (match < lowLimit)) 552 | goto _output_error; /* Error : offset outside destination buffer */ 553 | 554 | /* get matchlength */ 555 | if ((length = (token & ML_MASK)) == ML_MASK) 556 | { 557 | byte s; 558 | do 559 | { 560 | if ((endOnInput == EndCondition.EndOnInputSize) && (ip > iend - LASTLITERALS)) 561 | goto _output_error; 562 | 563 | s = *ip++; 564 | length += s; 565 | } 566 | while (s == 255); 567 | 568 | if ((safeDecode) && (op + length) < op) 569 | goto _output_error; /* overflow detection */ 570 | } 571 | 572 | length += MINMATCH; 573 | 574 | /* check external dictionary */ 575 | if ((dict == DictionaryType.UsingExtDict) && (match < lowPrefix)) 576 | { 577 | if (op + length > oend - LASTLITERALS) 578 | goto _output_error; /* doesn't respect parsing restriction */ 579 | 580 | if (length <= (int)(lowPrefix - match)) 581 | { 582 | /* match can be copied as a single segment from external dictionary */ 583 | match = dictEnd - (lowPrefix - match); 584 | Unsafe.CopyBlock(op, match, (uint)length); // TODO: Check if move is required. 585 | op += length; 586 | } 587 | else 588 | { 589 | /* match encompass external dictionary and current segment */ 590 | int copySize = (int)(lowPrefix - match); 591 | Unsafe.CopyBlock(op, dictEnd - copySize, (uint)copySize); 592 | op += copySize; 593 | 594 | copySize = length - copySize; 595 | if (copySize > (int)(op - lowPrefix)) /* overlap within current segment */ 596 | { 597 | byte* endOfMatch = op + copySize; 598 | byte* copyFrom = lowPrefix; 599 | while (op < endOfMatch) 600 | *op++ = *copyFrom++; 601 | } 602 | else 603 | { 604 | Unsafe.CopyBlock(op, lowPrefix, (uint)copySize); 605 | op += copySize; 606 | } 607 | } 608 | continue; 609 | } 610 | 611 | /* copy repeated sequence */ 612 | cpy = op + length; 613 | if ((op - match) < 8) 614 | { 615 | int dec64 = dec64table[op - match]; 616 | op[0] = match[0]; 617 | op[1] = match[1]; 618 | op[2] = match[2]; 619 | op[3] = match[3]; 620 | 621 | match += dec32table[op - match]; 622 | *((uint*)(op + 4)) = *(uint*)match; 623 | op += 8; 624 | match -= dec64; 625 | } 626 | else 627 | { 628 | *((ulong*)op) = *(ulong*)match; 629 | op += sizeof(ulong); 630 | match += sizeof(ulong); 631 | } 632 | 633 | if (cpy > oend - 12) 634 | { 635 | if (cpy > oend - LASTLITERALS) 636 | goto _output_error; /* Error : last LASTLITERALS bytes must be literals */ 637 | 638 | if (op < oend - 8) 639 | { 640 | WildCopy(op, match, oend - 8); 641 | match += (oend - 8) - op; 642 | op = oend - 8; 643 | } 644 | 645 | while (op < cpy) 646 | *op++ = *match++; 647 | } 648 | else 649 | { 650 | WildCopy(op, match, cpy); 651 | } 652 | 653 | op = cpy; /* correction */ 654 | } 655 | 656 | /* end of decoding */ 657 | if (endOnInput == EndCondition.EndOnInputSize) 658 | return (int)(op - dest); /* Nb of output bytes decoded */ 659 | else 660 | return (int)(ip - source); /* Nb of input bytes read */ 661 | 662 | /* Overflow error detected */ 663 | _output_error: 664 | return (int)(-(ip - source)) - 1; 665 | } 666 | 667 | private static void WildCopy(byte* dest, byte* src, byte* destEnd) 668 | { 669 | // This copy will use the same data that has already being copied as source 670 | // It is more of a repeater than a copy per-se. 671 | do 672 | { 673 | *((ulong*)dest) = *((ulong*)src); 674 | dest += sizeof(ulong); 675 | src += sizeof(ulong); 676 | } 677 | while (dest < destEnd); 678 | } 679 | 680 | #region IDisposable Support 681 | 682 | private bool disposedValue = false; // To detect redundant calls 683 | 684 | protected virtual void Dispose(bool disposing) 685 | { 686 | if (!disposedValue) 687 | { 688 | if (disposing) 689 | { 690 | // TODO: dispose managed state (managed objects). 691 | } 692 | 693 | // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. 694 | // TODO: set large fields to null. 695 | 696 | disposedValue = true; 697 | } 698 | } 699 | 700 | // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. 701 | // ~LZ4() { 702 | // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. 703 | // Dispose(false); 704 | // } 705 | 706 | // This code added to correctly implement the disposable pattern. 707 | public void Dispose() 708 | { 709 | // Do not change this code. Put cleanup code in Dispose(bool disposing) above. 710 | Dispose(true); 711 | // TODO: uncomment the following line if the finalizer is overridden above. 712 | // GC.SuppressFinalize(this); 713 | } 714 | 715 | #endregion 716 | } 717 | } 718 | -------------------------------------------------------------------------------- /Workshop/PageLocator.Benchmark.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using BenchmarkDotNet.Analysers; 5 | using BenchmarkDotNet.Attributes; 6 | using BenchmarkDotNet.Columns; 7 | using BenchmarkDotNet.Configs; 8 | using BenchmarkDotNet.Environments; 9 | using BenchmarkDotNet.Exporters; 10 | using BenchmarkDotNet.Jobs; 11 | using BenchmarkDotNet.Validators; 12 | 13 | namespace Workshop 14 | { 15 | [Config(typeof(Config))] 16 | public class PlRandomWrite 17 | { 18 | private class Config : ManualConfig 19 | { 20 | public Config() 21 | { 22 | Add(new Job 23 | { 24 | Env = 25 | { 26 | Runtime = Runtime.Core, 27 | Platform = Platform.X64, 28 | Jit = Jit.RyuJit 29 | }, 30 | // TODO: Next line is just for testing. Fine tune parameters. 31 | //Mode = Mode.SingleRun, 32 | //LaunchCount = 1, 33 | //WarmupCount = 2, 34 | //TargetCount = 40, 35 | }); 36 | 37 | // Exporters for data 38 | Add(GetExporters().ToArray()); 39 | // Generate plots using R if %R_HOME% is correctly set 40 | Add(RPlotExporter.Default); 41 | 42 | Add(StatisticColumn.AllStatistics); 43 | 44 | Add(BaselineValidator.FailOnError); 45 | Add(JitOptimizationsValidator.FailOnError); 46 | Add(EnvironmentAnalyser.Default); 47 | } 48 | } 49 | 50 | private const int NumberOfOperations = 10000; 51 | 52 | [Params(8, 16, 32, 64, 128, 256, 512)] 53 | public int CacheSize { get; set; } 54 | 55 | [Params(5)] 56 | public int RandomSeed { get; set; } 57 | 58 | private List _pageNumbers; 59 | 60 | private PageLocator _cacheV1; 61 | 62 | [Setup] 63 | public void Setup() 64 | { 65 | _cacheV1 = new PageLocator(CacheSize); 66 | 67 | var generator = new Random(RandomSeed); 68 | 69 | _pageNumbers = new List(); 70 | for (int i = 0; i < NumberOfOperations; i++) 71 | { 72 | long valueBuffer = generator.Next(); 73 | valueBuffer += (long)generator.Next() << 32; 74 | valueBuffer += (long)generator.Next() << 64; 75 | valueBuffer += (long)generator.Next() << 96; 76 | 77 | _pageNumbers.Add(valueBuffer); 78 | } 79 | } 80 | 81 | [Benchmark(OperationsPerInvoke = NumberOfOperations)] 82 | public void Basic() 83 | { 84 | foreach (var pageNumber in _pageNumbers) 85 | { 86 | _cacheV1.GetWritablePage(pageNumber); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Workshop/PageLocator.Stubs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Workshop 9 | { 10 | public class LowLevelTransaction 11 | { 12 | // TODO: implement register shuffling here. 13 | [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] 14 | public static MyPage ModifyPage(long pageNumber) 15 | { 16 | unsafe 17 | { 18 | return new MyPage { PageNumber = pageNumber }; 19 | } 20 | } 21 | 22 | // TODO: implement register shuffling here. 23 | [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] 24 | public static MyPage GetPage(long pageNumber) 25 | { 26 | unsafe 27 | { 28 | return new MyPage { PageNumber = pageNumber }; 29 | } 30 | } 31 | } 32 | 33 | public class MyPage 34 | { 35 | public long PageNumber; 36 | } 37 | 38 | public struct PageHandlePtr 39 | { 40 | public readonly MyPage Value; 41 | public readonly bool IsWritable; 42 | 43 | private const int Invalid = -1; 44 | 45 | public PageHandlePtr(MyPage value, bool isWritable) 46 | { 47 | this.Value = value; 48 | this.IsWritable = isWritable; 49 | } 50 | 51 | public long PageNumber 52 | { 53 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 54 | get { return IsValid ? Value.PageNumber : Invalid; } 55 | } 56 | 57 | public bool IsValid 58 | { 59 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 60 | get { return Value != null; } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Workshop/PageLocator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Workshop 9 | { 10 | public class PageLocator 11 | { 12 | private readonly PageHandlePtr[] _cache; 13 | private int _current; 14 | 15 | public PageLocator(int cacheSize = 4) 16 | { 17 | _cache = new PageHandlePtr[cacheSize]; 18 | } 19 | 20 | public MyPage GetReadOnlyPage(long pageNumber) 21 | { 22 | int position = _current; 23 | 24 | int itemsLeft = _cache.Length; 25 | while (itemsLeft > 0) 26 | { 27 | int i = position % _cache.Length; 28 | 29 | // If the page number is equal to the page number we are looking for (therefore it's valid) 30 | // Will not fail at PageNumber=0 because the accesor will handle that. 31 | if (_cache[i].PageNumber != pageNumber) 32 | { 33 | itemsLeft--; 34 | position++; 35 | 36 | continue; 37 | } 38 | 39 | return _cache[i].Value; 40 | } 41 | 42 | _current = (_current + 1) % _cache.Length; 43 | _cache[_current] = new PageHandlePtr(LowLevelTransaction.GetPage(pageNumber), false); 44 | return _cache[_current].Value; 45 | } 46 | 47 | public MyPage GetWritablePage(long pageNumber) 48 | { 49 | int position = _current; 50 | 51 | int itemsLeft = _cache.Length; 52 | while (itemsLeft > 0) 53 | { 54 | int i = position % _cache.Length; 55 | 56 | // If the page number is equal to the page number we are looking for (therefore it's valid) 57 | // Will not fail at PageNumber=0 because the accesor will handle that. 58 | if (_cache[i].PageNumber != pageNumber) 59 | { 60 | // we continue. 61 | itemsLeft--; 62 | position++; 63 | 64 | continue; 65 | } 66 | 67 | if (!_cache[i].IsWritable) 68 | _cache[i] = new PageHandlePtr(LowLevelTransaction.ModifyPage(pageNumber), true); 69 | 70 | return _cache[i].Value; 71 | } 72 | 73 | _current = (_current + 1) % _cache.Length; 74 | _cache[_current] = new PageHandlePtr(LowLevelTransaction.ModifyPage(pageNumber), true); 75 | return _cache[_current].Value; 76 | } 77 | 78 | public void Clear() 79 | { 80 | _current = 0; 81 | Array.Clear(_cache, 0, _cache.Length); 82 | } 83 | 84 | public void Reset(long pageNumber) 85 | { 86 | for (int i = 0; i < _cache.Length; i++) 87 | { 88 | if (_cache[i].PageNumber == pageNumber) 89 | { 90 | _cache[i] = new PageHandlePtr(); 91 | return; 92 | } 93 | } 94 | } 95 | } 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /Workshop/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using BenchmarkDotNet.Running; 8 | 9 | namespace Workshop 10 | { 11 | public class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly).Run(args); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Workshop/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Workshop")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Workshop")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("8ededf2a-b804-461c-bb24-a6fa0bac2e7f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Workshop/Workshop.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8EDEDF2A-B804-461C-BB24-A6FA0BAC2E7F} 8 | Exe 9 | Properties 10 | Workshop 11 | Workshop 12 | v4.6.2 13 | 512 14 | true 15 | 16 | 17 | 18 | 19 | 20 | AnyCPU 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | false 29 | true 30 | 31 | 32 | AnyCPU 33 | pdbonly 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | true 40 | false 41 | 42 | 43 | 44 | ..\packages\BenchmarkDotNet.0.10.3.87\lib\net46\BenchmarkDotNet.dll 45 | True 46 | 47 | 48 | ..\packages\BenchmarkDotNet.Core.0.10.3.87\lib\net46\BenchmarkDotNet.Core.dll 49 | True 50 | 51 | 52 | ..\packages\BenchmarkDotNet.Diagnostics.Windows.0.10.3.87\lib\net46\BenchmarkDotNet.Diagnostics.Windows.dll 53 | True 54 | 55 | 56 | ..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.3.87\lib\net46\BenchmarkDotNet.Toolchains.Roslyn.dll 57 | True 58 | 59 | 60 | ..\packages\DotNetCross.Memory.Unsafe.0.2.3.4\lib\netstandard1.0\DotNetCross.Memory.Unsafe.dll 61 | True 62 | 63 | 64 | ..\packages\Microsoft.CodeAnalysis.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll 65 | True 66 | 67 | 68 | ..\packages\Microsoft.CodeAnalysis.CSharp.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll 69 | True 70 | 71 | 72 | ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll 73 | True 74 | 75 | 76 | ..\packages\Microsoft.DotNet.InternalAbstractions.1.0.0\lib\net451\Microsoft.DotNet.InternalAbstractions.dll 77 | True 78 | 79 | 80 | ..\packages\Microsoft.DotNet.PlatformAbstractions.1.1.1\lib\net451\Microsoft.DotNet.PlatformAbstractions.dll 81 | True 82 | 83 | 84 | 85 | ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll 86 | True 87 | 88 | 89 | ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll 90 | True 91 | 92 | 93 | 94 | ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll 95 | True 96 | 97 | 98 | 99 | ..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll 100 | True 101 | 102 | 103 | ..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll 104 | True 105 | 106 | 107 | ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll 108 | True 109 | 110 | 111 | ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll 112 | True 113 | 114 | 115 | ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll 116 | True 117 | 118 | 119 | 120 | ..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll 121 | True 122 | 123 | 124 | ..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll 125 | True 126 | 127 | 128 | ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll 129 | True 130 | 131 | 132 | ..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll 133 | True 134 | 135 | 136 | ..\packages\System.Runtime.InteropServices.4.3.0\lib\net462\System.Runtime.InteropServices.dll 137 | True 138 | 139 | 140 | ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll 141 | True 142 | 143 | 144 | ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll 145 | True 146 | 147 | 148 | ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll 149 | True 150 | 151 | 152 | ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll 153 | True 154 | 155 | 156 | ..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll 157 | True 158 | 159 | 160 | ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll 161 | True 162 | 163 | 164 | ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll 165 | True 166 | 167 | 168 | ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll 169 | True 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll 179 | True 180 | 181 | 182 | ..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll 183 | True 184 | 185 | 186 | ..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll 187 | True 188 | 189 | 190 | ..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll 191 | True 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 220 | 221 | 222 | 223 | 230 | -------------------------------------------------------------------------------- /Workshop/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | --------------------------------------------------------------------------------