├── src ├── app.config ├── packages.config ├── Dictionary.csproj ├── Performance.cs ├── Tests.cs └── FastDictionary.cs ├── LICENSE ├── Dictionary.sln └── .gitignore /src/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Federico Andres Lois 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Dictionary.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dictionary", "src\Dictionary.csproj", "{77367691-7DF8-408A-A8BE-3D0F787DE388}" 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 | {77367691-7DF8-408A-A8BE-3D0F787DE388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {77367691-7DF8-408A-A8BE-3D0F787DE388}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {77367691-7DF8-408A-A8BE-3D0F787DE388}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {77367691-7DF8-408A-A8BE-3D0F787DE388}.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 = 8b733967-fb73-4e55-8512-76aeff438d47 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | -------------------------------------------------------------------------------- /src/Dictionary.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | {77367691-7DF8-408A-A8BE-3D0F787DE388} 10 | Exe 11 | Properties 12 | Dictionary 13 | Dictionary 14 | v4.5 15 | 512 16 | 53c07c1c 17 | 0 18 | publish\ 19 | true 20 | Disk 21 | false 22 | Foreground 23 | 7 24 | Days 25 | false 26 | false 27 | true 28 | 0 29 | 1.0.0.%2a 30 | false 31 | false 32 | true 33 | 34 | 35 | 36 | AnyCPU 37 | true 38 | full 39 | false 40 | bin\Debug\ 41 | DEBUG;TRACE 42 | prompt 43 | 4 44 | true 45 | false 46 | True 47 | False 48 | True 49 | False 50 | False 51 | True 52 | True 53 | True 54 | True 55 | True 56 | True 57 | True 58 | True 59 | True 60 | False 61 | True 62 | True 63 | True 64 | True 65 | False 66 | False 67 | False 68 | True 69 | False 70 | True 71 | True 72 | True 73 | False 74 | False 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | True 83 | False 84 | False 85 | False 86 | Full 87 | %28none%29 88 | 0 89 | 90 | 91 | AnyCPU 92 | pdbonly 93 | true 94 | bin\Release\ 95 | TRACE 96 | prompt 97 | 4 98 | true 99 | false 100 | 101 | 102 | Dictionary.Performance 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll 114 | True 115 | 116 | 117 | packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll 118 | True 119 | 120 | 121 | packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll 122 | True 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | False 140 | Microsoft .NET Framework 4.5 %28x86 and x64%29 141 | true 142 | 143 | 144 | False 145 | .NET Framework 3.5 SP1 Client Profile 146 | false 147 | 148 | 149 | False 150 | .NET Framework 3.5 SP1 151 | false 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 161 | 162 | 163 | 164 | 165 | 172 | -------------------------------------------------------------------------------- /src/Performance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Dictionary 10 | { 11 | public class Performance 12 | { 13 | public static void Main () 14 | { 15 | Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime; 16 | 17 | Random rnd = new Random(13); 18 | int[] tuples = new int[1000000]; 19 | string[] tuplesString = new string[1000000]; 20 | for (int i = 0; i < tuples.Length; i++) 21 | { 22 | tuples[i] = rnd.Next(); 23 | tuplesString[i] = tuples[i].ToString(); 24 | } 25 | 26 | Console.WriteLine("Structs: " + BenchmarkCreationOfArrayOfStructs()); 27 | Console.WriteLine("Arrays: " + BenchmarkCreationOfMultipleArrays()); 28 | 29 | int tries = 100000; 30 | Console.WriteLine("Structs with temps: " + BenchmarkAccessWithTemps(tries)); 31 | Console.WriteLine("Structs without temps: " + BenchmarkAccessWithoutTemps(tries)); 32 | Console.WriteLine("Structs with inline references: " + BenchmarkAccessWithInlineRef(tries)); 33 | 34 | tries = 5; 35 | 36 | Console.WriteLine("Native: " + BenchmarkNativeDictionary(tuples, tries)); 37 | Console.WriteLine("Fast: " + BenchmarkFastDictionary(tuples, tries)); 38 | 39 | Console.WriteLine("Native-String: " + BenchmarkNativeDictionaryString(tuplesString, tries)); 40 | Console.WriteLine("Fast-String: " + BenchmarkFastDictionaryString(tuplesString, tries)); 41 | 42 | Console.WriteLine("Native-String-Out: " + BenchmarkNativeDictionaryStringOut(tuplesString, tries)); 43 | Console.WriteLine("Fast-String-Out: " + BenchmarkFastDictionaryStringOut(tuplesString, tries)); 44 | } 45 | 46 | private static long BenchmarkNativeDictionary(int[] tuples, int tries) 47 | { 48 | var native = Stopwatch.StartNew(); 49 | for (int i = 0; i < tries; i++) 50 | { 51 | Dictionary nativeDict = new Dictionary(tuples.Length * 2); 52 | for (int j = 0; j < tuples.Length; j++) 53 | nativeDict[tuples[j]] = j; 54 | 55 | int k; 56 | for (int j = 0; j < tuples.Length; j++) 57 | { 58 | k = nativeDict[tuples[j]]; 59 | k++; 60 | } 61 | 62 | } 63 | native.Stop(); 64 | return native.ElapsedMilliseconds; 65 | } 66 | 67 | private static long BenchmarkNativeDictionaryString(string[] tuples, int tries) 68 | { 69 | var native = Stopwatch.StartNew(); 70 | for (int i = 0; i < tries; i++) 71 | { 72 | var nativeDict = new Dictionary(tuples.Length * 2); 73 | for (int j = 0; j < tuples.Length; j++) 74 | nativeDict[tuples[j]] = j; 75 | 76 | int k; 77 | for (int j = 0; j < tuples.Length; j++) 78 | { 79 | k = nativeDict[tuples[j]]; 80 | k++; 81 | } 82 | 83 | } 84 | native.Stop(); 85 | return native.ElapsedMilliseconds; 86 | } 87 | 88 | private static long BenchmarkNativeDictionaryStringOut(string[] tuples, int tries) 89 | { 90 | int y = 0; 91 | var native = Stopwatch.StartNew(); 92 | for (int i = 0; i < tries; i++) 93 | { 94 | var nativeDict = new Dictionary(tuples.Length * 2); 95 | for (int j = 0; j < tuples.Length; j++) 96 | nativeDict[j] = tuples[j]; 97 | 98 | string k; 99 | for (int j = 0; j < tuples.Length; j++) 100 | { 101 | k = nativeDict[j]; 102 | if (k != null) 103 | y++; 104 | } 105 | 106 | 107 | } 108 | native.Stop(); 109 | return native.ElapsedMilliseconds; 110 | } 111 | 112 | private static long BenchmarkFastDictionary(int[] tuples, int tries) 113 | { 114 | var fast = Stopwatch.StartNew(); 115 | for (int i = 0; i < tries; i++) 116 | { 117 | var fastDict = new FastDictionary(tuples.Length * 2); 118 | for (int j = 0; j < tuples.Length; j++) 119 | fastDict[tuples[j]] = j; 120 | 121 | int k; 122 | for (int j = 0; j < tuples.Length; j++) 123 | { 124 | fastDict.TryGetValue(tuples[j], out k); 125 | // k = fastDict[tuples[j]]; 126 | k++; 127 | } 128 | 129 | 130 | } 131 | fast.Stop(); 132 | return fast.ElapsedMilliseconds; 133 | 134 | } 135 | 136 | private static long BenchmarkFastDictionaryString(string[] tuples, int tries) 137 | { 138 | var fast = Stopwatch.StartNew(); 139 | for (int i = 0; i < tries; i++) 140 | { 141 | var fastDict = new FastDictionary(tuples.Length * 2); 142 | for (int j = 0; j < tuples.Length; j++) 143 | fastDict[tuples[j]] = j; 144 | 145 | int k; 146 | for (int j = 0; j < tuples.Length; j++) 147 | { 148 | fastDict.TryGetValue(tuples[j], out k); 149 | // k = fastDict[tuples[j]]; 150 | k++; 151 | } 152 | 153 | } 154 | fast.Stop(); 155 | return fast.ElapsedMilliseconds; 156 | } 157 | 158 | 159 | private static long BenchmarkFastDictionaryStringOut(string[] tuples, int tries) 160 | { 161 | int y = 0; 162 | var fast = Stopwatch.StartNew(); 163 | for (int i = 0; i < tries; i++) 164 | { 165 | var fastDict = new FastDictionary(tuples.Length * 2); 166 | for (int j = 0; j < tuples.Length; j++) 167 | fastDict[j] = tuples[j]; 168 | 169 | string k; 170 | for (int j = 0; j < tuples.Length; j++) 171 | { 172 | fastDict.TryGetValue(j, out k); 173 | //k = fastDict[j]; 174 | if (k != null) 175 | y++; 176 | } 177 | 178 | } 179 | fast.Stop(); 180 | return fast.ElapsedMilliseconds; 181 | } 182 | 183 | 184 | 185 | 186 | public struct TryEntry 187 | { 188 | public uint Hash; 189 | public TKey Key; 190 | public TValue Value; 191 | } 192 | 193 | private const int iterations = 4000; 194 | 195 | public static long BenchmarkCreationOfArrayOfStructs() 196 | { 197 | var fast = Stopwatch.StartNew(); 198 | 199 | TryEntry[] value; 200 | for (int i = 1; i < iterations; i++) 201 | { 202 | value = new TryEntry[iterations]; 203 | 204 | value[0].Hash = 18; 205 | value[0].Key = new object(); 206 | value[0].Value = new object(); 207 | } 208 | 209 | fast.Stop(); 210 | return fast.ElapsedMilliseconds; 211 | } 212 | 213 | public static long BenchmarkAccessWithoutTemps(int tries) 214 | { 215 | TryEntry[] value = new TryEntry[iterations]; 216 | for (int i = 0; i < iterations; i++) 217 | { 218 | value[i].Hash = 18; 219 | value[i].Key = new object(); 220 | value[i].Value = new object(); 221 | } 222 | 223 | int count = 0; 224 | 225 | var fast = Stopwatch.StartNew(); 226 | 227 | for (int @try = 0; @try < tries; @try++) 228 | { 229 | for (int i = 0; i < iterations; i++) 230 | { 231 | if (value[i].Hash == 18 && value[i].Key != null && value[i].Value == null) 232 | count++; 233 | } 234 | } 235 | 236 | fast.Stop(); 237 | return fast.ElapsedTicks; 238 | } 239 | 240 | 241 | public static long BenchmarkAccessWithTemps(int tries) 242 | { 243 | TryEntry[] value = new TryEntry[iterations]; 244 | for (int i = 0; i < iterations; i++) 245 | { 246 | value[i].Hash = 18; 247 | value[i].Key = new object(); 248 | value[i].Value = new object(); 249 | } 250 | 251 | int count = 0; 252 | 253 | var fast = Stopwatch.StartNew(); 254 | for (int @try = 0; @try < tries; @try++) 255 | { 256 | for (int i = 0; i < iterations; i++) 257 | { 258 | TryEntry tmp = value[i]; 259 | if (tmp.Hash == 18 && tmp.Key != null && tmp.Value == null) 260 | count++; 261 | } 262 | } 263 | 264 | fast.Stop(); 265 | return fast.ElapsedTicks; 266 | } 267 | 268 | public static long BenchmarkAccessWithInlineRef(int tries) 269 | { 270 | TryEntry[] value = new TryEntry[iterations]; 271 | for (int i = 0; i < iterations; i++) 272 | { 273 | value[i].Hash = 18; 274 | value[i].Key = new object(); 275 | value[i].Value = new object(); 276 | } 277 | 278 | int count = 0; 279 | 280 | var fast = Stopwatch.StartNew(); 281 | for (int @try = 0; @try < tries; @try++) 282 | { 283 | for (int i = 0; i < iterations; i++) 284 | { 285 | if (Check(ref value[i])) 286 | count++; 287 | } 288 | } 289 | 290 | fast.Stop(); 291 | return fast.ElapsedTicks; 292 | } 293 | 294 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 295 | private static bool Check(ref TryEntry tmp) 296 | { 297 | return tmp.Hash == 18 && tmp.Key != null && tmp.Value == null; 298 | } 299 | 300 | 301 | public static long BenchmarkCreationOfMultipleArrays() 302 | { 303 | var fast = Stopwatch.StartNew(); 304 | 305 | uint[] hashes; 306 | object[] keys; 307 | object[] values; 308 | 309 | for (int i = 1; i < iterations; i++) 310 | { 311 | hashes = new uint[iterations]; 312 | keys = new object[iterations]; 313 | values = new object[iterations]; 314 | 315 | hashes[0] = 18; 316 | keys[0] = new object(); 317 | values[0] = new object(); 318 | } 319 | 320 | fast.Stop(); 321 | return fast.ElapsedMilliseconds; 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Dictionary 9 | { 10 | public class Tests 11 | { 12 | [Fact] 13 | public void Construction() 14 | { 15 | var dict = new FastDictionary(); 16 | Assert.Equal(0, dict.Count); 17 | Assert.Equal(32, dict.Capacity); 18 | Assert.NotNull(dict.Comparer); 19 | 20 | dict = new FastDictionary(null as IEqualityComparer); 21 | Assert.Equal(0, dict.Count); 22 | Assert.Equal(32, dict.Capacity); 23 | Assert.NotNull(dict.Comparer); 24 | 25 | dict = new FastDictionary(16); 26 | Assert.Equal(0, dict.Count); 27 | Assert.Equal(16, dict.Capacity); 28 | Assert.NotNull(dict.Comparer); 29 | } 30 | 31 | [Fact] 32 | public void ConstructionWithNonPowerOf2() 33 | { 34 | var dict = new FastDictionary(5); 35 | Assert.Equal(0, dict.Count); 36 | Assert.Equal(8, dict.Capacity); 37 | Assert.NotNull(dict.Comparer); 38 | } 39 | 40 | 41 | [Fact] 42 | public void ConstructionWithExplicitZeroAndNegative() 43 | { 44 | var dict = new FastDictionary(0); 45 | Assert.Equal(0, dict.Count); 46 | Assert.Equal(4, dict.Capacity); 47 | Assert.NotNull(dict.Comparer); 48 | 49 | dict = new FastDictionary(-1); 50 | Assert.Equal(0, dict.Count); 51 | Assert.Equal(4, dict.Capacity); 52 | Assert.NotNull(dict.Comparer); 53 | } 54 | 55 | [Fact] 56 | public void ConstructionWithFastDictionary() 57 | { 58 | var dict = new FastDictionary(200, EqualityComparer.Default); 59 | for (int i = 0; i < 100; i++) 60 | dict[i] = i; 61 | 62 | var fromFastDictionary = new FastDictionary(dict.Capacity, dict, EqualityComparer.Default); 63 | Assert.Equal(dict.Count, fromFastDictionary.Count); 64 | Assert.Equal(dict.Capacity, fromFastDictionary.Capacity); 65 | Assert.Equal(dict.Comparer, fromFastDictionary.Comparer); 66 | 67 | int count = 0; 68 | foreach (var item in fromFastDictionary) 69 | { 70 | Assert.Equal(item.Key, item.Value); 71 | count++; 72 | } 73 | Assert.Equal(100, count); 74 | } 75 | 76 | private class CustomIntEqualityComparer : EqualityComparer 77 | { 78 | public override bool Equals(int x, int y) 79 | { 80 | return x == y; 81 | } 82 | 83 | public override int GetHashCode(int obj) 84 | { 85 | return obj; 86 | } 87 | } 88 | 89 | private class CollidingStringEqualityComparer : EqualityComparer 90 | { 91 | public override bool Equals(string x, string y) 92 | { 93 | return x == y; 94 | } 95 | 96 | public override int GetHashCode(string obj) 97 | { 98 | return obj.Length; 99 | } 100 | } 101 | 102 | [Fact] 103 | public void ConstructionWithFastDictionaryAndDifferentComparer() 104 | { 105 | var equalityComparer = new CustomIntEqualityComparer(); 106 | 107 | var dict = new FastDictionary(200, equalityComparer); 108 | for (int i = 0; i < 100; i++) 109 | dict[i] = i; 110 | 111 | var fromFastDictionary = new FastDictionary(dict, EqualityComparer.Default); 112 | Assert.Equal(dict.Count, fromFastDictionary.Count); 113 | Assert.Equal(dict.Capacity, fromFastDictionary.Capacity); 114 | Assert.NotSame(dict.Comparer, fromFastDictionary.Comparer); 115 | 116 | int count = 0; 117 | foreach (var item in fromFastDictionary) 118 | { 119 | Assert.Equal(item.Key, item.Value); 120 | count++; 121 | } 122 | Assert.Equal(100, count); 123 | } 124 | 125 | 126 | [Fact] 127 | public void ConstructionWithNativeDictionary() 128 | { 129 | var dict = new Dictionary(200, EqualityComparer.Default); 130 | for (int i = 0; i < 100; i++) 131 | dict[i] = i; 132 | 133 | var fromFastDictionary = new FastDictionary(dict.Count, dict, EqualityComparer.Default); 134 | Assert.Equal(dict.Count, fromFastDictionary.Count); 135 | Assert.Equal(dict.Comparer, fromFastDictionary.Comparer); 136 | 137 | int count = 0; 138 | foreach (var item in fromFastDictionary) 139 | { 140 | Assert.Equal(item.Key, item.Value); 141 | count++; 142 | } 143 | Assert.Equal(100, count); 144 | } 145 | 146 | 147 | 148 | [Fact] 149 | public void ConsecutiveInsertionsWithIndexerAndWithoutGrow() 150 | { 151 | var dict = new FastDictionary(200); 152 | 153 | for (int i = 0; i < 100; i++) 154 | dict[i] = i; 155 | 156 | for (int i = 0; i < 100; i++) 157 | { 158 | Assert.True(dict.Contains(i)); 159 | Assert.Equal(i, dict[i]); 160 | } 161 | 162 | int count = 0; 163 | foreach (var item in dict) 164 | { 165 | Assert.Equal(item.Key, item.Value); 166 | count++; 167 | } 168 | Assert.Equal(100, count); 169 | 170 | Assert.Equal(100, dict.Count); 171 | Assert.Equal(256, dict.Capacity); 172 | } 173 | 174 | 175 | [Fact] 176 | public void ConsecutiveInsertionsWithIndexerAndGrow() 177 | { 178 | var dict = new FastDictionary(4); 179 | 180 | for (int i = 0; i < 100; i++) 181 | dict[i] = i; 182 | 183 | for (int i = 0; i < 100; i++) 184 | { 185 | Assert.True(dict.Contains(i)); 186 | Assert.Equal(i, dict[i]); 187 | } 188 | 189 | int count = 0; 190 | foreach (var item in dict) 191 | { 192 | Assert.Equal(item.Key, item.Value); 193 | count++; 194 | } 195 | Assert.Equal(100, count); 196 | 197 | 198 | Assert.Equal(100, dict.Count); 199 | Assert.Equal(256, dict.Capacity); 200 | } 201 | 202 | [Fact] 203 | public void ConsecutiveInsertionsWithoutGrow() 204 | { 205 | var dict = new FastDictionary(200); 206 | 207 | for (int i = 0; i < 100; i++) 208 | dict.Add(i, i); 209 | 210 | for (int i = 0; i < 100; i++) 211 | { 212 | Assert.True(dict.Contains(i)); 213 | Assert.Equal(i, dict[i]); 214 | } 215 | 216 | int count = 0; 217 | foreach (var item in dict) 218 | { 219 | Assert.Equal(item.Key, item.Value); 220 | count++; 221 | } 222 | Assert.Equal(100, count); 223 | 224 | Assert.Equal(100, dict.Count); 225 | Assert.Equal(256, dict.Capacity); 226 | } 227 | 228 | [Fact] 229 | public void ConsecutiveInsertionsAndGrow() 230 | { 231 | var dict = new FastDictionary(4); 232 | 233 | for (int i = 0; i < 100; i++) 234 | dict.Add(i, i); 235 | 236 | for (int i = 0; i < 100; i++) 237 | { 238 | Assert.True(dict.Contains(i)); 239 | Assert.Equal(i, dict[i]); 240 | } 241 | 242 | Assert.Equal(100, dict.Count); 243 | Assert.Equal(256, dict.Capacity); 244 | } 245 | 246 | [Fact] 247 | public void ConsecutiveRemovesWithoutGrow() 248 | { 249 | var dict = new FastDictionary(200); 250 | 251 | for (int i = 0; i < 100; i++) 252 | dict[i] = i; 253 | 254 | for (int i = 0; i < 100; i += 2) 255 | Assert.True(dict.Remove(i)); 256 | 257 | for (int i = 0; i < 100; i++) 258 | { 259 | if (i % 2 == 0) 260 | Assert.False(dict.Contains(i)); 261 | else 262 | Assert.True(dict.Contains(i)); 263 | } 264 | 265 | Assert.Equal(50, dict.Count); 266 | Assert.Equal(256, dict.Capacity); 267 | } 268 | 269 | [Fact] 270 | public void ConsecutiveRemovesWithGrow() 271 | { 272 | var dict = new FastDictionary(4); 273 | 274 | for (int i = 0; i < 100; i++) 275 | dict[i] = i; 276 | 277 | for (int i = 0; i < 100; i += 2) 278 | Assert.True(dict.Remove(i)); 279 | 280 | for (int i = 0; i < 100; i++) 281 | { 282 | if (i % 2 == 0) 283 | Assert.False(dict.Contains(i)); 284 | else 285 | Assert.True(dict.Contains(i)); 286 | } 287 | 288 | Assert.Equal(50, dict.Count); 289 | Assert.Equal(256, dict.Capacity); 290 | } 291 | 292 | [Fact] 293 | public void ConsecutiveInsertsWithShrink() 294 | { 295 | var dict = new FastDictionary(); 296 | 297 | for (int i = 0; i < 100; i++) 298 | dict[i] = i; 299 | 300 | dict.Clear(); 301 | 302 | for (int i = 0; i < 33; i++) 303 | dict[i] = i; 304 | 305 | dict.Remove(32); 306 | 307 | int value; 308 | Assert.True(dict.TryGetValue(0, out value)); 309 | 310 | Assert.Equal(32, dict.Count); 311 | Assert.True(dict.Capacity > 32); 312 | } 313 | 314 | [Fact] 315 | public void InsertDeleted() 316 | { 317 | var dict = new FastDictionary(16); 318 | 319 | dict[1] = 1; 320 | dict[2] = 2; 321 | 322 | dict.Remove(1); 323 | 324 | dict[17] = 17; 325 | 326 | Assert.False(dict.Contains(1)); 327 | Assert.True(dict.Contains(2)); 328 | Assert.True(dict.Contains(17)); 329 | 330 | Assert.Equal(2, dict.Count); 331 | Assert.Equal(16, dict.Capacity); 332 | } 333 | 334 | [Fact] 335 | public void AddDeleted() 336 | { 337 | var dict = new FastDictionary(16); 338 | 339 | dict.Add(1, 1); 340 | dict.Add(2, 2); 341 | dict.Remove(1); 342 | dict.Add(17, 17); 343 | 344 | Assert.False(dict.Contains(1)); 345 | Assert.True(dict.Contains(2)); 346 | Assert.True(dict.Contains(17)); 347 | 348 | Assert.Equal(2, dict.Count); 349 | Assert.Equal(16, dict.Capacity); 350 | } 351 | 352 | [Fact] 353 | public void Duplicates() 354 | { 355 | var dict = new FastDictionary(16); 356 | dict[1] = 1; 357 | dict[1] = 2; 358 | 359 | Assert.Equal(2, dict[1]); 360 | Assert.Throws(() => dict.Add(1, 3)); 361 | } 362 | 363 | 364 | [Fact] 365 | public void EnumeratorsWithJumps() 366 | { 367 | var dict = new FastDictionary(16); 368 | dict[1] = 1; 369 | dict[2] = 2; 370 | dict[15] = 15; 371 | 372 | int count = 0; 373 | foreach (var item in dict.Keys) 374 | count++; 375 | Assert.Equal(3, count); 376 | 377 | count = 0; 378 | foreach (var item in dict.Values) 379 | count++; 380 | Assert.Equal(3, count); 381 | 382 | count = 0; 383 | foreach (var item in dict) 384 | count++; 385 | Assert.Equal(3, count); 386 | } 387 | 388 | [Fact] 389 | public void Clear() 390 | { 391 | var dict = new FastDictionary(200); 392 | for (int i = 0; i < 100; i++) 393 | dict[i] = i; 394 | 395 | dict.Clear(); 396 | 397 | Assert.Equal(0, dict.Count); 398 | Assert.Equal(256, dict.Capacity); 399 | 400 | for (int i = 0; i < 100; i++) 401 | Assert.False(dict.Contains(i)); 402 | } 403 | 404 | [Fact] 405 | public void InsertionAfterClear() 406 | { 407 | var dict = new FastDictionary(200); 408 | for (int i = 0; i < 100; i++) 409 | dict[i] = i; 410 | 411 | dict.Clear(); 412 | 413 | Assert.Equal(0, dict.Count); 414 | Assert.Equal(256, dict.Capacity); 415 | 416 | for (int i = 0; i < 100; i += 10) 417 | dict[i] = i; 418 | 419 | 420 | for (int i = 0; i < 100; i++) 421 | { 422 | if (i % 10 == 0) 423 | Assert.True(dict.Contains(i)); 424 | else 425 | Assert.False(dict.Contains(i)); 426 | } 427 | } 428 | 429 | [Fact] 430 | public void KeysArePresent() 431 | { 432 | var dict = new FastDictionary(4); 433 | for (int i = 0; i < 100; i++) 434 | dict[i] = i; 435 | 436 | int count = 0; 437 | foreach (var key in dict.Keys.ToList()) 438 | { 439 | Assert.True(dict.ContainsKey(key)); 440 | Assert.Equal(key, dict[key]); 441 | count++; 442 | } 443 | Assert.Equal(100, count); 444 | } 445 | 446 | [Fact] 447 | public void ValuesArePresent() 448 | { 449 | var dict = new FastDictionary(4); 450 | for (int i = 0; i < 100; i++) 451 | dict[i] = i; 452 | 453 | int count = 0; 454 | foreach (var value in dict.Values.ToList()) 455 | { 456 | Assert.True(dict.ContainsValue(value)); 457 | count++; 458 | } 459 | Assert.Equal(100, count); 460 | } 461 | 462 | 463 | private class ForceOutOfRangeHashesEqualityComparer : EqualityComparer 464 | { 465 | public override bool Equals(int x, int y) 466 | { 467 | return x == y; 468 | } 469 | 470 | public override int GetHashCode(int obj) 471 | { 472 | unchecked 473 | { 474 | if (obj % 2 == 0) 475 | return (int)0xFFFFFFFF; 476 | else 477 | return (int)0xFFFFFFFE; 478 | } 479 | } 480 | } 481 | 482 | [Fact] 483 | public void UseOfOfBoundsHashes() 484 | { 485 | var dict = new FastDictionary(16, new ForceOutOfRangeHashesEqualityComparer()); 486 | dict[1] = 1; 487 | dict[2] = 2; 488 | 489 | Assert.Equal(1, dict[1]); 490 | Assert.Equal(2, dict[2]); 491 | 492 | dict.Remove(1); 493 | Assert.False(dict.Contains(1)); 494 | Assert.True(dict.Contains(2)); 495 | 496 | dict.Remove(2); 497 | Assert.False(dict.Contains(1)); 498 | Assert.False(dict.Contains(2)); 499 | } 500 | 501 | [Fact] 502 | public void InsertAndRemoveWithoutGrowth() 503 | { 504 | var dict = new FastDictionary(8); 505 | 506 | for (int i = 0; i < 100; i++) 507 | { 508 | dict[i] = i; 509 | dict.Remove(i); 510 | int dummy; 511 | Assert.False(dict.TryGetValue(i, out dummy)); 512 | } 513 | 514 | Assert.Equal(0, dict.Count); 515 | Assert.Equal(8, dict.Capacity); 516 | } 517 | 518 | [Fact] 519 | public void AddAndRemoveWithoutGrowth() 520 | { 521 | var dict = new FastDictionary(8); 522 | 523 | for (int i = 0; i < 100; i++) 524 | { 525 | dict.Add(i, i); 526 | dict.Remove(i); 527 | int dummy; 528 | Assert.False(dict.TryGetValue(i, out dummy)); 529 | } 530 | 531 | Assert.Equal(0, dict.Count); 532 | Assert.Equal(8, dict.Capacity); 533 | } 534 | 535 | [Fact] 536 | public void AddWithHashCollision() 537 | { 538 | var dict = new FastDictionary( new CollidingStringEqualityComparer() ); 539 | 540 | dict.Add("a",1); 541 | dict.Add("b",1); 542 | dict.Remove("a"); 543 | 544 | Assert.Throws(() => dict.Add("b", 1)); 545 | } 546 | 547 | [Fact] 548 | public void UpdateWithHashCollision() 549 | { 550 | var dict = new FastDictionary(new CollidingStringEqualityComparer()); 551 | 552 | dict["a"] = 1; 553 | dict["b"] = 1; 554 | dict.Remove("a"); 555 | dict["b"] = 1; 556 | 557 | Assert.Equal(1, dict.Count); 558 | } 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /src/FastDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Diagnostics.Contracts; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Reflection.Emit; 9 | using System.Runtime.CompilerServices; 10 | using System.Runtime.InteropServices; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace Dictionary 15 | { 16 | internal static class DictionaryHelper 17 | { 18 | /// 19 | /// Minimum size we're willing to let hashtables be. 20 | /// Must be a power of two, and at least 4. 21 | /// Note, however, that for a given hashtable, the initial size is a function of the first constructor arg, and may be > kMinBuckets. 22 | /// 23 | internal const int kMinBuckets = 4; 24 | 25 | /// 26 | /// 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. 27 | /// 28 | internal const int kInitialCapacity = 32; 29 | 30 | internal const int kPowerOfTableSize = 2048; 31 | 32 | private readonly static int[] nextPowerOf2Table = new int[kPowerOfTableSize]; 33 | 34 | static DictionaryHelper() 35 | { 36 | for (int i = 0; i <= kMinBuckets; i++) 37 | nextPowerOf2Table[i] = kMinBuckets; 38 | 39 | for (int i = kMinBuckets + 1; i < kPowerOfTableSize; i++) 40 | nextPowerOf2Table[i] = NextPowerOf2Internal(i); 41 | } 42 | 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 44 | internal static int NextPowerOf2(int v) 45 | { 46 | if (v < kPowerOfTableSize) 47 | { 48 | return nextPowerOf2Table[v]; 49 | } 50 | else 51 | { 52 | return NextPowerOf2Internal(v); 53 | } 54 | } 55 | 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | private static int NextPowerOf2Internal(int v) 58 | { 59 | v--; 60 | v |= v >> 1; 61 | v |= v >> 2; 62 | v |= v >> 4; 63 | v |= v >> 8; 64 | v |= v >> 16; 65 | v++; 66 | 67 | return v; 68 | } 69 | } 70 | 71 | public class FastDictionary : IEnumerable> 72 | { 73 | const int InvalidNodePosition = -1; 74 | 75 | public const uint kUnusedHash = 0xFFFFFFFF; 76 | public const uint kDeletedHash = 0xFFFFFFFE; 77 | 78 | // TLoadFactor4 - controls hash map load. 4 means 100% load, ie. hashmap will grow 79 | // when number of items == capacity. Default value of 6 means it grows when 80 | // number of items == capacity * 3/2 (6/4). Higher load == tighter maps, but bigger 81 | // risk of collisions. 82 | static int tLoadFactor = 6; 83 | 84 | private struct Entry 85 | { 86 | public uint Hash; 87 | public TKey Key; 88 | public TValue Value; 89 | 90 | public Entry ( uint hash, TKey key, TValue value) 91 | { 92 | this.Hash = hash; 93 | this.Key = key; 94 | this.Value = value; 95 | } 96 | } 97 | 98 | private Entry[] _entries; 99 | 100 | private int _capacity; 101 | 102 | private int _initialCapacity; // This is the initial capacity of the dictionary, we will never shrink beyond this point. 103 | private int _size; // This is the real counter of how many items are in the hash-table (regardless of buckets) 104 | private int _numberOfUsed; // How many used buckets. 105 | private int _numberOfDeleted; // how many occupied buckets are marked deleted 106 | private int _nextGrowthThreshold; 107 | 108 | 109 | private readonly IEqualityComparer comparer; 110 | public IEqualityComparer Comparer 111 | { 112 | get { return comparer; } 113 | } 114 | 115 | public int Capacity 116 | { 117 | get { return _capacity; } 118 | } 119 | 120 | public int Count 121 | { 122 | get { return _size; } 123 | } 124 | 125 | public bool IsEmpty 126 | { 127 | get { return Count == 0; } 128 | } 129 | 130 | public FastDictionary(int initialBucketCount, IEnumerable> src, IEqualityComparer comparer) 131 | : this(initialBucketCount, comparer) 132 | { 133 | Contract.Requires(src != null); 134 | Contract.Ensures(_capacity >= initialBucketCount); 135 | 136 | foreach (var item in src) 137 | this[item.Key] = item.Value; 138 | } 139 | 140 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 141 | public FastDictionary(FastDictionary src, IEqualityComparer comparer) 142 | : this(src._capacity, src, comparer) 143 | { } 144 | 145 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 146 | public FastDictionary(FastDictionary src) 147 | : this(src._capacity, src, src.comparer) 148 | { } 149 | 150 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 151 | public FastDictionary(int initialBucketCount, FastDictionary src, IEqualityComparer comparer) 152 | { 153 | Contract.Requires(src != null); 154 | Contract.Ensures(_capacity >= initialBucketCount); 155 | Contract.Ensures(_capacity >= src._capacity); 156 | 157 | this.comparer = comparer ?? EqualityComparer.Default; 158 | 159 | this._initialCapacity = DictionaryHelper.NextPowerOf2(initialBucketCount); 160 | this._capacity = Math.Max(src._capacity, initialBucketCount); 161 | this._size = src._size; 162 | this._numberOfUsed = src._numberOfUsed; 163 | this._numberOfDeleted = src._numberOfDeleted; 164 | this._nextGrowthThreshold = src._nextGrowthThreshold; 165 | 166 | int newCapacity = _capacity; 167 | 168 | if (comparer == src.comparer) 169 | { 170 | // Initialization through copy (very efficient) because the comparer is the same. 171 | this._entries = new Entry[newCapacity]; 172 | Array.Copy(src._entries, _entries, newCapacity); 173 | } 174 | else 175 | { 176 | // Initialization through rehashing because the comparer is not the same. 177 | var entries = new Entry[newCapacity]; 178 | BlockCopyMemoryHelper.Memset(entries, new Entry(kUnusedHash, default(TKey), default(TValue))); 179 | 180 | // Creating a temporary alias to use for rehashing. 181 | this._entries = src._entries; 182 | 183 | // This call will rewrite the aliases 184 | Rehash(entries); 185 | } 186 | } 187 | 188 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 189 | public FastDictionary(IEqualityComparer comparer) 190 | : this(DictionaryHelper.kInitialCapacity, comparer) 191 | { } 192 | 193 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 194 | public FastDictionary(int initialBucketCount, IEqualityComparer comparer) 195 | { 196 | Contract.Ensures(_capacity >= initialBucketCount); 197 | 198 | this.comparer = comparer ?? EqualityComparer.Default; 199 | 200 | // Calculate the next power of 2. 201 | int newCapacity = initialBucketCount >= DictionaryHelper.kMinBuckets ? initialBucketCount : DictionaryHelper.kMinBuckets; 202 | newCapacity = DictionaryHelper.NextPowerOf2(newCapacity); 203 | 204 | this._initialCapacity = newCapacity; 205 | 206 | // Initialization 207 | this._entries = new Entry[newCapacity]; 208 | BlockCopyMemoryHelper.Memset(this._entries, new Entry(kUnusedHash, default(TKey), default(TValue))); 209 | 210 | this._capacity = newCapacity; 211 | 212 | this._numberOfUsed = 0; 213 | this._numberOfDeleted = 0; 214 | this._size = 0; 215 | 216 | this._nextGrowthThreshold = _capacity * 4 / tLoadFactor; 217 | } 218 | 219 | public FastDictionary(int initialBucketCount = DictionaryHelper.kInitialCapacity) 220 | : this(initialBucketCount, EqualityComparer.Default) 221 | { } 222 | 223 | public void Add(TKey key, TValue value) 224 | { 225 | Contract.Ensures(this._numberOfUsed <= this._capacity); 226 | Contract.EndContractBlock(); 227 | 228 | if (key == null) 229 | throw new ArgumentNullException("key"); 230 | 231 | ResizeIfNeeded(); 232 | 233 | int hash = GetInternalHashCode(key); 234 | int bucket = hash % _capacity; 235 | 236 | uint uhash = (uint)hash; 237 | int numProbes = 1; 238 | do 239 | { 240 | uint nHash = _entries[bucket].Hash; 241 | if (nHash == kUnusedHash) 242 | { 243 | _numberOfUsed++; 244 | _size++; 245 | 246 | goto SET; 247 | } 248 | 249 | if (nHash == uhash && comparer.Equals(_entries[bucket].Key, key)) 250 | throw new ArgumentException("Cannot add duplicated key.", "key"); 251 | 252 | bucket = (bucket + numProbes) % _capacity; 253 | numProbes++; 254 | } 255 | while (true); 256 | 257 | SET: 258 | this._entries[bucket].Hash = uhash; 259 | this._entries[bucket].Key = key; 260 | this._entries[bucket].Value = value; 261 | } 262 | 263 | public bool Remove(TKey key) 264 | { 265 | Contract.Ensures(this._numberOfUsed < this._capacity); 266 | 267 | if (key == null) 268 | throw new ArgumentNullException("key"); 269 | 270 | int bucket = Lookup(key); 271 | if (bucket == InvalidNodePosition) 272 | return false; 273 | 274 | SetDeleted(bucket); 275 | 276 | return true; 277 | } 278 | 279 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 280 | private void SetDeleted(int node) 281 | { 282 | Contract.Ensures(_size <= Contract.OldValue(_size)); 283 | 284 | if (_entries[node].Hash < kDeletedHash) 285 | { 286 | _entries[node].Hash = kDeletedHash; 287 | _entries[node].Key = default(TKey); 288 | _entries[node].Value = default(TValue); 289 | 290 | _numberOfDeleted++; 291 | _size--; 292 | } 293 | 294 | Contract.Assert(_numberOfDeleted >= Contract.OldValue(_numberOfDeleted)); 295 | Contract.Assert(_entries[node].Hash == kDeletedHash); 296 | 297 | if (3 * this._numberOfDeleted / 2 > this._capacity - this._numberOfUsed) 298 | { 299 | // We will force a rehash with the growth factor based on the current size. 300 | Shrink(Math.Max(_initialCapacity, _size * 2)); 301 | } 302 | } 303 | 304 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 305 | private void ResizeIfNeeded() 306 | { 307 | if (_size >= _nextGrowthThreshold) 308 | { 309 | Grow(_capacity * 2); 310 | } 311 | } 312 | 313 | private void Shrink(int newCapacity) 314 | { 315 | Contract.Requires(newCapacity > _size); 316 | Contract.Ensures(this._numberOfUsed < this._capacity); 317 | 318 | // Calculate the next power of 2. 319 | newCapacity = Math.Max(DictionaryHelper.NextPowerOf2(newCapacity), _initialCapacity); 320 | 321 | var entries = new Entry[newCapacity]; 322 | BlockCopyMemoryHelper.Memset(entries, new Entry(kUnusedHash, default(TKey), default(TValue))); 323 | 324 | Rehash(entries); 325 | } 326 | 327 | 328 | public TValue this[TKey key] 329 | { 330 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 331 | get 332 | { 333 | Contract.Requires(key != null); 334 | Contract.Ensures(this._numberOfUsed <= this._capacity); 335 | 336 | int hash = GetInternalHashCode(key); 337 | int bucket = hash % _capacity; 338 | 339 | var entries = _entries; 340 | 341 | uint nHash; 342 | int numProbes = 1; 343 | do 344 | { 345 | nHash = entries[bucket].Hash; 346 | if (nHash == hash && comparer.Equals(entries[bucket].Key, key)) 347 | return entries[bucket].Value; 348 | 349 | bucket = (bucket + numProbes) % _capacity; 350 | numProbes++; 351 | 352 | Debug.Assert(numProbes < 100); 353 | } 354 | while (nHash != kUnusedHash); 355 | 356 | throw new KeyNotFoundException(); 357 | } 358 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 359 | set 360 | { 361 | Contract.Requires(key != null); 362 | Contract.Ensures(this._numberOfUsed <= this._capacity); 363 | 364 | ResizeIfNeeded(); 365 | 366 | int hash = GetInternalHashCode(key); 367 | int bucket = hash % _capacity; 368 | 369 | uint uhash = (uint)hash; 370 | int numProbes = 1; 371 | do 372 | { 373 | uint nHash = _entries[bucket].Hash; 374 | if (nHash == kUnusedHash) 375 | { 376 | _numberOfUsed++; 377 | _size++; 378 | 379 | goto SET; 380 | } 381 | 382 | if (nHash == uhash && comparer.Equals(_entries[bucket].Key, key)) 383 | goto SET; 384 | 385 | bucket = (bucket + numProbes) % _capacity; 386 | numProbes++; 387 | 388 | Debug.Assert(numProbes < 100); 389 | } 390 | while (true); 391 | 392 | SET: 393 | this._entries[bucket].Hash = uhash; 394 | this._entries[bucket].Key = key; 395 | this._entries[bucket].Value = value; 396 | } 397 | } 398 | 399 | public void Clear() 400 | { 401 | this._entries = new Entry[_capacity]; 402 | BlockCopyMemoryHelper.Memset(this._entries, new Entry(kUnusedHash, default(TKey), default(TValue))); 403 | 404 | this._numberOfUsed = 0; 405 | this._numberOfDeleted = 0; 406 | this._size = 0; 407 | } 408 | 409 | public bool Contains(TKey key) 410 | { 411 | Contract.Ensures(this._numberOfUsed <= this._capacity); 412 | 413 | if (key == null) 414 | throw new ArgumentNullException("key"); 415 | 416 | return (Lookup(key) != InvalidNodePosition); 417 | } 418 | 419 | private void Grow(int newCapacity) 420 | { 421 | Contract.Requires(newCapacity >= _capacity); 422 | Contract.Ensures((_capacity & (_capacity - 1)) == 0); 423 | 424 | var entries = new Entry[newCapacity]; 425 | BlockCopyMemoryHelper.Memset(entries, new Entry(kUnusedHash, default(TKey), default(TValue))); 426 | 427 | Rehash(entries); 428 | } 429 | 430 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 431 | public bool TryGetValue(TKey key, out TValue value) 432 | { 433 | Contract.Requires(key != null); 434 | Contract.Ensures(this._numberOfUsed <= this._capacity); 435 | 436 | int hash = GetInternalHashCode(key); 437 | int bucket = hash % _capacity; 438 | 439 | var entries = _entries; 440 | 441 | uint nHash; 442 | int numProbes = 1; 443 | do 444 | { 445 | nHash = entries[bucket].Hash; 446 | if (nHash == hash && comparer.Equals(entries[bucket].Key, key)) 447 | { 448 | value = entries[bucket].Value; 449 | return true; 450 | } 451 | 452 | bucket = (bucket + numProbes) % _capacity; 453 | numProbes++; 454 | 455 | Debug.Assert(numProbes < 100); 456 | } 457 | while (nHash != kUnusedHash); 458 | 459 | value = default(TValue); 460 | return false; 461 | 462 | } 463 | 464 | /// 465 | /// 466 | /// 467 | /// 468 | /// Position of the node in the array 469 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 470 | private int Lookup(TKey key) 471 | { 472 | int hash = GetInternalHashCode(key); 473 | int bucket = hash % _capacity; 474 | 475 | var entries = _entries; 476 | 477 | uint uhash = (uint)hash; 478 | uint numProbes = 1; // how many times we've probed 479 | 480 | uint nHash; 481 | do 482 | { 483 | nHash = entries[bucket].Hash; 484 | if (nHash == hash && comparer.Equals(entries[bucket].Key, key)) 485 | return bucket; 486 | 487 | bucket = (int)((bucket + numProbes) % _capacity); 488 | numProbes++; 489 | 490 | Debug.Assert(numProbes < 100); 491 | } 492 | while (nHash != kUnusedHash); 493 | 494 | return InvalidNodePosition; 495 | } 496 | 497 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 498 | private int GetInternalHashCode(TKey key) 499 | { 500 | return comparer.GetHashCode(key) & 0x7FFFFFFF; 501 | } 502 | 503 | private void Rehash(Entry[] entries) 504 | { 505 | uint capacity = (uint)entries.Length; 506 | 507 | var size = 0; 508 | 509 | for (int it = 0; it < _entries.Length; it++) 510 | { 511 | uint hash = _entries[it].Hash; 512 | if (hash >= kDeletedHash) // No interest for the process of rehashing, we are skipping it. 513 | continue; 514 | 515 | uint bucket = hash % capacity; 516 | 517 | uint numProbes = 0; 518 | while (!(entries[bucket].Hash == kUnusedHash)) 519 | { 520 | numProbes++; 521 | bucket = (bucket + numProbes) % capacity; 522 | } 523 | 524 | entries[bucket].Hash = hash; 525 | entries[bucket].Key = _entries[it].Key; 526 | entries[bucket].Value = _entries[it].Value; 527 | 528 | size++; 529 | } 530 | 531 | this._capacity = entries.Length; 532 | this._size = size; 533 | this._entries = entries; 534 | 535 | this._numberOfUsed = size; 536 | this._numberOfDeleted = 0; 537 | 538 | this._nextGrowthThreshold = _capacity * 4 / tLoadFactor; 539 | } 540 | 541 | 542 | public void CopyTo(KeyValuePair[] array, int index) 543 | { 544 | if (array == null) 545 | throw new ArgumentNullException("The array cannot be null", "array"); 546 | 547 | if (array.Rank != 1) 548 | throw new ArgumentException("Multiple dimensions array are not supporter", "array"); 549 | 550 | if (index < 0 || index > array.Length) 551 | throw new ArgumentOutOfRangeException("index"); 552 | 553 | if (array.Length - index < Count) 554 | throw new ArgumentException("The array plus the offset is too small."); 555 | 556 | int count = _capacity; 557 | 558 | var entries = _entries; 559 | 560 | for (int i = 0; i < count; i++) 561 | { 562 | if (entries[i].Hash < kDeletedHash) 563 | array[index++] = new KeyValuePair(entries[i].Key, entries[i].Value); 564 | } 565 | } 566 | 567 | IEnumerator IEnumerable.GetEnumerator() 568 | { 569 | return new Enumerator(this); 570 | } 571 | 572 | public IEnumerator> GetEnumerator() 573 | { 574 | return new Enumerator(this); 575 | } 576 | 577 | 578 | [Serializable] 579 | public struct Enumerator : IEnumerator> 580 | { 581 | private FastDictionary dictionary; 582 | private int index; 583 | private KeyValuePair current; 584 | 585 | internal const int DictEntry = 1; 586 | internal const int KeyValuePair = 2; 587 | 588 | internal Enumerator(FastDictionary dictionary) 589 | { 590 | this.dictionary = dictionary; 591 | this.index = 0; 592 | this.current = new KeyValuePair(); 593 | } 594 | 595 | public bool MoveNext() 596 | { 597 | var count = dictionary._capacity; 598 | var entries = dictionary._entries; 599 | 600 | // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends. 601 | // dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue 602 | while (index < count) 603 | { 604 | if (entries[index].Hash < kDeletedHash) 605 | { 606 | current = new KeyValuePair(entries[index].Key, entries[index].Value); 607 | index++; 608 | return true; 609 | } 610 | index++; 611 | } 612 | 613 | index = count + 1; 614 | current = new KeyValuePair(); 615 | return false; 616 | } 617 | 618 | public KeyValuePair Current 619 | { 620 | get { return current; } 621 | } 622 | 623 | public void Dispose() 624 | { 625 | } 626 | 627 | object IEnumerator.Current 628 | { 629 | get 630 | { 631 | if (index == 0 || (index == dictionary._capacity + 1)) 632 | throw new InvalidOperationException("Can't happen."); 633 | 634 | return new KeyValuePair(current.Key, current.Value); 635 | } 636 | } 637 | 638 | void IEnumerator.Reset() 639 | { 640 | index = 0; 641 | current = new KeyValuePair(); 642 | } 643 | } 644 | 645 | public KeyCollection Keys 646 | { 647 | get { return new KeyCollection(this); } 648 | } 649 | 650 | public ValueCollection Values 651 | { 652 | get { return new ValueCollection(this); } 653 | } 654 | 655 | public bool ContainsKey(TKey key) 656 | { 657 | if (key == null) 658 | throw new ArgumentNullException("key"); 659 | 660 | return (Lookup(key) != InvalidNodePosition); 661 | } 662 | 663 | public bool ContainsValue(TValue value) 664 | { 665 | var entries = _entries; 666 | int count = _capacity; 667 | 668 | if (value == null) 669 | { 670 | for (int i = 0; i < count; i++) 671 | { 672 | if (entries[i].Hash < kDeletedHash && entries[i].Value == null) 673 | return true; 674 | } 675 | } 676 | else 677 | { 678 | EqualityComparer c = EqualityComparer.Default; 679 | for (int i = 0; i < count; i++) 680 | { 681 | if (entries[i].Hash < kDeletedHash && c.Equals(entries[i].Value, value)) 682 | return true; 683 | } 684 | } 685 | return false; 686 | } 687 | 688 | 689 | public sealed class KeyCollection : IEnumerable, IEnumerable 690 | { 691 | private FastDictionary dictionary; 692 | 693 | public KeyCollection(FastDictionary dictionary) 694 | { 695 | Contract.Requires(dictionary != null); 696 | 697 | this.dictionary = dictionary; 698 | } 699 | 700 | public Enumerator GetEnumerator() 701 | { 702 | return new Enumerator(dictionary); 703 | } 704 | 705 | public void CopyTo(TKey[] array, int index) 706 | { 707 | if (array == null) 708 | throw new ArgumentNullException("The array cannot be null", "array"); 709 | 710 | if (index < 0 || index > array.Length) 711 | throw new ArgumentOutOfRangeException("index"); 712 | 713 | if (array.Length - index < dictionary.Count) 714 | throw new ArgumentException("The array plus the offset is too small."); 715 | 716 | int count = dictionary._capacity; 717 | var entries = dictionary._entries; 718 | 719 | for (int i = 0; i < count; i++) 720 | { 721 | if (entries[i].Hash < kDeletedHash) 722 | array[index++] = entries[i].Key; 723 | } 724 | } 725 | 726 | public int Count 727 | { 728 | get { return dictionary.Count; } 729 | } 730 | 731 | 732 | IEnumerator IEnumerable.GetEnumerator() 733 | { 734 | return new Enumerator(dictionary); 735 | } 736 | 737 | IEnumerator IEnumerable.GetEnumerator() 738 | { 739 | return new Enumerator(dictionary); 740 | } 741 | 742 | 743 | [Serializable] 744 | public struct Enumerator : IEnumerator, IEnumerator 745 | { 746 | private FastDictionary dictionary; 747 | private int index; 748 | private TKey currentKey; 749 | 750 | internal Enumerator(FastDictionary dictionary) 751 | { 752 | this.dictionary = dictionary; 753 | index = 0; 754 | currentKey = default(TKey); 755 | } 756 | 757 | public void Dispose() 758 | { 759 | } 760 | 761 | public bool MoveNext() 762 | { 763 | var count = dictionary._capacity; 764 | 765 | var entries = dictionary._entries; 766 | while (index < count) 767 | { 768 | if (entries[index].Hash < kDeletedHash) 769 | { 770 | currentKey = entries[index].Key; 771 | index++; 772 | return true; 773 | } 774 | index++; 775 | } 776 | 777 | index = count + 1; 778 | currentKey = default(TKey); 779 | return false; 780 | } 781 | 782 | public TKey Current 783 | { 784 | get 785 | { 786 | return currentKey; 787 | } 788 | } 789 | 790 | Object System.Collections.IEnumerator.Current 791 | { 792 | get 793 | { 794 | if (index == 0 || (index == dictionary.Count + 1)) 795 | throw new InvalidOperationException("Cant happen."); 796 | 797 | return currentKey; 798 | } 799 | } 800 | 801 | void System.Collections.IEnumerator.Reset() 802 | { 803 | index = 0; 804 | currentKey = default(TKey); 805 | } 806 | } 807 | } 808 | 809 | 810 | 811 | public sealed class ValueCollection : IEnumerable, IEnumerable 812 | { 813 | private FastDictionary dictionary; 814 | 815 | public ValueCollection(FastDictionary dictionary) 816 | { 817 | Contract.Requires(dictionary != null); 818 | 819 | this.dictionary = dictionary; 820 | } 821 | 822 | public Enumerator GetEnumerator() 823 | { 824 | return new Enumerator(dictionary); 825 | } 826 | 827 | public void CopyTo(TValue[] array, int index) 828 | { 829 | if (array == null) 830 | throw new ArgumentNullException("The array cannot be null", "array"); 831 | 832 | if (index < 0 || index > array.Length) 833 | throw new ArgumentOutOfRangeException("index"); 834 | 835 | if (array.Length - index < dictionary.Count) 836 | throw new ArgumentException("The array plus the offset is too small."); 837 | 838 | int count = dictionary._capacity; 839 | 840 | var entries = dictionary._entries; 841 | for (int i = 0; i < count; i++) 842 | { 843 | if (entries[i].Hash < kDeletedHash) 844 | array[index++] = entries[i].Value; 845 | } 846 | } 847 | 848 | public int Count 849 | { 850 | get { return dictionary.Count; } 851 | } 852 | 853 | IEnumerator IEnumerable.GetEnumerator() 854 | { 855 | return new Enumerator(dictionary); 856 | } 857 | 858 | IEnumerator IEnumerable.GetEnumerator() 859 | { 860 | return new Enumerator(dictionary); 861 | } 862 | 863 | 864 | [Serializable] 865 | public struct Enumerator : IEnumerator, IEnumerator 866 | { 867 | private FastDictionary dictionary; 868 | private int index; 869 | private TValue currentValue; 870 | 871 | internal Enumerator(FastDictionary dictionary) 872 | { 873 | this.dictionary = dictionary; 874 | index = 0; 875 | currentValue = default(TValue); 876 | } 877 | 878 | public void Dispose() 879 | { 880 | } 881 | 882 | public bool MoveNext() 883 | { 884 | var count = dictionary._capacity; 885 | 886 | var entries = dictionary._entries; 887 | while (index < count) 888 | { 889 | if (entries[index].Hash < kDeletedHash) 890 | { 891 | currentValue = entries[index].Value; 892 | index++; 893 | return true; 894 | } 895 | index++; 896 | } 897 | 898 | index = count + 1; 899 | currentValue = default(TValue); 900 | return false; 901 | } 902 | 903 | public TValue Current 904 | { 905 | get 906 | { 907 | return currentValue; 908 | } 909 | } 910 | Object IEnumerator.Current 911 | { 912 | get 913 | { 914 | if (index == 0 || (index == dictionary.Count + 1)) 915 | throw new InvalidOperationException("Cant happen."); 916 | 917 | return currentValue; 918 | } 919 | } 920 | 921 | void IEnumerator.Reset() 922 | { 923 | index = 0; 924 | currentValue = default(TValue); 925 | } 926 | } 927 | } 928 | 929 | private class BlockCopyMemoryHelper 930 | { 931 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 932 | public static void Memset(Entry[] array, Entry value) 933 | { 934 | int block = 64, index = 0; 935 | int length = Math.Min(block, array.Length); 936 | 937 | //Fill the initial array 938 | while (index < length) 939 | { 940 | array[index++] = value; 941 | } 942 | 943 | length = array.Length; 944 | while (index < length) 945 | { 946 | Array.Copy(array, 0, array, index, Math.Min(block, (length - index))); 947 | index += block; 948 | 949 | block *= 2; 950 | } 951 | } 952 | } 953 | } 954 | } 955 | --------------------------------------------------------------------------------