├── .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 |
--------------------------------------------------------------------------------