├── .gitignore ├── ByteArrayComparer.cs ├── ByteArrayHashtable.cs ├── FarmHash.cs ├── FastHashtable.csproj ├── FuncExtensions.cs ├── ReadMe.md ├── StringKeyHashtable.cs ├── ThreadSafeHashTable.cs ├── ThreadsafeFixedSizeHashtable.cs └── TypeKeyHashable.cs /.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 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | #*.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.jfm 192 | *.pfx 193 | *.publishsettings 194 | node_modules/ 195 | orleans.codegen.cs 196 | 197 | # Since there are multiple workflows, uncomment next line to ignore bower_components 198 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 199 | #bower_components/ 200 | 201 | # RIA/Silverlight projects 202 | Generated_Code/ 203 | 204 | # Backup & report files from converting an old project file 205 | # to a newer Visual Studio version. Backup files are not needed, 206 | # because we have git ;-) 207 | _UpgradeReport_Files/ 208 | Backup*/ 209 | UpgradeLog*.XML 210 | UpgradeLog*.htm 211 | 212 | # SQL Server files 213 | *.mdf 214 | *.ldf 215 | 216 | # Business Intelligence projects 217 | *.rdl.data 218 | *.bim.layout 219 | *.bim_*.settings 220 | 221 | # Microsoft Fakes 222 | FakesAssemblies/ 223 | 224 | # GhostDoc plugin setting file 225 | *.GhostDoc.xml 226 | 227 | # Node.js Tools for Visual Studio 228 | .ntvs_analysis.dat 229 | 230 | # Visual Studio 6 build log 231 | *.plg 232 | 233 | # Visual Studio 6 workspace options file 234 | *.opt 235 | 236 | # Visual Studio LightSwitch build output 237 | **/*.HTMLClient/GeneratedArtifacts 238 | **/*.DesktopClient/GeneratedArtifacts 239 | **/*.DesktopClient/ModelManifest.xml 240 | **/*.Server/GeneratedArtifacts 241 | **/*.Server/ModelManifest.xml 242 | _Pvt_Extensions 243 | 244 | # Paket dependency manager 245 | .paket/paket.exe 246 | paket-files/ 247 | 248 | # FAKE - F# Make 249 | .fake/ 250 | 251 | # JetBrains Rider 252 | .idea/ 253 | *.sln.iml 254 | 255 | # CodeRush 256 | .cr/ 257 | 258 | # Python Tools for Visual Studio (PTVS) 259 | __pycache__/ 260 | *.pyc 261 | 262 | # Unity 263 | 264 | src/MessagePack.UnityClient/bin/* 265 | src/MessagePack.UnityClient/Library/* 266 | src/MessagePack.UnityClient/obj/* 267 | src/MessagePack.UnityClient/Temp/* 268 | 269 | # Project Specified 270 | 271 | nuget/mpc.exe 272 | nuget/mpc.exe.config 273 | 274 | nuget/tools/* 275 | nuget/unity/tools/* 276 | nuget/unity* 277 | /nuget/*.unitypackage 278 | -------------------------------------------------------------------------------- /ByteArrayComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace FastHashtable 5 | { 6 | public static class ByteArrayComparer 7 | { 8 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 9 | public static int GetHashCode(byte[] bytes) 10 | { 11 | return GetHashCode(bytes, 0, bytes.Length); 12 | } 13 | 14 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 15 | public static int GetHashCode(byte[] bytes, int offset, int count) 16 | { 17 | if (IntPtr.Size == 4) 18 | { 19 | return unchecked((int)FarmHash.Hash32(bytes, offset, count)); 20 | } 21 | else 22 | { 23 | return unchecked((int)FarmHash.Hash64(bytes, offset, count)); 24 | } 25 | } 26 | 27 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 28 | public static unsafe bool Equals(byte[] xs, byte[] ys) 29 | { 30 | return Equals(xs, 0, xs.Length, ys, 0, ys.Length); 31 | } 32 | 33 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 34 | public static unsafe bool Equals(byte[] xs, int xsOffset, int xsCount, byte[] ys, int ysOffset, int ysCount) 35 | { 36 | if (IntPtr.Size == 4) 37 | { 38 | return Equals32(xs, xsOffset, xsCount, ys, ysOffset, ysCount); 39 | } 40 | else 41 | { 42 | return Equals64(xs, xsOffset, xsCount, ys, ysOffset, ysCount); 43 | } 44 | } 45 | 46 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 47 | public static unsafe bool Equals32(byte[] xs, int xsOffset, int xsCount, byte[] ys, int ysOffset, int ysCount) 48 | { 49 | if (xs == null && ys == null) return true; 50 | if (xs == null || ys == null || xsCount != ysCount) 51 | { 52 | return false; 53 | } 54 | if (xsCount == 0 && ysCount == 0) return true; 55 | 56 | fixed (byte* p1 = &xs[xsOffset]) 57 | fixed (byte* p2 = &ys[ysOffset]) 58 | { 59 | switch (xsCount) 60 | { 61 | case 0: 62 | return true; 63 | case 1: 64 | return *p1 == *p2; 65 | case 2: 66 | return *(ushort*)p1 == *(ushort*)p2; 67 | case 3: 68 | if (*(byte*)p1 != *(byte*)p2) return false; 69 | return *(ushort*)(p1 + 1) == *(ushort*)(p2 + 1); 70 | case 4: 71 | return *(uint*)p1 == *(uint*)p2; 72 | default: 73 | { 74 | var x1 = p1; 75 | var x2 = p2; 76 | 77 | byte* xEnd = p1 + xsCount - 4; 78 | byte* yEnd = p2 + ysCount - 4; 79 | 80 | while (x1 < xEnd) 81 | { 82 | if (*(uint*)x1 != *(uint*)x2) 83 | { 84 | return false; 85 | } 86 | 87 | x1 += 8; 88 | x2 += 8; 89 | } 90 | 91 | return *(uint*)xEnd == *(uint*)yEnd; 92 | } 93 | } 94 | } 95 | } 96 | 97 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 98 | public static unsafe bool Equals64(byte[] xs, int xsOffset, int xsCount, byte[] ys, int ysOffset, int ysCount) 99 | { 100 | if (xs == null && ys == null) return true; 101 | if (xs == null || ys == null || xsCount != ysCount) 102 | { 103 | return false; 104 | } 105 | 106 | fixed (byte* p1 = &xs[xsOffset]) 107 | fixed (byte* p2 = &ys[ysOffset]) 108 | { 109 | switch (xsCount) 110 | { 111 | case 0: 112 | return true; 113 | case 1: 114 | return *p1 == *p2; 115 | case 2: 116 | return *(ushort*)p1 == *(ushort*)p2; 117 | case 3: 118 | if (*(byte*)p1 != *(byte*)p2) return false; 119 | return *(ushort*)(p1 + 1) == *(ushort*)(p2 + 1); 120 | case 4: 121 | return *(uint*)p1 == *(uint*)p2; 122 | case 5: 123 | if (*(byte*)p1 != *(byte*)p2) return false; 124 | return *(uint*)(p1 + 1) == *(uint*)(p2 + 1); 125 | case 6: 126 | if (*(ushort*)p1 != *(ushort*)p2) return false; 127 | return *(uint*)(p1 + 2) == *(uint*)(p2 + 2); 128 | case 7: 129 | if (*(byte*)p1 != *(byte*)p2) return false; 130 | if (*(short*)(p1 + 1) != *(short*)(p2 + 1)) return false; 131 | return *(uint*)(p1 + 3) == *(uint*)(p2 + 3); 132 | case 8: 133 | return *(ulong*)p1 == *(ulong*)p2; 134 | default: 135 | { 136 | var x1 = p1; 137 | var x2 = p2; 138 | 139 | byte* xEnd = p1 + xsCount - 8; 140 | byte* yEnd = p2 + ysCount - 8; 141 | 142 | while (x1 < xEnd) 143 | { 144 | if (*(ulong*)x1 != *(ulong*)x2) 145 | { 146 | return false; 147 | } 148 | 149 | x1 += 8; 150 | x2 += 8; 151 | } 152 | 153 | return *(ulong*)xEnd == *(ulong*)yEnd; 154 | } 155 | } 156 | } 157 | } 158 | } 159 | 160 | public class ByteArrayEqualityComaprer : IEqualityComparer 161 | { 162 | public static readonly IEqualityComparer Default = new ByteArrayEqualityComaprer(); 163 | 164 | public bool Equals(byte[] x, byte[] y) 165 | { 166 | return ByteArrayComparer.Equals(x, y); 167 | } 168 | 169 | public int GetHashCode(byte[] obj) 170 | { 171 | return ByteArrayComparer.GetHashCode(obj); 172 | } 173 | } 174 | 175 | public class ByteArraySegmentEqualityComaprer : IEqualityComparer> 176 | { 177 | public static readonly IEqualityComparer Default = new ByteArrayEqualityComaprer(); 178 | 179 | public bool Equals(ArraySegment x, ArraySegment y) 180 | { 181 | return ByteArrayComparer.Equals(x.Array, x.Offset, x.Count, y.Array, y.Offset, y.Count); 182 | } 183 | 184 | public int GetHashCode(ArraySegment obj) 185 | { 186 | return ByteArrayComparer.GetHashCode(obj.Array, obj.Offset, obj.Count); 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /ByteArrayHashtable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace FastHashtable 5 | { 6 | public sealed class ByteArrayHashtable : ThreadSafeFixedSizeHashtable, TValue> 7 | { 8 | public ByteArrayHashtable(IEnumerable> values) : base(values) 9 | { 10 | } 11 | 12 | public ByteArrayHashtable(IEnumerable> values, float loadFactor) : base(values, loadFactor) 13 | { 14 | } 15 | 16 | public override int GetHashCode(byte[] key) 17 | { 18 | if (key == null || key.Length == 0) return 0; 19 | return ByteArrayComparer.GetHashCode(key); 20 | } 21 | 22 | public override int GetHashCode(ArraySegment key) 23 | { 24 | if (key.Array == null || key.Count == 0) return 0; 25 | return ByteArrayComparer.GetHashCode(key.Array, key.Offset, key.Count); 26 | } 27 | 28 | public override bool KeyEquals(ArraySegment key1, byte[] key2) 29 | { 30 | if (key1.Array == null && key2 == null) return true; 31 | if (key1.Array == null || key2 == null) return false; 32 | return ByteArrayComparer.Equals(key1.Array, key1.Offset, key1.Count, key2, 0, key2.Length); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /FarmHash.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace FastHashtable 4 | { 5 | public static class FarmHash 6 | { 7 | // entry point of 32bit 8 | 9 | #region Hash32 10 | 11 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 12 | public static unsafe uint Hash32(byte[] bytes, int offset, int count) 13 | { 14 | if (count <= 4) 15 | { 16 | return Hash32Len0to4(bytes, offset, (uint)count); 17 | } 18 | 19 | fixed (byte* p = &bytes[offset]) 20 | { 21 | return Hash32(p, (uint)count); 22 | } 23 | } 24 | 25 | // port of farmhash.cc, 32bit only 26 | 27 | // Magic numbers for 32-bit hashing. Copied from Murmur3. 28 | const uint c1 = 0xcc9e2d51; 29 | const uint c2 = 0x1b873593; 30 | 31 | static unsafe uint Fetch32(byte* p) 32 | { 33 | return *(uint*)p; 34 | } 35 | 36 | static uint Rotate32(uint val, int shift) 37 | { 38 | return shift == 0 ? val : ((val >> shift) | (val << (32 - shift))); 39 | } 40 | 41 | // A 32-bit to 32-bit integer hash copied from Murmur3. 42 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 43 | static uint fmix(uint h) 44 | { 45 | unchecked 46 | { 47 | h ^= h >> 16; 48 | h *= 0x85ebca6b; 49 | h ^= h >> 13; 50 | h *= 0xc2b2ae35; 51 | h ^= h >> 16; 52 | return h; 53 | } 54 | } 55 | 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | static uint Mur(uint a, uint h) 58 | { 59 | unchecked 60 | { 61 | // Helper from Murmur3 for combining two 32-bit values. 62 | a *= c1; 63 | a = Rotate32(a, 17); 64 | a *= c2; 65 | h ^= a; 66 | h = Rotate32(h, 19); 67 | return h * 5 + 0xe6546b64; 68 | } 69 | } 70 | 71 | // 0-4 72 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 73 | static unsafe uint Hash32Len0to4(byte[] s, int offset, uint len) 74 | { 75 | unchecked 76 | { 77 | uint b = 0; 78 | uint c = 9; 79 | var max = offset + len; 80 | for (int i = offset; i < max; i++) 81 | { 82 | b = b * c1 + s[i]; 83 | c ^= b; 84 | } 85 | return fmix(Mur(b, Mur(len, c))); 86 | } 87 | } 88 | 89 | // 5-12 90 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 91 | static unsafe uint Hash32Len5to12(byte* s, uint len) 92 | { 93 | unchecked 94 | { 95 | uint a = len, b = len * 5, c = 9, d = b; 96 | a += Fetch32(s); 97 | b += Fetch32(s + len - 4); 98 | c += Fetch32(s + ((len >> 1) & 4)); 99 | return fmix(Mur(c, Mur(b, Mur(a, d)))); 100 | } 101 | } 102 | 103 | // 13-24 104 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 105 | static unsafe uint Hash32Len13to24(byte* s, uint len) 106 | { 107 | unchecked 108 | { 109 | uint a = Fetch32(s - 4 + (len >> 1)); 110 | uint b = Fetch32(s + 4); 111 | uint c = Fetch32(s + len - 8); 112 | uint d = Fetch32(s + (len >> 1)); 113 | uint e = Fetch32(s); 114 | uint f = Fetch32(s + len - 4); 115 | uint h = d * c1 + len; 116 | a = Rotate32(a, 12) + f; 117 | h = Mur(c, h) + a; 118 | a = Rotate32(a, 3) + c; 119 | h = Mur(e, h) + a; 120 | a = Rotate32(a + f, 12) + d; 121 | h = Mur(b, h) + a; 122 | return fmix(h); 123 | } 124 | } 125 | 126 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 127 | static unsafe uint Hash32(byte* s, uint len) 128 | { 129 | if (len <= 24) 130 | { 131 | return len <= 12 ? Hash32Len5to12(s, len) : Hash32Len13to24(s, len); 132 | } 133 | 134 | unchecked 135 | { 136 | // len > 24 137 | uint h = len, g = c1 * len, f = g; 138 | uint a0 = Rotate32(Fetch32(s + len - 4) * c1, 17) * c2; 139 | uint a1 = Rotate32(Fetch32(s + len - 8) * c1, 17) * c2; 140 | uint a2 = Rotate32(Fetch32(s + len - 16) * c1, 17) * c2; 141 | uint a3 = Rotate32(Fetch32(s + len - 12) * c1, 17) * c2; 142 | uint a4 = Rotate32(Fetch32(s + len - 20) * c1, 17) * c2; 143 | h ^= a0; 144 | h = Rotate32(h, 19); 145 | h = h * 5 + 0xe6546b64; 146 | h ^= a2; 147 | h = Rotate32(h, 19); 148 | h = h * 5 + 0xe6546b64; 149 | g ^= a1; 150 | g = Rotate32(g, 19); 151 | g = g * 5 + 0xe6546b64; 152 | g ^= a3; 153 | g = Rotate32(g, 19); 154 | g = g * 5 + 0xe6546b64; 155 | f += a4; 156 | f = Rotate32(f, 19) + 113; 157 | uint iters = (len - 1) / 20; 158 | do 159 | { 160 | uint a = Fetch32(s); 161 | uint b = Fetch32(s + 4); 162 | uint c = Fetch32(s + 8); 163 | uint d = Fetch32(s + 12); 164 | uint e = Fetch32(s + 16); 165 | h += a; 166 | g += b; 167 | f += c; 168 | h = Mur(d, h) + e; 169 | g = Mur(c, g) + a; 170 | f = Mur(b + e * c1, f) + d; 171 | f += g; 172 | g += f; 173 | s += 20; 174 | } while (--iters != 0); 175 | g = Rotate32(g, 11) * c1; 176 | g = Rotate32(g, 17) * c1; 177 | f = Rotate32(f, 11) * c1; 178 | f = Rotate32(f, 17) * c1; 179 | h = Rotate32(h + g, 19); 180 | h = h * 5 + 0xe6546b64; 181 | h = Rotate32(h, 17) * c1; 182 | h = Rotate32(h + f, 19); 183 | h = h * 5 + 0xe6546b64; 184 | h = Rotate32(h, 17) * c1; 185 | return h; 186 | } 187 | } 188 | 189 | #endregion 190 | 191 | #region hash64 192 | 193 | // entry point 194 | 195 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 196 | public static unsafe ulong Hash64(byte[] bytes, int offset, int count) 197 | { 198 | fixed (byte* p = &bytes[offset]) 199 | { 200 | return Hash64(p, (uint)count); 201 | } 202 | } 203 | 204 | // port from farmhash.cc 205 | 206 | struct pair 207 | { 208 | public ulong first; 209 | public ulong second; 210 | 211 | public pair(ulong first, ulong second) 212 | { 213 | this.first = first; 214 | this.second = second; 215 | } 216 | } 217 | 218 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 219 | static pair make_pair(ulong first, ulong second) 220 | { 221 | return new pair(first, second); 222 | } 223 | 224 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 225 | static void swap(ref ulong x, ref ulong z) 226 | { 227 | var temp = z; 228 | z = x; 229 | x = temp; 230 | } 231 | 232 | // Some primes between 2^63 and 2^64 for various uses. 233 | const ulong k0 = 0xc3a5c85c97cb3127UL; 234 | const ulong k1 = 0xb492b66fbe98f273UL; 235 | const ulong k2 = 0x9ae16a3b2f90404fUL; 236 | 237 | static unsafe ulong Fetch64(byte* p) 238 | { 239 | return *(ulong*)p; 240 | } 241 | 242 | static ulong Rotate64(ulong val, int shift) 243 | { 244 | return shift == 0 ? val : (val >> shift) | (val << (64 - shift)); 245 | } 246 | 247 | // farmhashna.cc 248 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 249 | static ulong ShiftMix(ulong val) 250 | { 251 | return val ^ (val >> 47); 252 | } 253 | 254 | // farmhashna.cc 255 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 256 | static ulong HashLen16(ulong u, ulong v, ulong mul) 257 | { 258 | unchecked 259 | { 260 | // Murmur-inspired hashing. 261 | ulong a = (u ^ v) * mul; 262 | a ^= a >> 47; 263 | ulong b = (v ^ a) * mul; 264 | b ^= b >> 47; 265 | b *= mul; 266 | return b; 267 | } 268 | } 269 | 270 | // farmhashxo.cc 271 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 272 | static unsafe ulong Hash64(byte* s, uint len) 273 | { 274 | if (len <= 16) 275 | { 276 | // farmhashna:: 277 | return HashLen0to16(s, len); 278 | } 279 | 280 | if (len <= 32) 281 | { 282 | // farmhashna:: 283 | return HashLen17to32(s, len); 284 | } 285 | 286 | if (len <= 64) 287 | { 288 | return HashLen33to64(s, len); 289 | } 290 | 291 | if (len <= 96) 292 | { 293 | return HashLen65to96(s, len); 294 | } 295 | 296 | if (len <= 256) 297 | { 298 | // farmhashna:: 299 | return Hash64NA(s, len); 300 | } 301 | 302 | // farmhashuo:: 303 | return Hash64UO(s, len); 304 | } 305 | 306 | // 0-16 farmhashna.cc 307 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 308 | static unsafe ulong HashLen0to16(byte* s, uint len) 309 | { 310 | unchecked 311 | { 312 | if (len >= 8) 313 | { 314 | ulong mul = k2 + len * 2; 315 | ulong a = Fetch64(s) + k2; 316 | ulong b = Fetch64(s + len - 8); 317 | ulong c = Rotate64(b, 37) * mul + a; 318 | ulong d = (Rotate64(a, 25) + b) * mul; 319 | return HashLen16(c, d, mul); 320 | } 321 | if (len >= 4) 322 | { 323 | ulong mul = k2 + len * 2; 324 | ulong a = Fetch32(s); 325 | return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul); 326 | } 327 | if (len > 0) 328 | { 329 | ushort a = s[0]; 330 | ushort b = s[len >> 1]; 331 | ushort c = s[len - 1]; 332 | uint y = a + ((uint)b << 8); 333 | uint z = len + ((uint)c << 2); 334 | return ShiftMix(y * k2 ^ z * k0) * k2; 335 | } 336 | return k2; 337 | } 338 | } 339 | 340 | // 17-32 farmhashna.cc 341 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 342 | static unsafe ulong HashLen17to32(byte* s, uint len) 343 | { 344 | unchecked 345 | { 346 | ulong mul = k2 + len * 2; 347 | ulong a = Fetch64(s) * k1; 348 | ulong b = Fetch64(s + 8); 349 | ulong c = Fetch64(s + len - 8) * mul; 350 | ulong d = Fetch64(s + len - 16) * k2; 351 | return HashLen16(Rotate64(a + b, 43) + Rotate64(c, 30) + d, 352 | a + Rotate64(b + k2, 18) + c, mul); 353 | } 354 | } 355 | 356 | // farmhashxo.cc 357 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 358 | static unsafe ulong H32(byte* s, uint len, ulong mul, ulong seed0 = 0, ulong seed1 = 0) 359 | { 360 | unchecked 361 | { 362 | ulong a = Fetch64(s) * k1; 363 | ulong b = Fetch64(s + 8); 364 | ulong c = Fetch64(s + len - 8) * mul; 365 | ulong d = Fetch64(s + len - 16) * k2; 366 | ulong u = Rotate64(a + b, 43) + Rotate64(c, 30) + d + seed0; 367 | ulong v = a + Rotate64(b + k2, 18) + c + seed1; 368 | a = ShiftMix((u ^ v) * mul); 369 | b = ShiftMix((v ^ a) * mul); 370 | return b; 371 | } 372 | } 373 | 374 | // 33-64 farmhashxo.cc 375 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 376 | static unsafe ulong HashLen33to64(byte* s, uint len) 377 | { 378 | const ulong mul0 = k2 - 30; 379 | 380 | unchecked 381 | { 382 | ulong mul1 = k2 - 30 + 2 * len; 383 | ulong h0 = H32(s, 32, mul0); 384 | ulong h1 = H32(s + len - 32, 32, mul1); 385 | return (h1 * mul1 + h0) * mul1; 386 | } 387 | } 388 | 389 | // 65-96 farmhashxo.cc 390 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 391 | static unsafe ulong HashLen65to96(byte* s, uint len) 392 | { 393 | const ulong mul0 = k2 - 114; 394 | 395 | unchecked 396 | { 397 | ulong mul1 = k2 - 114 + 2 * len; 398 | ulong h0 = H32(s, 32, mul0); 399 | ulong h1 = H32(s + 32, 32, mul1); 400 | ulong h2 = H32(s + len - 32, 32, mul1, h0, h1); 401 | return (h2 * 9 + (h0 >> 17) + (h1 >> 21)) * mul1; 402 | } 403 | } 404 | 405 | // farmhashna.cc 406 | // Return a 16-byte hash for 48 bytes. Quick and dirty. 407 | // Callers do best to use "random-looking" values for a and b. 408 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 409 | static unsafe pair WeakHashLen32WithSeeds(ulong w, ulong x, ulong y, ulong z, ulong a, ulong b) 410 | { 411 | unchecked 412 | { 413 | a += w; 414 | b = Rotate64(b + a + z, 21); 415 | ulong c = a; 416 | a += x; 417 | a += y; 418 | b += Rotate64(a, 44); 419 | return make_pair(a + z, b + c); 420 | } 421 | } 422 | 423 | // farmhashna.cc 424 | // Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty. 425 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 426 | static unsafe pair WeakHashLen32WithSeeds(byte* s, ulong a, ulong b) 427 | { 428 | return WeakHashLen32WithSeeds(Fetch64(s), 429 | Fetch64(s + 8), 430 | Fetch64(s + 16), 431 | Fetch64(s + 24), 432 | a, 433 | b); 434 | } 435 | 436 | // na(97-256) farmhashna.cc 437 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 438 | static unsafe ulong Hash64NA(byte* s, uint len) 439 | { 440 | const ulong seed = 81; 441 | 442 | unchecked 443 | { 444 | // For strings over 64 bytes we loop. Internal state consists of 445 | // 56 bytes: v, w, x, y, and z. 446 | ulong x = seed; 447 | ulong y = seed * k1 + 113; 448 | ulong z = ShiftMix(y * k2 + 113) * k2; 449 | var v = make_pair(0, 0); 450 | var w = make_pair(0, 0); 451 | x = x * k2 + Fetch64(s); 452 | 453 | // Set end so that after the loop we have 1 to 64 bytes left to process. 454 | byte* end = s + ((len - 1) / 64) * 64; 455 | byte* last64 = end + ((len - 1) & 63) - 63; 456 | 457 | do 458 | { 459 | x = Rotate64(x + y + v.first + Fetch64(s + 8), 37) * k1; 460 | y = Rotate64(y + v.second + Fetch64(s + 48), 42) * k1; 461 | x ^= w.second; 462 | y += v.first + Fetch64(s + 40); 463 | z = Rotate64(z + w.first, 33) * k1; 464 | v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); 465 | w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); 466 | swap(ref z, ref x); 467 | s += 64; 468 | } while (s != end); 469 | ulong mul = k1 + ((z & 0xff) << 1); 470 | // Make s point to the last 64 bytes of input. 471 | s = last64; 472 | w.first += ((len - 1) & 63); 473 | v.first += w.first; 474 | w.first += v.first; 475 | x = Rotate64(x + y + v.first + Fetch64(s + 8), 37) * mul; 476 | y = Rotate64(y + v.second + Fetch64(s + 48), 42) * mul; 477 | x ^= w.second * 9; 478 | y += v.first * 9 + Fetch64(s + 40); 479 | z = Rotate64(z + w.first, 33) * mul; 480 | v = WeakHashLen32WithSeeds(s, v.second * mul, x + w.first); 481 | w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); 482 | swap(ref z, ref x); 483 | return HashLen16(HashLen16(v.first, w.first, mul) + ShiftMix(y) * k0 + z, 484 | HashLen16(v.second, w.second, mul) + x, 485 | mul); 486 | } 487 | } 488 | 489 | // farmhashuo.cc 490 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 491 | static ulong H(ulong x, ulong y, ulong mul, int r) 492 | { 493 | unchecked 494 | { 495 | ulong a = (x ^ y) * mul; 496 | a ^= (a >> 47); 497 | ulong b = (y ^ a) * mul; 498 | return Rotate64(b, r) * mul; 499 | } 500 | } 501 | 502 | // uo(257-) farmhashuo.cc, Hash64WithSeeds 503 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 504 | static unsafe ulong Hash64UO(byte* s, uint len) 505 | { 506 | const ulong seed0 = 81; 507 | const ulong seed1 = 0; 508 | 509 | unchecked 510 | { 511 | // For strings over 64 bytes we loop. Internal state consists of 512 | // 64 bytes: u, v, w, x, y, and z. 513 | ulong x = seed0; 514 | ulong y = seed1 * k2 + 113; 515 | ulong z = ShiftMix(y * k2) * k2; 516 | var v = make_pair(seed0, seed1); 517 | var w = make_pair(0, 0); 518 | ulong u = x - z; 519 | x *= k2; 520 | ulong mul = k2 + (u & 0x82); 521 | 522 | // Set end so that after the loop we have 1 to 64 bytes left to process. 523 | byte* end = s + ((len - 1) / 64) * 64; 524 | byte* last64 = end + ((len - 1) & 63) - 63; 525 | 526 | do 527 | { 528 | ulong a0 = Fetch64(s); 529 | ulong a1 = Fetch64(s + 8); 530 | ulong a2 = Fetch64(s + 16); 531 | ulong a3 = Fetch64(s + 24); 532 | ulong a4 = Fetch64(s + 32); 533 | ulong a5 = Fetch64(s + 40); 534 | ulong a6 = Fetch64(s + 48); 535 | ulong a7 = Fetch64(s + 56); 536 | x += a0 + a1; 537 | y += a2; 538 | z += a3; 539 | v.first += a4; 540 | v.second += a5 + a1; 541 | w.first += a6; 542 | w.second += a7; 543 | 544 | x = Rotate64(x, 26); 545 | x *= 9; 546 | y = Rotate64(y, 29); 547 | z *= mul; 548 | v.first = Rotate64(v.first, 33); 549 | v.second = Rotate64(v.second, 30); 550 | w.first ^= x; 551 | w.first *= 9; 552 | z = Rotate64(z, 32); 553 | z += w.second; 554 | w.second += z; 555 | z *= 9; 556 | swap(ref u, ref y); 557 | 558 | z += a0 + a6; 559 | v.first += a2; 560 | v.second += a3; 561 | w.first += a4; 562 | w.second += a5 + a6; 563 | x += a1; 564 | y += a7; 565 | 566 | y += v.first; 567 | v.first += x - y; 568 | v.second += w.first; 569 | w.first += v.second; 570 | w.second += x - y; 571 | x += w.second; 572 | w.second = Rotate64(w.second, 34); 573 | swap(ref u, ref z); 574 | s += 64; 575 | } while (s != end); 576 | // Make s point to the last 64 bytes of input. 577 | s = last64; 578 | u *= 9; 579 | v.second = Rotate64(v.second, 28); 580 | v.first = Rotate64(v.first, 20); 581 | w.first += ((len - 1) & 63); 582 | u += y; 583 | y += u; 584 | x = Rotate64(y - x + v.first + Fetch64(s + 8), 37) * mul; 585 | y = Rotate64(y ^ v.second ^ Fetch64(s + 48), 42) * mul; 586 | x ^= w.second * 9; 587 | y += v.first + Fetch64(s + 40); 588 | z = Rotate64(z + w.first, 33) * mul; 589 | v = WeakHashLen32WithSeeds(s, v.second * mul, x + w.first); 590 | w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); 591 | return H(HashLen16(v.first + x, w.first ^ y, mul) + z - u, 592 | H(v.second + y, w.second + z, k2, 30) ^ x, 593 | k2, 594 | 31); 595 | } 596 | } 597 | 598 | #endregion 599 | } 600 | } -------------------------------------------------------------------------------- /FastHashtable.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | true 9 | 10 | 11 | 12 | true 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /FuncExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FastHashtable 4 | { 5 | internal static class FuncExtensions 6 | { 7 | // hack of avoid closure allocation(() => value). 8 | public static Func AsFunc(this T value) 9 | { 10 | return new Func(value.ReturnBox); 11 | } 12 | 13 | static T ReturnBox(this object value, TIgnore _) 14 | { 15 | return (T)value; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | FastHashtable 2 | === 3 | Infrastructure for high performance code which used in [MessagePack for C#](https://github.com/neuecc/MessagePack-CSharp/) and [Utf8Json](https://github.com/neuecc/Utf8Json/). 4 | 5 | `ThreadSafeFixedSizeHashtable`, `ThreadSafeHashtable`, `ByteArrayComparer`, `FarmHash`. 6 | 7 | // TODO:I'll add benchmark and how to use document and upload to NuGet. 8 | -------------------------------------------------------------------------------- /StringKeyHashtable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace FastHashtable 5 | { 6 | public sealed class StringKeyFixedSizeHashable : ThreadSafeFixedSizeHashtable 7 | { 8 | public StringKeyFixedSizeHashable(IEnumerable> values) : base(values) 9 | { 10 | } 11 | 12 | public StringKeyFixedSizeHashable(IEnumerable> values, float loadFactor) : base(values, loadFactor) 13 | { 14 | } 15 | 16 | public override bool KeyEquals(string key1, string key2) 17 | { 18 | return key1 == key2; 19 | } 20 | 21 | public override int KeyGetHashCode(string key) 22 | { 23 | if (key == null) return 0; 24 | return key.GetHashCode(); 25 | } 26 | } 27 | 28 | public sealed class StringKeyHashtable : ThreadSafeHashtable 29 | { 30 | public StringKeyHashtable(int capacity = 4, float loadFactor = 0.75F) 31 | : base(capacity, loadFactor) 32 | { 33 | } 34 | 35 | public override bool KeyEquals(string key1, string key2) 36 | { 37 | return key1 == key2; 38 | } 39 | 40 | public override int KeyGetHashCode(string key) 41 | { 42 | if (key == null) return 0; 43 | return key.GetHashCode(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ThreadSafeHashTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace FastHashtable 5 | { 6 | // Safe for multiple-read, single-write. 7 | public abstract class ThreadSafeHashtable 8 | { 9 | Entry[] buckets; 10 | int size; // only use in writer lock 11 | 12 | readonly object writerLock = new object(); 13 | readonly float loadFactor; 14 | 15 | public ThreadSafeHashtable(int capacity = 4, float loadFactor = 0.75f) 16 | { 17 | var tableSize = CalculateCapacity(capacity, loadFactor); 18 | this.buckets = new Entry[tableSize]; 19 | this.loadFactor = loadFactor; 20 | } 21 | 22 | public abstract int KeyGetHashCode(TKey key); 23 | public abstract bool KeyEquals(TKey key1, TKey key2); 24 | 25 | public bool TryAdd(TKey key, TValue value) 26 | { 27 | return TryAdd(key, value.AsFunc()); 28 | } 29 | 30 | public bool TryAdd(TKey key, Func valueFactory) 31 | { 32 | TValue _; 33 | return TryAddInternal(key, valueFactory, out _); 34 | } 35 | 36 | bool TryAddInternal(TKey key, Func valueFactory, out TValue resultingValue) 37 | { 38 | lock (writerLock) 39 | { 40 | var nextCapacity = CalculateCapacity(size + 1, loadFactor); 41 | 42 | if (buckets.Length < nextCapacity) 43 | { 44 | // rehash 45 | var nextBucket = new Entry[nextCapacity]; 46 | for (int i = 0; i < buckets.Length; i++) 47 | { 48 | var e = buckets[i]; 49 | while (e != null) 50 | { 51 | var newEntry = new Entry { Key = e.Key, Value = e.Value, Hash = e.Hash }; 52 | AddToBuckets(nextBucket, key, newEntry, null, out resultingValue); 53 | e = e.Next; 54 | } 55 | } 56 | 57 | // add entry(if failed to add, only do resize) 58 | var successAdd = AddToBuckets(nextBucket, key, null, valueFactory, out resultingValue); 59 | 60 | // replace field(threadsafe for read) 61 | Volatile.Write(ref buckets, nextBucket); 62 | 63 | if (successAdd) size++; 64 | return successAdd; 65 | } 66 | else 67 | { 68 | // add entry(insert last is thread safe for read) 69 | var successAdd = AddToBuckets(buckets, key, null, valueFactory, out resultingValue); 70 | if (successAdd) size++; 71 | return successAdd; 72 | } 73 | } 74 | } 75 | 76 | bool AddToBuckets(Entry[] buckets, TKey newKey, Entry newEntryOrNull, Func valueFactory, out TValue resultingValue) 77 | { 78 | var h = (newEntryOrNull != null) ? newEntryOrNull.Hash : KeyGetHashCode(newKey); 79 | if (buckets[h & (buckets.Length - 1)] == null) 80 | { 81 | if (newEntryOrNull != null) 82 | { 83 | resultingValue = newEntryOrNull.Value; 84 | Volatile.Write(ref buckets[h & (buckets.Length - 1)], newEntryOrNull); 85 | } 86 | else 87 | { 88 | resultingValue = valueFactory(newKey); 89 | Volatile.Write(ref buckets[h & (buckets.Length - 1)], new Entry { Key = newKey, Value = resultingValue, Hash = h }); 90 | } 91 | } 92 | else 93 | { 94 | var searchLastEntry = buckets[h & (buckets.Length - 1)]; 95 | while (true) 96 | { 97 | if (KeyEquals(searchLastEntry.Key, newKey)) 98 | { 99 | resultingValue = searchLastEntry.Value; 100 | return false; 101 | } 102 | 103 | if (searchLastEntry.Next == null) 104 | { 105 | if (newEntryOrNull != null) 106 | { 107 | resultingValue = newEntryOrNull.Value; 108 | Volatile.Write(ref searchLastEntry.Next, newEntryOrNull); 109 | } 110 | else 111 | { 112 | resultingValue = valueFactory(newKey); 113 | Volatile.Write(ref searchLastEntry.Next, new Entry { Key = newKey, Value = resultingValue, Hash = h }); 114 | } 115 | break; 116 | } 117 | searchLastEntry = searchLastEntry.Next; 118 | } 119 | } 120 | 121 | return true; 122 | } 123 | 124 | public bool TryGetValue(TKey key, out TValue value) 125 | { 126 | var table = buckets; 127 | var hash = KeyGetHashCode(key); 128 | var entry = table[hash & table.Length - 1]; 129 | 130 | if (entry == null) goto NOT_FOUND; 131 | 132 | if (KeyEquals(entry.Key, key)) 133 | { 134 | value = entry.Value; 135 | return true; 136 | } 137 | 138 | var next = entry.Next; 139 | while (next != null) 140 | { 141 | if (KeyEquals(next.Key, key)) 142 | { 143 | value = next.Value; 144 | return true; 145 | } 146 | next = next.Next; 147 | } 148 | 149 | NOT_FOUND: 150 | value = default(TValue); 151 | return false; 152 | } 153 | 154 | public TValue GetOrAdd(TKey key, Func valueFactory) 155 | { 156 | TValue v; 157 | if (TryGetValue(key, out v)) 158 | { 159 | return v; 160 | } 161 | 162 | TryAddInternal(key, valueFactory, out v); 163 | return v; 164 | } 165 | 166 | static int CalculateCapacity(int collectionSize, float loadFactor) 167 | { 168 | var size = (int)(((float)collectionSize) / loadFactor); 169 | 170 | size--; 171 | size |= size >> 1; 172 | size |= size >> 2; 173 | size |= size >> 4; 174 | size |= size >> 8; 175 | size |= size >> 16; 176 | size += 1; 177 | 178 | if (size < 8) 179 | { 180 | size = 8; 181 | } 182 | return size; 183 | } 184 | 185 | class Entry 186 | { 187 | public TKey Key; 188 | public TValue Value; 189 | public int Hash; 190 | public Entry Next; 191 | 192 | // debug only 193 | public override string ToString() 194 | { 195 | return Key + "(" + Count() + ")"; 196 | } 197 | 198 | int Count() 199 | { 200 | var count = 1; 201 | var n = this; 202 | while (n.Next != null) 203 | { 204 | count++; 205 | n = n.Next; 206 | } 207 | return count; 208 | } 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /ThreadsafeFixedSizeHashtable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | 5 | namespace FastHashtable 6 | { 7 | public abstract class ThreadSafeFixedSizeHashtable 8 | { 9 | readonly Entry[][] buckets; // immutable array(faster than linkedlist) 10 | readonly int indexFor; 11 | 12 | public ThreadSafeFixedSizeHashtable(IEnumerable> values) 13 | : this(values, 0.75f) 14 | { 15 | } 16 | 17 | public ThreadSafeFixedSizeHashtable(IEnumerable> values, float loadFactor) 18 | { 19 | var array = values.ToArray(); 20 | 21 | var tableSize = CalculateCapacity(array.Length, loadFactor); 22 | this.buckets = new Entry[tableSize][]; 23 | this.indexFor = buckets.Length - 1; 24 | 25 | foreach (var item in array) 26 | { 27 | if (!TryAddInternal(item.Key, item.Value)) 28 | { 29 | throw new ArgumentException("Key was already exists. Key:" + item.Key); 30 | } 31 | } 32 | } 33 | 34 | public abstract int KeyGetHashCode(TKey key); 35 | public abstract bool KeyEquals(TKey key1, TKey key2); 36 | 37 | bool TryAddInternal(TKey key, TValue value) 38 | { 39 | var h = KeyGetHashCode(key); 40 | var entry = new Entry { Key = key, Value = value }; 41 | 42 | var array = buckets[h & (indexFor)]; 43 | if (array == null) 44 | { 45 | buckets[h & (indexFor)] = new[] { entry }; 46 | } 47 | else 48 | { 49 | // check duplicate 50 | for (int i = 0; i < array.Length; i++) 51 | { 52 | var e = array[i].Key; 53 | if (Equals(key, e)) 54 | { 55 | return false; 56 | } 57 | } 58 | 59 | var newArray = new Entry[array.Length + 1]; 60 | Array.Copy(array, newArray, array.Length); 61 | array = newArray; 62 | array[array.Length - 1] = entry; 63 | buckets[h & (indexFor)] = array; 64 | } 65 | 66 | return true; 67 | } 68 | 69 | public bool TryGetValue(TKey key, out TValue value) 70 | { 71 | var table = buckets; 72 | var hash = KeyGetHashCode(key); 73 | var entry = table[hash & indexFor]; 74 | 75 | if (entry == null) goto NOT_FOUND; 76 | 77 | { 78 | var v = entry[0]; 79 | if (KeyEquals(key, v.Key)) 80 | { 81 | value = v.Value; 82 | return true; 83 | } 84 | } 85 | 86 | for (int i = 1; i < entry.Length; i++) 87 | { 88 | var v = entry[i]; 89 | if (KeyEquals(key, v.Key)) 90 | { 91 | value = v.Value; 92 | return true; 93 | } 94 | } 95 | 96 | NOT_FOUND: 97 | value = default(TValue); 98 | return false; 99 | } 100 | 101 | static int CalculateCapacity(int collectionSize, float loadFactor) 102 | { 103 | var size = (int)(((float)collectionSize) / loadFactor); 104 | 105 | size--; 106 | size |= size >> 1; 107 | size |= size >> 2; 108 | size |= size >> 4; 109 | size |= size >> 8; 110 | size |= size >> 16; 111 | size += 1; 112 | 113 | if (size < 8) 114 | { 115 | size = 8; 116 | } 117 | return size; 118 | } 119 | 120 | struct Entry 121 | { 122 | public TKey Key; 123 | public TValue Value; 124 | 125 | // for debugging 126 | public override string ToString() 127 | { 128 | return "(" + Key + ", " + Value + ")"; 129 | } 130 | } 131 | } 132 | 133 | public abstract class ThreadSafeFixedSizeHashtable 134 | { 135 | readonly Entry[][] buckets; // immutable array(faster than linkedlist) 136 | readonly int indexFor; 137 | 138 | public ThreadSafeFixedSizeHashtable(IEnumerable> values) 139 | : this(values, 0.75f) 140 | { 141 | } 142 | 143 | public ThreadSafeFixedSizeHashtable(IEnumerable> values, float loadFactor) 144 | { 145 | var array = values.ToArray(); 146 | 147 | var tableSize = CalculateCapacity(array.Length, loadFactor); 148 | this.buckets = new Entry[tableSize][]; 149 | this.indexFor = buckets.Length - 1; 150 | 151 | foreach (var item in array) 152 | { 153 | if (!TryAddInternal(item.Key, item.Value)) 154 | { 155 | throw new ArgumentException("Key was already exists. Key:" + item.Key); 156 | } 157 | } 158 | } 159 | 160 | public abstract int GetHashCode(TKeyForSave key); 161 | public abstract int GetHashCode(TKeyForGet key); 162 | public abstract bool KeyEquals(TKeyForGet key1, TKeyForSave key2); 163 | 164 | bool TryAddInternal(TKeyForSave key, TValue value) 165 | { 166 | var h = GetHashCode(key); 167 | var entry = new Entry { Key = key, Value = value }; 168 | 169 | var array = buckets[h & (indexFor)]; 170 | if (array == null) 171 | { 172 | buckets[h & (indexFor)] = new[] { entry }; 173 | } 174 | else 175 | { 176 | // check duplicate 177 | for (int i = 0; i < array.Length; i++) 178 | { 179 | var e = array[i].Key; 180 | if (Equals(key, e)) 181 | { 182 | return false; 183 | } 184 | } 185 | 186 | var newArray = new Entry[array.Length + 1]; 187 | Array.Copy(array, newArray, array.Length); 188 | array = newArray; 189 | array[array.Length - 1] = entry; 190 | buckets[h & (indexFor)] = array; 191 | } 192 | 193 | return true; 194 | } 195 | 196 | public bool TryGetValue(TKeyForGet key, out TValue value) 197 | { 198 | var table = buckets; 199 | var hash = GetHashCode(key); 200 | var entry = table[hash & indexFor]; 201 | 202 | if (entry == null) goto NOT_FOUND; 203 | 204 | { 205 | var v = entry[0]; 206 | if (KeyEquals(key, v.Key)) 207 | { 208 | value = v.Value; 209 | return true; 210 | } 211 | } 212 | 213 | for (int i = 1; i < entry.Length; i++) 214 | { 215 | var v = entry[i]; 216 | if (KeyEquals(key, v.Key)) 217 | { 218 | value = v.Value; 219 | return true; 220 | } 221 | } 222 | 223 | NOT_FOUND: 224 | value = default(TValue); 225 | return false; 226 | } 227 | 228 | static int CalculateCapacity(int collectionSize, float loadFactor) 229 | { 230 | var size = (int)(((float)collectionSize) / loadFactor); 231 | 232 | size--; 233 | size |= size >> 1; 234 | size |= size >> 2; 235 | size |= size >> 4; 236 | size |= size >> 8; 237 | size |= size >> 16; 238 | size += 1; 239 | 240 | if (size < 8) 241 | { 242 | size = 8; 243 | } 244 | return size; 245 | } 246 | 247 | struct Entry 248 | { 249 | public TKeyForSave Key; 250 | public TValue Value; 251 | 252 | // for debugging 253 | public override string ToString() 254 | { 255 | return "(" + Key + ", " + Value + ")"; 256 | } 257 | } 258 | } 259 | } -------------------------------------------------------------------------------- /TypeKeyHashable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FastHashtable 4 | { 5 | public sealed class TypeKeyHashable : ThreadSafeHashtable 6 | { 7 | public TypeKeyHashable(int capacity = 4, float loadFactor = 0.75F) 8 | : base(capacity, loadFactor) 9 | { 10 | } 11 | 12 | public override int KeyGetHashCode(Type key) 13 | { 14 | if (key == null) return 0; 15 | return key.GetHashCode(); 16 | } 17 | 18 | public override bool KeyEquals(Type key1, Type key2) 19 | { 20 | return key1 == key2; 21 | } 22 | } 23 | } 24 | --------------------------------------------------------------------------------