├── .gitignore ├── FastDictionary.Profile ├── FastDictionary.Profile.fsproj └── Program.fs ├── FastDictionaryTest.Benchmark ├── Baseline.fs ├── Benchmarks.fs ├── ByteList.fs ├── ByteListRobinHood.fs ├── ByteListRobinHoodInline.fs ├── ByteListRobinHoodVec128.fs ├── CStyle.fs ├── CacheEquality.fs ├── Domain.fs ├── EmbeddedHead.fs ├── FastDictionaryTest.Benchmark.fsproj ├── FastTypeBranch.fs ├── FibonacciHashing.fs ├── LinearProbing.fs ├── Naive.fs ├── Program.fs ├── RobinHood.fs ├── RobinHoodEviction.fs ├── SOA.fs └── ZeroAllocation.fs ├── FastDictionaryTest.Scratchpad ├── FastDictionaryTest.Scratchpad.fsproj └── Program.fs ├── FastDictionaryTest.Tests ├── FastDictionaryTest.Tests.fsproj ├── Program.fs └── UnitTest1.fs ├── FastDictionaryTest.sln ├── FastDictionaryTest ├── ByteList.fs ├── ByteListRobinHood.fs ├── ByteListRobinHoodInline.fs ├── ByteListRobinHoodVec128.fs ├── CStyle.fs ├── CacheEquality.fs ├── EmbeddedHead.fs ├── FastDictionaryTest.fsproj ├── FastTypeBranch.fs ├── FibonacciHashing.fs ├── LinearProbing.fs ├── Naive.fs ├── RobinHood.fs ├── RobinHoodEviction.fs ├── SOA.fs ├── SOA │ ├── Helpers.fs │ ├── IntDict.fs │ ├── RefDict.fs │ ├── StrDict.fs │ └── ValueDict.fs └── ZeroAllocation.fs ├── README.md ├── presentation.html └── presentation.md /.gitignore: -------------------------------------------------------------------------------- 1 | .fable/ 2 | *.fs.js 3 | *.fs.js.map 4 | .fake/ 5 | .farmer/ 6 | .idea/ 7 | .ionide/ 8 | .vs/ 9 | deploy/ 10 | obj/ 11 | bin/ 12 | output/ 13 | packages/ 14 | paket-files/ 15 | node_modules/ 16 | release.cmd 17 | release.sh 18 | *.orig 19 | *.DotSettings.user 20 | **/BenchmarkDotNet.Artifacts/ 21 | -------------------------------------------------------------------------------- /FastDictionary.Profile/FastDictionary.Profile.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | x64 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /FastDictionary.Profile/Program.fs: -------------------------------------------------------------------------------- 1 | open System.Collections.Frozen 2 | open System.Collections.Generic 3 | 4 | 5 | // type KeyCount = 6 | // | ``10`` = 0 7 | // | ``100`` = 1 8 | // | ``1_000`` = 2 9 | // | ``10_000`` = 3 10 | // 11 | // let valueCounts = [| 12 | // KeyCount.``10`` , 10 13 | // KeyCount.``100`` , 100 14 | // KeyCount.``1_000`` , 1_000 15 | // KeyCount.``10_000`` , 10_000 16 | // |] 17 | 18 | let rng = System.Random 123 19 | let minKey = -100_000 20 | let maxKey = 100_000 21 | let maxValue = 100_000 22 | let dictionaryCount = 100 23 | let testCount = 100 24 | let keyCount = 100 25 | let lookupCount = 100 26 | 27 | 28 | let strDataSets = 29 | [| for _ in 0 .. testCount - 1 -> 30 | let d = Dictionary() 31 | 32 | while d.Count < dictionaryCount do 33 | let k = $"Key[{((rng.Next (minKey, maxKey)) <<< 16)}]" 34 | let v = rng.Next maxValue 35 | d[k] <- v 36 | 37 | d 38 | // |> Seq.map (|KeyValue|) 39 | |> Array.ofSeq 40 | |] 41 | 42 | // Get samples of random keys to look up for each data set 43 | let strKeySets = 44 | [| for testKey in 0 .. testCount - 1 -> 45 | let data = strDataSets[testKey] 46 | [| for _ in 1 .. lookupCount -> 47 | // Next is exclusive on the upper bound 48 | data[rng.Next data.Length].Key |] 49 | |] 50 | 51 | let testDictionaries = 52 | [| for data in strDataSets do 53 | FrozenDictionary.ToFrozenDictionary(data) |] 54 | 55 | printfn "Starting loops" 56 | for _ in 1 .. 100_000 do 57 | 58 | for i in 0 .. strKeySets.Length - 1 do 59 | let mutable acc = 0 60 | let keys = strKeySets[i] 61 | let d = testDictionaries[i] 62 | 63 | for key in keys do 64 | acc <- acc + d[key] 65 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/Baseline.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | open System.Collections.Generic 4 | open System.Collections.ObjectModel 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest.Benchmark.Domain 8 | 9 | [] 10 | [] 13 | type Baseline () = 14 | 15 | let intMaps = 16 | [| for countKey, _ in valueCounts -> 17 | [|for testKey in 0 .. testCount - 1 -> 18 | intDataSets[int countKey][testKey] 19 | |> Map 20 | |] 21 | |] 22 | 23 | let intDictionaries = 24 | [| for countKey, _ in valueCounts -> 25 | [|for testKey in 0 .. testCount - 1 -> 26 | intDataSets[int countKey][testKey] 27 | |> Array.map KeyValuePair 28 | |> Dictionary 29 | |] 30 | |] 31 | 32 | let intReadOnlyDictionaries = 33 | [| for countKey, _ in valueCounts -> 34 | [|for testKey in 0 .. testCount - 1 -> 35 | intDataSets[int countKey][testKey] 36 | |> Array.map KeyValuePair 37 | |> Dictionary 38 | |> ReadOnlyDictionary 39 | |] 40 | |] 41 | 42 | let intDicts = 43 | [| for countKey, _ in valueCounts -> 44 | [|for testKey in 0 .. testCount - 1 -> 45 | intDataSets[int countKey][testKey] 46 | |> dict 47 | |] 48 | |] 49 | 50 | let intReadOnlyDicts = 51 | [| for countKey, _ in valueCounts -> 52 | [|for testKey in 0 .. testCount - 1 -> 53 | intDataSets[int countKey][testKey] 54 | |> readOnlyDict 55 | |] 56 | |] 57 | 58 | let strMaps = 59 | [| for countKey, _ in valueCounts -> 60 | [|for testKey in 0 .. testCount - 1 -> 61 | strDataSets[int countKey][testKey] 62 | |> Map 63 | |] 64 | |] 65 | 66 | let strDictionaries = 67 | [| for countKey, _ in valueCounts -> 68 | [|for testKey in 0 .. testCount - 1 -> 69 | strDataSets[int countKey][testKey] 70 | |> Array.map KeyValuePair 71 | |> Dictionary 72 | |] 73 | |] 74 | 75 | let strReadOnlyDictionaries = 76 | [| for countKey, _ in valueCounts -> 77 | [|for testKey in 0 .. testCount - 1 -> 78 | strDataSets[int countKey][testKey] 79 | |> Array.map KeyValuePair 80 | |> Dictionary 81 | |> ReadOnlyDictionary 82 | |] 83 | |] 84 | 85 | let strDicts = 86 | [| for countKey, _ in valueCounts -> 87 | [|for testKey in 0 .. testCount - 1 -> 88 | strDataSets[int countKey][testKey] 89 | |> dict 90 | |] 91 | |] 92 | 93 | let strReadOnlyDicts = 94 | [| for countKey, _ in valueCounts -> 95 | [|for testKey in 0 .. testCount - 1 -> 96 | strDataSets[int countKey][testKey] 97 | |> readOnlyDict 98 | |] 99 | |] 100 | 101 | [] 102 | member val KeyType = KeyType.Int with get, set 103 | 104 | [] 110 | member val KeyCount = KeyCount.``10`` with get, set 111 | 112 | 113 | [] 114 | member b.Map () = 115 | 116 | if b.KeyType = KeyType.Int then 117 | let mutable acc = 0 118 | let dataSet = intMaps[int b.KeyCount] 119 | let keySet = intKeySets[int b.KeyCount] 120 | 121 | for testKey in 0 .. testCount - 1 do 122 | let data = dataSet[testKey] 123 | let keys = keySet[testKey] 124 | 125 | for k in keys do 126 | acc <- acc + data[k] 127 | 128 | acc 129 | 130 | else 131 | let mutable acc = 0 132 | let dataSet = strMaps[int b.KeyCount] 133 | let keySet = strKeySets[int b.KeyCount] 134 | 135 | for testKey in 0 .. testCount - 1 do 136 | let data = dataSet[testKey] 137 | let keys = keySet[testKey] 138 | 139 | for k in keys do 140 | acc <- acc + data[k] 141 | 142 | acc 143 | 144 | [] 145 | member b.Dictionary () = 146 | 147 | if b.KeyType = KeyType.Int then 148 | let mutable acc = 0 149 | let dataSet = intDictionaries[int b.KeyCount] 150 | let keySet = intKeySets[int b.KeyCount] 151 | 152 | for testKey in 0 .. testCount - 1 do 153 | let data = dataSet[testKey] 154 | let keys = keySet[testKey] 155 | 156 | for k in keys do 157 | acc <- acc + data[k] 158 | 159 | acc 160 | 161 | else 162 | let mutable acc = 0 163 | let dataSet = strDictionaries[int b.KeyCount] 164 | let keySet = strKeySets[int b.KeyCount] 165 | 166 | for testKey in 0 .. testCount - 1 do 167 | let data = dataSet[testKey] 168 | let keys = keySet[testKey] 169 | 170 | for k in keys do 171 | acc <- acc + data[k] 172 | 173 | acc 174 | 175 | [] 176 | member b.ReadOnlyDictionary () = 177 | if b.KeyType = KeyType.Int then 178 | let mutable acc = 0 179 | let dataSet = intReadOnlyDictionaries[int b.KeyCount] 180 | let keySet = intKeySets[int b.KeyCount] 181 | 182 | for testKey in 0 .. testCount - 1 do 183 | let data = dataSet[testKey] 184 | let keys = keySet[testKey] 185 | 186 | for k in keys do 187 | acc <- acc + data[k] 188 | 189 | acc 190 | 191 | else 192 | let mutable acc = 0 193 | let dataSet = strReadOnlyDictionaries[int b.KeyCount] 194 | let keySet = strKeySets[int b.KeyCount] 195 | 196 | for testKey in 0 .. testCount - 1 do 197 | let data = dataSet[testKey] 198 | let keys = keySet[testKey] 199 | 200 | for k in keys do 201 | acc <- acc + data[k] 202 | 203 | acc 204 | 205 | [] 206 | member b.Dict () = 207 | if b.KeyType = KeyType.Int then 208 | let mutable acc = 0 209 | let dataSet = intDicts[int b.KeyCount] 210 | let keySet = intKeySets[int b.KeyCount] 211 | 212 | for testKey in 0 .. testCount - 1 do 213 | let data = dataSet[testKey] 214 | let keys = keySet[testKey] 215 | 216 | for k in keys do 217 | acc <- acc + data[k] 218 | 219 | acc 220 | 221 | else 222 | let mutable acc = 0 223 | let dataSet = strDicts[int b.KeyCount] 224 | let keySet = strKeySets[int b.KeyCount] 225 | 226 | for testKey in 0 .. testCount - 1 do 227 | let data = dataSet[testKey] 228 | let keys = keySet[testKey] 229 | 230 | for k in keys do 231 | acc <- acc + data[k] 232 | 233 | acc 234 | 235 | [] 236 | member b.ReadOnlyDict () = 237 | if b.KeyType = KeyType.Int then 238 | let mutable acc = 0 239 | let dataSet = intReadOnlyDicts[int b.KeyCount] 240 | let keySet = intKeySets[int b.KeyCount] 241 | 242 | for testKey in 0 .. testCount - 1 do 243 | let data = dataSet[testKey] 244 | let keys = keySet[testKey] 245 | 246 | for k in keys do 247 | acc <- acc + data[k] 248 | 249 | acc 250 | 251 | else 252 | let mutable acc = 0 253 | let dataSet = strReadOnlyDicts[int b.KeyCount] 254 | let keySet = strKeySets[int b.KeyCount] 255 | 256 | for testKey in 0 .. testCount - 1 do 257 | let data = dataSet[testKey] 258 | let keys = keySet[testKey] 259 | 260 | for k in keys do 261 | acc <- acc + data[k] 262 | 263 | acc 264 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/ByteList.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | type ByteList () = 15 | 16 | let intDictionaries = 17 | [| for countKey, _ in valueCounts -> 18 | [|for testKey in 0 .. testCount - 1 -> 19 | intDataSets[int countKey][testKey] 20 | |> Array.map KeyValuePair 21 | |> Dictionary 22 | |] 23 | |] 24 | 25 | let strDictionaries = 26 | [| for countKey, _ in valueCounts -> 27 | [|for testKey in 0 .. testCount - 1 -> 28 | strDataSets[int countKey][testKey] 29 | |> Array.map KeyValuePair 30 | |> Dictionary 31 | |] 32 | |] 33 | 34 | let intTestDictionaries = 35 | [| for countKey, _ in valueCounts -> 36 | [|for testKey in 0 .. testCount - 1 -> 37 | intDataSets[int countKey][testKey] 38 | |> ByteList.Dictionary 39 | |] 40 | |] 41 | 42 | let strTestDictionaries = 43 | [| for countKey, _ in valueCounts -> 44 | [|for testKey in 0 .. testCount - 1 -> 45 | strDataSets[int countKey][testKey] 46 | |> ByteList.Dictionary 47 | |] 48 | |] 49 | 50 | [] 51 | member val KeyType = KeyType.Int with get, set 52 | 53 | [] 59 | member val KeyCount = KeyCount.``10`` with get, set 60 | 61 | 62 | [] 63 | member b.Dictionary () = 64 | 65 | if b.KeyType = KeyType.Int then 66 | let mutable acc = 0 67 | let dataSet = intDictionaries[int b.KeyCount] 68 | let keySet = intKeySets[int b.KeyCount] 69 | 70 | for testKey in 0 .. testCount - 1 do 71 | let data = dataSet[testKey] 72 | let keys = keySet[testKey] 73 | 74 | for k in keys do 75 | acc <- acc + data[k] 76 | 77 | acc 78 | 79 | else 80 | let mutable acc = 0 81 | let dataSet = strDictionaries[int b.KeyCount] 82 | let keySet = strKeySets[int b.KeyCount] 83 | 84 | for testKey in 0 .. testCount - 1 do 85 | let data = dataSet[testKey] 86 | let keys = keySet[testKey] 87 | 88 | for k in keys do 89 | acc <- acc + data[k] 90 | 91 | acc 92 | 93 | 94 | [] 95 | member b.Test () = 96 | 97 | if b.KeyType = KeyType.Int then 98 | let mutable acc = 0 99 | let dataSet = intTestDictionaries[int b.KeyCount] 100 | let keySet = intKeySets[int b.KeyCount] 101 | 102 | for testKey in 0 .. testCount - 1 do 103 | let data = dataSet[testKey] 104 | let keys = keySet[testKey] 105 | 106 | for k in keys do 107 | acc <- acc + data[k] 108 | 109 | acc 110 | 111 | else 112 | let mutable acc = 0 113 | let dataSet = strTestDictionaries[int b.KeyCount] 114 | let keySet = strKeySets[int b.KeyCount] 115 | 116 | for testKey in 0 .. testCount - 1 do 117 | let data = dataSet[testKey] 118 | let keys = keySet[testKey] 119 | 120 | for k in keys do 121 | acc <- acc + data[k] 122 | 123 | acc 124 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/ByteListRobinHood.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | [] 15 | type ByteListRobinHood () = 16 | 17 | let intDictionaries = 18 | [| for countKey, _ in valueCounts -> 19 | [|for testKey in 0 .. testCount - 1 -> 20 | intDataSets[int countKey][testKey] 21 | |> Array.map KeyValuePair 22 | |> Dictionary 23 | |] 24 | |] 25 | 26 | let strDictionaries = 27 | [| for countKey, _ in valueCounts -> 28 | [|for testKey in 0 .. testCount - 1 -> 29 | strDataSets[int countKey][testKey] 30 | |> Array.map KeyValuePair 31 | |> Dictionary 32 | |] 33 | |] 34 | 35 | let intTestDictionaries = 36 | [| for countKey, _ in valueCounts -> 37 | [|for testKey in 0 .. testCount - 1 -> 38 | intDataSets[int countKey][testKey] 39 | |> ByteListRobinHood.Dictionary 40 | |] 41 | |] 42 | 43 | let strTestDictionaries = 44 | [| for countKey, _ in valueCounts -> 45 | [|for testKey in 0 .. testCount - 1 -> 46 | strDataSets[int countKey][testKey] 47 | |> ByteListRobinHood.Dictionary 48 | |] 49 | |] 50 | 51 | [] 52 | member val KeyType = KeyType.Int with get, set 53 | 54 | [] 60 | member val KeyCount = KeyCount.``10`` with get, set 61 | 62 | 63 | [] 64 | member b.Dictionary () = 65 | 66 | if b.KeyType = KeyType.Int then 67 | let mutable acc = 0 68 | let dataSet = intDictionaries[int b.KeyCount] 69 | let keySet = intKeySets[int b.KeyCount] 70 | 71 | for testKey in 0 .. testCount - 1 do 72 | let data = dataSet[testKey] 73 | let keys = keySet[testKey] 74 | 75 | for k in keys do 76 | acc <- acc + data[k] 77 | 78 | acc 79 | 80 | else 81 | let mutable acc = 0 82 | let dataSet = strDictionaries[int b.KeyCount] 83 | let keySet = strKeySets[int b.KeyCount] 84 | 85 | for testKey in 0 .. testCount - 1 do 86 | let data = dataSet[testKey] 87 | let keys = keySet[testKey] 88 | 89 | for k in keys do 90 | acc <- acc + data[k] 91 | 92 | acc 93 | 94 | 95 | [] 96 | member b.Test () = 97 | 98 | if b.KeyType = KeyType.Int then 99 | let mutable acc = 0 100 | let dataSet = intTestDictionaries[int b.KeyCount] 101 | let keySet = intKeySets[int b.KeyCount] 102 | 103 | for testKey in 0 .. testCount - 1 do 104 | let data = dataSet[testKey] 105 | let keys = keySet[testKey] 106 | 107 | for k in keys do 108 | acc <- acc + data[k] 109 | 110 | acc 111 | 112 | else 113 | let mutable acc = 0 114 | let dataSet = strTestDictionaries[int b.KeyCount] 115 | let keySet = strKeySets[int b.KeyCount] 116 | 117 | for testKey in 0 .. testCount - 1 do 118 | let data = dataSet[testKey] 119 | let keys = keySet[testKey] 120 | 121 | for k in keys do 122 | acc <- acc + data[k] 123 | 124 | acc 125 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/ByteListRobinHoodInline.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Frozen 5 | open System.Collections.Generic 6 | open BenchmarkDotNet.Diagnosers 7 | open BenchmarkDotNet.Attributes 8 | open BenchmarkDotNet.Jobs 9 | open FastDictionaryTest 10 | open FastDictionaryTest.Benchmark.Domain 11 | 12 | [] 13 | [] 16 | [] 17 | type ByteListRobinHoodInline () = 18 | 19 | let intDictionaries = 20 | [| for countKey, _ in valueCounts -> 21 | [|for testKey in 0 .. testCount - 1 -> 22 | intDataSets[int countKey][testKey] 23 | |> Array.map KeyValuePair 24 | |> Dictionary 25 | |] 26 | |] 27 | 28 | let strDictionaries = 29 | [| for countKey, _ in valueCounts -> 30 | [|for testKey in 0 .. testCount - 1 -> 31 | strDataSets[int countKey][testKey] 32 | |> Array.map KeyValuePair 33 | |> Dictionary 34 | |] 35 | |] 36 | 37 | 38 | let intFrozenDictionaries = 39 | [| for countKey, _ in valueCounts -> 40 | [|for testKey in 0 .. testCount - 1 -> 41 | intDataSets[int countKey][testKey] 42 | |> Array.map KeyValuePair 43 | |> Dictionary 44 | |> fun x -> FrozenDictionary.ToFrozenDictionary(x) 45 | |] 46 | |] 47 | 48 | let strFrozenDictionaries = 49 | [| for countKey, _ in valueCounts -> 50 | [|for testKey in 0 .. testCount - 1 -> 51 | strDataSets[int countKey][testKey] 52 | |> Array.map KeyValuePair 53 | |> Dictionary 54 | |> fun x -> FrozenDictionary.ToFrozenDictionary(x) 55 | |] 56 | |] 57 | 58 | let intTestDictionaries = 59 | [| for countKey, _ in valueCounts -> 60 | [|for testKey in 0 .. testCount - 1 -> 61 | intDataSets[int countKey][testKey] 62 | |> ByteListRobinHoodInline.Dictionary 63 | |] 64 | |] 65 | 66 | let strTestDictionaries = 67 | [| for countKey, _ in valueCounts -> 68 | [|for testKey in 0 .. testCount - 1 -> 69 | strDataSets[int countKey][testKey] 70 | |> ByteListRobinHoodInline.Dictionary 71 | |] 72 | |] 73 | 74 | [] 75 | member val KeyType = KeyType.Int with get, set 76 | 77 | [] 83 | member val KeyCount = KeyCount.``10`` with get, set 84 | 85 | 86 | [] 87 | member b.Dictionary () = 88 | 89 | if b.KeyType = KeyType.Int then 90 | let mutable acc = 0 91 | let dataSet = intDictionaries[int b.KeyCount] 92 | let keySet = intKeySets[int b.KeyCount] 93 | 94 | for testKey in 0 .. testCount - 1 do 95 | let data = dataSet[testKey] 96 | let keys = keySet[testKey] 97 | 98 | for k in keys do 99 | acc <- acc + data[k] 100 | 101 | acc 102 | 103 | else 104 | let mutable acc = 0 105 | let dataSet = strDictionaries[int b.KeyCount] 106 | let keySet = strKeySets[int b.KeyCount] 107 | 108 | for testKey in 0 .. testCount - 1 do 109 | let data = dataSet[testKey] 110 | let keys = keySet[testKey] 111 | 112 | for k in keys do 113 | acc <- acc + data[k] 114 | 115 | acc 116 | 117 | [] 118 | member b.FrozenDictionary () = 119 | 120 | if b.KeyType = KeyType.Int then 121 | let mutable acc = 0 122 | let dataSet = intFrozenDictionaries[int b.KeyCount] 123 | let keySet = intKeySets[int b.KeyCount] 124 | 125 | for testKey in 0 .. testCount - 1 do 126 | let data = dataSet[testKey] 127 | let keys = keySet[testKey] 128 | 129 | for k in keys do 130 | acc <- acc + data[k] 131 | 132 | acc 133 | 134 | else 135 | let mutable acc = 0 136 | let dataSet = strFrozenDictionaries[int b.KeyCount] 137 | let keySet = strKeySets[int b.KeyCount] 138 | 139 | for testKey in 0 .. testCount - 1 do 140 | let data = dataSet[testKey] 141 | let keys = keySet[testKey] 142 | 143 | for k in keys do 144 | acc <- acc + data[k] 145 | 146 | acc 147 | 148 | 149 | [] 150 | member b.Test () = 151 | 152 | if b.KeyType = KeyType.Int then 153 | let mutable acc = 0 154 | let dataSet = intTestDictionaries[int b.KeyCount] 155 | let keySet = intKeySets[int b.KeyCount] 156 | 157 | for testKey in 0 .. testCount - 1 do 158 | let data = dataSet[testKey] 159 | let keys = keySet[testKey] 160 | 161 | for k in keys do 162 | acc <- acc + data[k] 163 | 164 | acc 165 | 166 | else 167 | let mutable acc = 0 168 | let dataSet = strTestDictionaries[int b.KeyCount] 169 | let keySet = strKeySets[int b.KeyCount] 170 | 171 | for testKey in 0 .. testCount - 1 do 172 | let data = dataSet[testKey] 173 | let keys = keySet[testKey] 174 | 175 | for k in keys do 176 | acc <- acc + data[k] 177 | 178 | acc 179 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/ByteListRobinHoodVec128.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | type ByteListRobinHoodVec128 () = 15 | 16 | let intDictionaries = 17 | [| for countKey, _ in valueCounts -> 18 | [|for testKey in 0 .. testCount - 1 -> 19 | intDataSets[int countKey][testKey] 20 | |> Array.map KeyValuePair 21 | |> Dictionary 22 | |] 23 | |] 24 | 25 | let strDictionaries = 26 | [| for countKey, _ in valueCounts -> 27 | [|for testKey in 0 .. testCount - 1 -> 28 | strDataSets[int countKey][testKey] 29 | |> Array.map KeyValuePair 30 | |> Dictionary 31 | |] 32 | |] 33 | 34 | let intTestDictionaries = 35 | [| for countKey, _ in valueCounts -> 36 | [|for testKey in 0 .. testCount - 1 -> 37 | intDataSets[int countKey][testKey] 38 | |> ByteListRobinHoodVec128.Dictionary 39 | |] 40 | |] 41 | 42 | let strTestDictionaries = 43 | [| for countKey, _ in valueCounts -> 44 | [|for testKey in 0 .. testCount - 1 -> 45 | strDataSets[int countKey][testKey] 46 | |> ByteListRobinHoodVec128.Dictionary 47 | |] 48 | |] 49 | 50 | [] 51 | member val KeyType = KeyType.Int with get, set 52 | 53 | [] 59 | member val KeyCount = KeyCount.``10`` with get, set 60 | 61 | 62 | [] 63 | member b.Dictionary () = 64 | 65 | if b.KeyType = KeyType.Int then 66 | let mutable acc = 0 67 | let dataSet = intDictionaries[int b.KeyCount] 68 | let keySet = intKeySets[int b.KeyCount] 69 | 70 | for testKey in 0 .. testCount - 1 do 71 | let data = dataSet[testKey] 72 | let keys = keySet[testKey] 73 | 74 | for k in keys do 75 | acc <- acc + data[k] 76 | 77 | acc 78 | 79 | else 80 | let mutable acc = 0 81 | let dataSet = strDictionaries[int b.KeyCount] 82 | let keySet = strKeySets[int b.KeyCount] 83 | 84 | for testKey in 0 .. testCount - 1 do 85 | let data = dataSet[testKey] 86 | let keys = keySet[testKey] 87 | 88 | for k in keys do 89 | acc <- acc + data[k] 90 | 91 | acc 92 | 93 | 94 | [] 95 | member b.Test () = 96 | 97 | if b.KeyType = KeyType.Int then 98 | let mutable acc = 0 99 | let dataSet = intTestDictionaries[int b.KeyCount] 100 | let keySet = intKeySets[int b.KeyCount] 101 | 102 | for testKey in 0 .. testCount - 1 do 103 | let data = dataSet[testKey] 104 | let keys = keySet[testKey] 105 | 106 | for k in keys do 107 | acc <- acc + data[k] 108 | 109 | acc 110 | 111 | else 112 | let mutable acc = 0 113 | let dataSet = strTestDictionaries[int b.KeyCount] 114 | let keySet = strKeySets[int b.KeyCount] 115 | 116 | for testKey in 0 .. testCount - 1 do 117 | let data = dataSet[testKey] 118 | let keys = keySet[testKey] 119 | 120 | for k in keys do 121 | acc <- acc + data[k] 122 | 123 | acc 124 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/CStyle.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | [] 15 | type CStyle () = 16 | 17 | let intDictionaries = 18 | [| for countKey, _ in valueCounts -> 19 | [|for testKey in 0 .. testCount - 1 -> 20 | intDataSets[int countKey][testKey] 21 | |> Array.map KeyValuePair 22 | |> Dictionary 23 | |] 24 | |] 25 | 26 | let strDictionaries = 27 | [| for countKey, _ in valueCounts -> 28 | [|for testKey in 0 .. testCount - 1 -> 29 | strDataSets[int countKey][testKey] 30 | |> Array.map KeyValuePair 31 | |> Dictionary 32 | |] 33 | |] 34 | 35 | let intTestDictionaries = 36 | [| for countKey, _ in valueCounts -> 37 | [|for testKey in 0 .. testCount - 1 -> 38 | intDataSets[int countKey][testKey] 39 | |> CStyle.Dictionary 40 | |] 41 | |] 42 | 43 | let strTestDictionaries = 44 | [| for countKey, _ in valueCounts -> 45 | [|for testKey in 0 .. testCount - 1 -> 46 | strDataSets[int countKey][testKey] 47 | |> CStyle.Dictionary 48 | |] 49 | |] 50 | 51 | [] 52 | member val KeyType = KeyType.Int with get, set 53 | 54 | [] 60 | member val KeyCount = KeyCount.``10`` with get, set 61 | 62 | 63 | // [] 64 | member b.Dictionary () = 65 | 66 | if b.KeyType = KeyType.Int then 67 | let mutable acc = 0 68 | let dataSet = intDictionaries[int b.KeyCount] 69 | let keySet = intKeySets[int b.KeyCount] 70 | 71 | for testKey in 0 .. testCount - 1 do 72 | let data = dataSet[testKey] 73 | let keys = keySet[testKey] 74 | 75 | for k in keys do 76 | acc <- acc + data[k] 77 | 78 | acc 79 | 80 | else 81 | let mutable acc = 0 82 | let dataSet = strDictionaries[int b.KeyCount] 83 | let keySet = strKeySets[int b.KeyCount] 84 | 85 | for testKey in 0 .. testCount - 1 do 86 | let data = dataSet[testKey] 87 | let keys = keySet[testKey] 88 | 89 | for k in keys do 90 | acc <- acc + data[k] 91 | 92 | acc 93 | 94 | 95 | [] 96 | member b.Test () = 97 | 98 | if b.KeyType = KeyType.Int then 99 | let mutable acc = 0 100 | let dataSet = intTestDictionaries[int b.KeyCount] 101 | let keySet = intKeySets[int b.KeyCount] 102 | 103 | for testKey in 0 .. testCount - 1 do 104 | let data = dataSet[testKey] 105 | let keys = keySet[testKey] 106 | 107 | for k in keys do 108 | acc <- acc + data[k] 109 | 110 | acc 111 | 112 | else 113 | let mutable acc = 0 114 | let dataSet = strTestDictionaries[int b.KeyCount] 115 | let keySet = strKeySets[int b.KeyCount] 116 | 117 | for testKey in 0 .. testCount - 1 do 118 | let data = dataSet[testKey] 119 | let keys = keySet[testKey] 120 | 121 | for k in keys do 122 | acc <- acc + data[k] 123 | 124 | acc 125 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/CacheEquality.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | type CacheEquality () = 15 | 16 | let intDictionaries = 17 | [| for countKey, _ in valueCounts -> 18 | [|for testKey in 0 .. testCount - 1 -> 19 | intDataSets[int countKey][testKey] 20 | |> Array.map KeyValuePair 21 | |> Dictionary 22 | |] 23 | |] 24 | 25 | let strDictionaries = 26 | [| for countKey, _ in valueCounts -> 27 | [|for testKey in 0 .. testCount - 1 -> 28 | strDataSets[int countKey][testKey] 29 | |> Array.map KeyValuePair 30 | |> Dictionary 31 | |] 32 | |] 33 | 34 | let intTestDictionaries = 35 | [| for countKey, _ in valueCounts -> 36 | [|for testKey in 0 .. testCount - 1 -> 37 | intDataSets[int countKey][testKey] 38 | |> CacheEquality.Dictionary 39 | |] 40 | |] 41 | 42 | let strTestDictionaries = 43 | [| for countKey, _ in valueCounts -> 44 | [|for testKey in 0 .. testCount - 1 -> 45 | strDataSets[int countKey][testKey] 46 | |> CacheEquality.Dictionary 47 | |] 48 | |] 49 | 50 | [] 51 | member val KeyType = KeyType.Int with get, set 52 | 53 | [] 59 | member val KeyCount = KeyCount.``10`` with get, set 60 | 61 | 62 | [] 63 | member b.Dictionary () = 64 | 65 | if b.KeyType = KeyType.Int then 66 | let mutable acc = 0 67 | let dataSet = intDictionaries[int b.KeyCount] 68 | let keySet = intKeySets[int b.KeyCount] 69 | 70 | for testKey in 0 .. testCount - 1 do 71 | let data = dataSet[testKey] 72 | let keys = keySet[testKey] 73 | 74 | for k in keys do 75 | acc <- acc + data[k] 76 | 77 | acc 78 | 79 | else 80 | let mutable acc = 0 81 | let dataSet = strDictionaries[int b.KeyCount] 82 | let keySet = strKeySets[int b.KeyCount] 83 | 84 | for testKey in 0 .. testCount - 1 do 85 | let data = dataSet[testKey] 86 | let keys = keySet[testKey] 87 | 88 | for k in keys do 89 | acc <- acc + data[k] 90 | 91 | acc 92 | 93 | 94 | [] 95 | member b.Test () = 96 | 97 | if b.KeyType = KeyType.Int then 98 | let mutable acc = 0 99 | let dataSet = intTestDictionaries[int b.KeyCount] 100 | let keySet = intKeySets[int b.KeyCount] 101 | 102 | for testKey in 0 .. testCount - 1 do 103 | let data = dataSet[testKey] 104 | let keys = keySet[testKey] 105 | 106 | for k in keys do 107 | acc <- acc + data[k] 108 | 109 | acc 110 | 111 | else 112 | let mutable acc = 0 113 | let dataSet = strTestDictionaries[int b.KeyCount] 114 | let keySet = strKeySets[int b.KeyCount] 115 | 116 | for testKey in 0 .. testCount - 1 do 117 | let data = dataSet[testKey] 118 | let keys = keySet[testKey] 119 | 120 | for k in keys do 121 | acc <- acc + data[k] 122 | 123 | acc 124 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/Domain.fs: -------------------------------------------------------------------------------- 1 | module FastDictionaryTest.Benchmark.Domain 2 | 3 | open System.Collections.Generic 4 | 5 | [] 6 | type StructKey = { 7 | Value: int 8 | } 9 | 10 | type RefKey = { 11 | Value: int 12 | } 13 | 14 | 15 | type KeyType = 16 | | Int = 0 17 | | String = 1 18 | | Struct = 2 19 | | Ref = 3 20 | 21 | type KeyCount = 22 | | ``10`` = 0 23 | | ``100`` = 1 24 | | ``1_000`` = 2 25 | | ``10_000`` = 3 26 | 27 | let valueCounts = [| 28 | KeyCount.``10`` , 10 29 | KeyCount.``100`` , 100 30 | KeyCount.``1_000`` , 1_000 31 | KeyCount.``10_000`` , 10_000 32 | |] 33 | 34 | let rng = System.Random 123 35 | let minKey = -100_000 36 | let maxKey = 100_000 37 | let maxValue = 100_000 38 | let testCount = 100 39 | let lookupCount = 100 40 | 41 | let intDataSets = 42 | [| for _, count in valueCounts -> 43 | [| for _ in 0 .. testCount - 1 -> 44 | let d = Dictionary() 45 | 46 | while d.Count < count do 47 | let k = rng.Next (minKey, maxKey) 48 | // Make the range of keys brutal for a naive Hashing function for mapping keys to slots 49 | // let k = ((rng.Next (minKey, maxKey)) <<< 16) 50 | let v = rng.Next maxValue 51 | d[k] <- v 52 | 53 | d 54 | |> Seq.map (|KeyValue|) 55 | |> Array.ofSeq 56 | |] 57 | |] 58 | 59 | // Get samples of random keys to look up for each data set 60 | let intKeySets = 61 | [| for keyCount, count in valueCounts -> 62 | [| for testKey in 0 .. testCount - 1 -> 63 | let data = intDataSets[int keyCount][testKey] 64 | [| for _ in 1 .. lookupCount -> 65 | // Next is exclusive on the upper bound 66 | fst data[rng.Next data.Length] |] 67 | |] 68 | |] 69 | 70 | let structDataSets = 71 | [| for _, count in valueCounts -> 72 | [| for _ in 0 .. testCount - 1 -> 73 | let d = Dictionary() 74 | 75 | while d.Count < count do 76 | let k = rng.Next (minKey, maxKey) 77 | let k = { StructKey.Value = k } 78 | // Make the range of keys brutal for a naive Hashing function for mapping keys to slots 79 | // let k = ((rng.Next (minKey, maxKey)) <<< 16) 80 | let v = rng.Next maxValue 81 | d[k] <- v 82 | 83 | d 84 | |> Seq.map (|KeyValue|) 85 | |> Array.ofSeq 86 | |] 87 | |] 88 | 89 | // Get samples of random keys to look up for each data set 90 | let structKeySets = 91 | [| for keyCount, count in valueCounts -> 92 | [| for testKey in 0 .. testCount - 1 -> 93 | let data = structDataSets[int keyCount][testKey] 94 | [| for _ in 1 .. lookupCount -> 95 | // Next is exclusive on the upper bound 96 | fst data[rng.Next data.Length] |] 97 | |] 98 | |] 99 | 100 | let refDataSets = 101 | [| for _, count in valueCounts -> 102 | [| for _ in 0 .. testCount - 1 -> 103 | let d = Dictionary() 104 | 105 | while d.Count < count do 106 | let k = rng.Next (minKey, maxKey) 107 | let k = { RefKey.Value = k } 108 | // Make the range of keys brutal for a naive Hashing function for mapping keys to slots 109 | // let k = ((rng.Next (minKey, maxKey)) <<< 16) 110 | let v = rng.Next maxValue 111 | d[k] <- v 112 | 113 | d 114 | |> Seq.map (|KeyValue|) 115 | |> Array.ofSeq 116 | |] 117 | |] 118 | 119 | // Get samples of random keys to look up for each data set 120 | let refKeySets = 121 | [| for keyCount, count in valueCounts -> 122 | [| for testKey in 0 .. testCount - 1 -> 123 | let data = refDataSets[int keyCount][testKey] 124 | [| for _ in 1 .. lookupCount -> 125 | // Next is exclusive on the upper bound 126 | fst data[rng.Next data.Length] |] 127 | |] 128 | |] 129 | 130 | 131 | let strDataSets = 132 | [| for _, count in valueCounts -> 133 | [| for _ in 0 .. testCount - 1 -> 134 | let d = Dictionary() 135 | 136 | while d.Count < count do 137 | let k = $"Key[{((rng.Next (minKey, maxKey)) <<< 16)}]" 138 | let v = rng.Next maxValue 139 | d[k] <- v 140 | 141 | d 142 | |> Seq.map (|KeyValue|) 143 | |> Array.ofSeq 144 | |] 145 | |] 146 | 147 | // Get samples of random keys to look up for each data set 148 | let strKeySets = 149 | [| for keyCount, count in valueCounts -> 150 | [| for testKey in 0 .. testCount - 1 -> 151 | let data = strDataSets[int keyCount][testKey] 152 | [| for _ in 1 .. lookupCount -> 153 | // Next is exclusive on the upper bound 154 | let key = fst data[rng.Next data.Length] 155 | let newKey = System.String key 156 | newKey |] 157 | |] 158 | |] 159 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/EmbeddedHead.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | type EmbeddedHead () = 15 | 16 | let intDictionaries = 17 | [| for countKey, _ in valueCounts -> 18 | [|for testKey in 0 .. testCount - 1 -> 19 | intDataSets[int countKey][testKey] 20 | |> Array.map KeyValuePair 21 | |> Dictionary 22 | |] 23 | |] 24 | 25 | let strDictionaries = 26 | [| for countKey, _ in valueCounts -> 27 | [|for testKey in 0 .. testCount - 1 -> 28 | strDataSets[int countKey][testKey] 29 | |> Array.map KeyValuePair 30 | |> Dictionary 31 | |] 32 | |] 33 | 34 | let intTestDictionaries = 35 | [| for countKey, _ in valueCounts -> 36 | [|for testKey in 0 .. testCount - 1 -> 37 | intDataSets[int countKey][testKey] 38 | |> EmbeddedHead.Dictionary 39 | |] 40 | |] 41 | 42 | let strTestDictionaries = 43 | [| for countKey, _ in valueCounts -> 44 | [|for testKey in 0 .. testCount - 1 -> 45 | strDataSets[int countKey][testKey] 46 | |> EmbeddedHead.Dictionary 47 | |] 48 | |] 49 | 50 | [] 51 | member val KeyType = KeyType.Int with get, set 52 | 53 | [] 59 | member val KeyCount = KeyCount.``10`` with get, set 60 | 61 | 62 | [] 63 | member b.Dictionary () = 64 | 65 | if b.KeyType = KeyType.Int then 66 | let mutable acc = 0 67 | let dataSet = intDictionaries[int b.KeyCount] 68 | let keySet = intKeySets[int b.KeyCount] 69 | 70 | for testKey in 0 .. testCount - 1 do 71 | let data = dataSet[testKey] 72 | let keys = keySet[testKey] 73 | 74 | for k in keys do 75 | acc <- acc + data[k] 76 | 77 | acc 78 | 79 | else 80 | let mutable acc = 0 81 | let dataSet = strDictionaries[int b.KeyCount] 82 | let keySet = strKeySets[int b.KeyCount] 83 | 84 | for testKey in 0 .. testCount - 1 do 85 | let data = dataSet[testKey] 86 | let keys = keySet[testKey] 87 | 88 | for k in keys do 89 | acc <- acc + data[k] 90 | 91 | acc 92 | 93 | 94 | [] 95 | member b.Test () = 96 | 97 | if b.KeyType = KeyType.Int then 98 | let mutable acc = 0 99 | let dataSet = intTestDictionaries[int b.KeyCount] 100 | let keySet = intKeySets[int b.KeyCount] 101 | 102 | for testKey in 0 .. testCount - 1 do 103 | let data = dataSet[testKey] 104 | let keys = keySet[testKey] 105 | 106 | for k in keys do 107 | acc <- acc + data[k] 108 | 109 | acc 110 | 111 | else 112 | let mutable acc = 0 113 | let dataSet = strTestDictionaries[int b.KeyCount] 114 | let keySet = strKeySets[int b.KeyCount] 115 | 116 | for testKey in 0 .. testCount - 1 do 117 | let data = dataSet[testKey] 118 | let keys = keySet[testKey] 119 | 120 | for k in keys do 121 | acc <- acc + data[k] 122 | 123 | acc 124 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/FastDictionaryTest.Benchmark.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | x64 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/FastTypeBranch.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | type FastTypeBranch () = 15 | 16 | let intDictionaries = 17 | [| for countKey, _ in valueCounts -> 18 | [|for testKey in 0 .. testCount - 1 -> 19 | intDataSets[int countKey][testKey] 20 | |> Array.map KeyValuePair 21 | |> Dictionary 22 | |] 23 | |] 24 | 25 | let strDictionaries = 26 | [| for countKey, _ in valueCounts -> 27 | [|for testKey in 0 .. testCount - 1 -> 28 | strDataSets[int countKey][testKey] 29 | |> Array.map KeyValuePair 30 | |> Dictionary 31 | |] 32 | |] 33 | 34 | let intTestDictionaries = 35 | [| for countKey, _ in valueCounts -> 36 | [|for testKey in 0 .. testCount - 1 -> 37 | intDataSets[int countKey][testKey] 38 | |> FastTypeBranch.Dictionary 39 | |] 40 | |] 41 | 42 | let strTestDictionaries = 43 | [| for countKey, _ in valueCounts -> 44 | [|for testKey in 0 .. testCount - 1 -> 45 | strDataSets[int countKey][testKey] 46 | |> FastTypeBranch.Dictionary 47 | |] 48 | |] 49 | 50 | [] 51 | member val KeyType = KeyType.Int with get, set 52 | 53 | [] 59 | member val KeyCount = KeyCount.``10`` with get, set 60 | 61 | 62 | [] 63 | member b.Dictionary () = 64 | 65 | if b.KeyType = KeyType.Int then 66 | let mutable acc = 0 67 | let dataSet = intDictionaries[int b.KeyCount] 68 | let keySet = intKeySets[int b.KeyCount] 69 | 70 | for testKey in 0 .. testCount - 1 do 71 | let data = dataSet[testKey] 72 | let keys = keySet[testKey] 73 | 74 | for k in keys do 75 | acc <- acc + data[k] 76 | 77 | acc 78 | 79 | else 80 | let mutable acc = 0 81 | let dataSet = strDictionaries[int b.KeyCount] 82 | let keySet = strKeySets[int b.KeyCount] 83 | 84 | for testKey in 0 .. testCount - 1 do 85 | let data = dataSet[testKey] 86 | let keys = keySet[testKey] 87 | 88 | for k in keys do 89 | acc <- acc + data[k] 90 | 91 | acc 92 | 93 | 94 | [] 95 | member b.Test () = 96 | 97 | if b.KeyType = KeyType.Int then 98 | let mutable acc = 0 99 | let dataSet = intTestDictionaries[int b.KeyCount] 100 | let keySet = intKeySets[int b.KeyCount] 101 | 102 | for testKey in 0 .. testCount - 1 do 103 | let data = dataSet[testKey] 104 | let keys = keySet[testKey] 105 | 106 | for k in keys do 107 | acc <- acc + data[k] 108 | 109 | acc 110 | 111 | else 112 | let mutable acc = 0 113 | let dataSet = strTestDictionaries[int b.KeyCount] 114 | let keySet = strKeySets[int b.KeyCount] 115 | 116 | for testKey in 0 .. testCount - 1 do 117 | let data = dataSet[testKey] 118 | let keys = keySet[testKey] 119 | 120 | for k in keys do 121 | acc <- acc + data[k] 122 | 123 | acc 124 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/FibonacciHashing.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | type FibonacciHashing () = 15 | 16 | let intDictionaries = 17 | [| for countKey, _ in valueCounts -> 18 | [|for testKey in 0 .. testCount - 1 -> 19 | intDataSets[int countKey][testKey] 20 | |> Array.map KeyValuePair 21 | |> Dictionary 22 | |] 23 | |] 24 | 25 | let strDictionaries = 26 | [| for countKey, _ in valueCounts -> 27 | [|for testKey in 0 .. testCount - 1 -> 28 | strDataSets[int countKey][testKey] 29 | |> Array.map KeyValuePair 30 | |> Dictionary 31 | |] 32 | |] 33 | 34 | let intTestDictionaries = 35 | [| for countKey, _ in valueCounts -> 36 | [|for testKey in 0 .. testCount - 1 -> 37 | intDataSets[int countKey][testKey] 38 | |> FibonacciHashing.Dictionary 39 | |] 40 | |] 41 | 42 | let strTestDictionaries = 43 | [| for countKey, _ in valueCounts -> 44 | [|for testKey in 0 .. testCount - 1 -> 45 | strDataSets[int countKey][testKey] 46 | |> FibonacciHashing.Dictionary 47 | |] 48 | |] 49 | 50 | [] 51 | member val KeyType = KeyType.Int with get, set 52 | 53 | [] 59 | member val KeyCount = KeyCount.``10`` with get, set 60 | 61 | 62 | [] 63 | member b.Dictionary () = 64 | 65 | if b.KeyType = KeyType.Int then 66 | let mutable acc = 0 67 | let dataSet = intDictionaries[int b.KeyCount] 68 | let keySet = intKeySets[int b.KeyCount] 69 | 70 | for testKey in 0 .. testCount - 1 do 71 | let data = dataSet[testKey] 72 | let keys = keySet[testKey] 73 | 74 | for k in keys do 75 | acc <- acc + data[k] 76 | 77 | acc 78 | 79 | else 80 | let mutable acc = 0 81 | let dataSet = strDictionaries[int b.KeyCount] 82 | let keySet = strKeySets[int b.KeyCount] 83 | 84 | for testKey in 0 .. testCount - 1 do 85 | let data = dataSet[testKey] 86 | let keys = keySet[testKey] 87 | 88 | for k in keys do 89 | acc <- acc + data[k] 90 | 91 | acc 92 | 93 | 94 | [] 95 | member b.Test () = 96 | 97 | if b.KeyType = KeyType.Int then 98 | let mutable acc = 0 99 | let dataSet = intTestDictionaries[int b.KeyCount] 100 | let keySet = intKeySets[int b.KeyCount] 101 | 102 | for testKey in 0 .. testCount - 1 do 103 | let data = dataSet[testKey] 104 | let keys = keySet[testKey] 105 | 106 | for k in keys do 107 | acc <- acc + data[k] 108 | 109 | acc 110 | 111 | else 112 | let mutable acc = 0 113 | let dataSet = strTestDictionaries[int b.KeyCount] 114 | let keySet = strKeySets[int b.KeyCount] 115 | 116 | for testKey in 0 .. testCount - 1 do 117 | let data = dataSet[testKey] 118 | let keys = keySet[testKey] 119 | 120 | for k in keys do 121 | acc <- acc + data[k] 122 | 123 | acc 124 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/LinearProbing.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | type LinearProbing () = 15 | 16 | let intDictionaries = 17 | [| for countKey, _ in valueCounts -> 18 | [|for testKey in 0 .. testCount - 1 -> 19 | intDataSets[int countKey][testKey] 20 | |> Array.map KeyValuePair 21 | |> Dictionary 22 | |] 23 | |] 24 | 25 | let strDictionaries = 26 | [| for countKey, _ in valueCounts -> 27 | [|for testKey in 0 .. testCount - 1 -> 28 | strDataSets[int countKey][testKey] 29 | |> Array.map KeyValuePair 30 | |> Dictionary 31 | |] 32 | |] 33 | 34 | let intTestDictionaries = 35 | [| for countKey, _ in valueCounts -> 36 | [|for testKey in 0 .. testCount - 1 -> 37 | intDataSets[int countKey][testKey] 38 | |> LinearProbing.Dictionary 39 | |] 40 | |] 41 | 42 | let strTestDictionaries = 43 | [| for countKey, _ in valueCounts -> 44 | [|for testKey in 0 .. testCount - 1 -> 45 | strDataSets[int countKey][testKey] 46 | |> LinearProbing.Dictionary 47 | |] 48 | |] 49 | 50 | [] 51 | member val KeyType = KeyType.Int with get, set 52 | 53 | [] 59 | member val KeyCount = KeyCount.``10`` with get, set 60 | 61 | 62 | [] 63 | member b.Dictionary () = 64 | 65 | if b.KeyType = KeyType.Int then 66 | let mutable acc = 0 67 | let dataSet = intDictionaries[int b.KeyCount] 68 | let keySet = intKeySets[int b.KeyCount] 69 | 70 | for testKey in 0 .. testCount - 1 do 71 | let data = dataSet[testKey] 72 | let keys = keySet[testKey] 73 | 74 | for k in keys do 75 | acc <- acc + data[k] 76 | 77 | acc 78 | 79 | else 80 | let mutable acc = 0 81 | let dataSet = strDictionaries[int b.KeyCount] 82 | let keySet = strKeySets[int b.KeyCount] 83 | 84 | for testKey in 0 .. testCount - 1 do 85 | let data = dataSet[testKey] 86 | let keys = keySet[testKey] 87 | 88 | for k in keys do 89 | acc <- acc + data[k] 90 | 91 | acc 92 | 93 | 94 | [] 95 | member b.Test () = 96 | 97 | if b.KeyType = KeyType.Int then 98 | let mutable acc = 0 99 | let dataSet = intTestDictionaries[int b.KeyCount] 100 | let keySet = intKeySets[int b.KeyCount] 101 | 102 | for testKey in 0 .. testCount - 1 do 103 | let data = dataSet[testKey] 104 | let keys = keySet[testKey] 105 | 106 | for k in keys do 107 | acc <- acc + data[k] 108 | 109 | acc 110 | 111 | else 112 | let mutable acc = 0 113 | let dataSet = strTestDictionaries[int b.KeyCount] 114 | let keySet = strKeySets[int b.KeyCount] 115 | 116 | for testKey in 0 .. testCount - 1 do 117 | let data = dataSet[testKey] 118 | let keys = keySet[testKey] 119 | 120 | for k in keys do 121 | acc <- acc + data[k] 122 | 123 | acc 124 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/Naive.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | type Naive () = 15 | 16 | let intDictionaries = 17 | [| for countKey, _ in valueCounts -> 18 | [|for testKey in 0 .. testCount - 1 -> 19 | intDataSets[int countKey][testKey] 20 | |> Array.map KeyValuePair 21 | |> Dictionary 22 | |] 23 | |] 24 | 25 | let strDictionaries = 26 | [| for countKey, _ in valueCounts -> 27 | [|for testKey in 0 .. testCount - 1 -> 28 | strDataSets[int countKey][testKey] 29 | |> Array.map KeyValuePair 30 | |> Dictionary 31 | |] 32 | |] 33 | 34 | let intTestDictionaries = 35 | [| for countKey, _ in valueCounts -> 36 | [|for testKey in 0 .. testCount - 1 -> 37 | intDataSets[int countKey][testKey] 38 | |> Naive.Dictionary 39 | |] 40 | |] 41 | 42 | let strTestDictionaries = 43 | [| for countKey, _ in valueCounts -> 44 | [|for testKey in 0 .. testCount - 1 -> 45 | strDataSets[int countKey][testKey] 46 | |> Naive.Dictionary 47 | |] 48 | |] 49 | 50 | [] 51 | member val KeyType = KeyType.Int with get, set 52 | 53 | [] 59 | member val KeyCount = KeyCount.``10`` with get, set 60 | 61 | 62 | [] 63 | member b.Dictionary () = 64 | 65 | if b.KeyType = KeyType.Int then 66 | let mutable acc = 0 67 | let dataSet = intDictionaries[int b.KeyCount] 68 | let keySet = intKeySets[int b.KeyCount] 69 | 70 | for testKey in 0 .. testCount - 1 do 71 | let data = dataSet[testKey] 72 | let keys = keySet[testKey] 73 | 74 | for k in keys do 75 | acc <- acc + data[k] 76 | 77 | acc 78 | 79 | else 80 | let mutable acc = 0 81 | let dataSet = strDictionaries[int b.KeyCount] 82 | let keySet = strKeySets[int b.KeyCount] 83 | 84 | for testKey in 0 .. testCount - 1 do 85 | let data = dataSet[testKey] 86 | let keys = keySet[testKey] 87 | 88 | for k in keys do 89 | acc <- acc + data[k] 90 | 91 | acc 92 | 93 | 94 | [] 95 | member b.Test () = 96 | 97 | if b.KeyType = KeyType.Int then 98 | let mutable acc = 0 99 | let dataSet = intTestDictionaries[int b.KeyCount] 100 | let keySet = intKeySets[int b.KeyCount] 101 | 102 | for testKey in 0 .. testCount - 1 do 103 | let data = dataSet[testKey] 104 | let keys = keySet[testKey] 105 | 106 | for k in keys do 107 | acc <- acc + data[k] 108 | 109 | acc 110 | 111 | else 112 | let mutable acc = 0 113 | let dataSet = strTestDictionaries[int b.KeyCount] 114 | let keySet = strKeySets[int b.KeyCount] 115 | 116 | for testKey in 0 .. testCount - 1 do 117 | let data = dataSet[testKey] 118 | let keys = keySet[testKey] 119 | 120 | for k in keys do 121 | acc <- acc + data[k] 122 | 123 | acc 124 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/Program.fs: -------------------------------------------------------------------------------- 1 | open Argu 2 | open BenchmarkDotNet.Running 3 | open FastDictionaryTest.Benchmark 4 | 5 | 6 | [] 7 | type Args = 8 | | Task of task: string 9 | | Benchmark of benchmark: string 10 | | Iterations of iterations: int 11 | | Suite of suite: string 12 | 13 | interface IArgParserTemplate with 14 | member this.Usage = 15 | match this with 16 | | Task _ -> "Which task to perform. Options: Benchmark or Profile" 17 | | Benchmark _ -> "Which Benchmark to profile." 18 | | Iterations _ -> "Number of iterations of the Method to perform for profiling" 19 | | Suite _ -> "White suite of benchmarks to run" 20 | 21 | 22 | let profile (version: string) loopCount = 23 | 24 | printfn $"Profiling: {version}, LoopCount: {loopCount}" 25 | let mutable result = 0 26 | 27 | match version.ToLower() with 28 | | "naive" -> 29 | let b = Naive() 30 | for i in 1 .. loopCount do 31 | result <- b.Test() 32 | 33 | | "zeroalloc" -> 34 | let b = ZeroAllocation() 35 | for i in 1 .. loopCount do 36 | result <- b.Test() 37 | 38 | | "fibonaccihashing" -> 39 | let b = FibonacciHashing() 40 | for i in 1 .. loopCount do 41 | result <- b.Test() 42 | 43 | | "cacheequality" -> 44 | let b = CacheEquality() 45 | for i in 1 .. loopCount do 46 | result <- b.Test() 47 | 48 | | "fasttypebranch" -> 49 | let b = CacheEquality() 50 | for i in 1 .. loopCount do 51 | result <- b.Test() 52 | 53 | | "embeddedhead" -> 54 | let b = EmbeddedHead() 55 | for i in 1 .. loopCount do 56 | result <- b.Test() 57 | 58 | | "linearprobing" -> 59 | let b = LinearProbing() 60 | for i in 1 .. loopCount do 61 | result <- b.Test() 62 | 63 | | "robinhood" -> 64 | let b = RobinHood() 65 | for i in 1 .. loopCount do 66 | result <- b.Test() 67 | 68 | | "robinhoodeviction" -> 69 | let b = RobinHoodEviction() 70 | for i in 1 .. loopCount do 71 | result <- b.Test() 72 | 73 | | "bytelist" -> 74 | let b = ByteList() 75 | for i in 1 .. loopCount do 76 | result <- b.Test() 77 | 78 | | "bytelistrobinhood" -> 79 | let b = ByteListRobinHood() 80 | for i in 1 .. loopCount do 81 | result <- b.Test() 82 | 83 | | "bytelistrobinhoodinline" -> 84 | let b = ByteListRobinHoodInline() 85 | for i in 1 .. loopCount do 86 | result <- b.Test() 87 | 88 | | "cstyle" -> 89 | let b = CStyle() 90 | for i in 1 .. loopCount do 91 | result <- b.Test() 92 | 93 | | "bytelistrobinhoodvec128" -> 94 | let b = ByteListRobinHoodVec128() 95 | for i in 1 .. loopCount do 96 | result <- b.Test() 97 | 98 | | unknownVersion -> 99 | failwith $"Unknown version: {unknownVersion}" 100 | 101 | result 102 | 103 | 104 | [] 105 | let main argv = 106 | 107 | let parser = ArgumentParser.Create (programName = "Fast Dictionary") 108 | let results = parser.Parse argv 109 | let task = results.GetResult Args.Task 110 | 111 | match task.ToLower() with 112 | | "benchmark" -> 113 | let suite = results.GetResult Args.Suite 114 | 115 | match suite.ToLower() with 116 | | "baseline" -> 117 | let _ = BenchmarkRunner.Run() 118 | () 119 | 120 | | "naive" -> 121 | let _ = BenchmarkRunner.Run() 122 | () 123 | 124 | | "zeroallocation" -> 125 | let _ = BenchmarkRunner.Run() 126 | () 127 | 128 | | "fibonaccihashing" -> 129 | let _ = BenchmarkRunner.Run() 130 | () 131 | 132 | | "cacheequality" -> 133 | let _ = BenchmarkRunner.Run() 134 | () 135 | 136 | | "fasttypebranch" -> 137 | let _ = BenchmarkRunner.Run() 138 | () 139 | 140 | | "embeddedhead" -> 141 | let _ = BenchmarkRunner.Run() 142 | () 143 | 144 | | "linearprobing" -> 145 | let _ = BenchmarkRunner.Run() 146 | () 147 | 148 | | "robinhood" -> 149 | let _ = BenchmarkRunner.Run() 150 | () 151 | 152 | | "robinhoodeviction" -> 153 | let _ = BenchmarkRunner.Run() 154 | () 155 | 156 | | "bytelist" -> 157 | let _ = BenchmarkRunner.Run() 158 | () 159 | 160 | | "bytelistrobinhood" -> 161 | let _ = BenchmarkRunner.Run() 162 | () 163 | 164 | | "bytelistrobinhoodinline" -> 165 | let _ = BenchmarkRunner.Run() 166 | () 167 | 168 | | "soa" -> 169 | let _ = BenchmarkRunner.Run() 170 | () 171 | 172 | | "cstyle" -> 173 | let _ = BenchmarkRunner.Run() 174 | () 175 | 176 | | "bytelistrobinhoodvec128" -> 177 | let _ = BenchmarkRunner.Run() 178 | () 179 | 180 | | unknownSuite -> 181 | failwith $"Unknown suite of benchmarks: {unknownSuite}" 182 | () 183 | 184 | | "profile" -> 185 | let method = results.GetResult Args.Benchmark 186 | let iterations = results.GetResult Args.Iterations 187 | let _ = profile method iterations 188 | () 189 | 190 | | unknownTask -> failwith $"Unknown task: {unknownTask}" 191 | 192 | 1 193 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/RobinHood.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | type RobinHood () = 15 | 16 | let intDictionaries = 17 | [| for countKey, _ in valueCounts -> 18 | [|for testKey in 0 .. testCount - 1 -> 19 | intDataSets[int countKey][testKey] 20 | |> Array.map KeyValuePair 21 | |> Dictionary 22 | |] 23 | |] 24 | 25 | let strDictionaries = 26 | [| for countKey, _ in valueCounts -> 27 | [|for testKey in 0 .. testCount - 1 -> 28 | strDataSets[int countKey][testKey] 29 | |> Array.map KeyValuePair 30 | |> Dictionary 31 | |] 32 | |] 33 | 34 | let intTestDictionaries = 35 | [| for countKey, _ in valueCounts -> 36 | [|for testKey in 0 .. testCount - 1 -> 37 | intDataSets[int countKey][testKey] 38 | |> RobinHood.Dictionary 39 | |] 40 | |] 41 | 42 | let strTestDictionaries = 43 | [| for countKey, _ in valueCounts -> 44 | [|for testKey in 0 .. testCount - 1 -> 45 | strDataSets[int countKey][testKey] 46 | |> RobinHood.Dictionary 47 | |] 48 | |] 49 | 50 | [] 51 | member val KeyType = KeyType.Int with get, set 52 | 53 | [] 59 | member val KeyCount = KeyCount.``10`` with get, set 60 | 61 | 62 | [] 63 | member b.Dictionary () = 64 | 65 | if b.KeyType = KeyType.Int then 66 | let mutable acc = 0 67 | let dataSet = intDictionaries[int b.KeyCount] 68 | let keySet = intKeySets[int b.KeyCount] 69 | 70 | for testKey in 0 .. testCount - 1 do 71 | let data = dataSet[testKey] 72 | let keys = keySet[testKey] 73 | 74 | for k in keys do 75 | acc <- acc + data[k] 76 | 77 | acc 78 | 79 | else 80 | let mutable acc = 0 81 | let dataSet = strDictionaries[int b.KeyCount] 82 | let keySet = strKeySets[int b.KeyCount] 83 | 84 | for testKey in 0 .. testCount - 1 do 85 | let data = dataSet[testKey] 86 | let keys = keySet[testKey] 87 | 88 | for k in keys do 89 | acc <- acc + data[k] 90 | 91 | acc 92 | 93 | 94 | [] 95 | member b.Test () = 96 | 97 | if b.KeyType = KeyType.Int then 98 | let mutable acc = 0 99 | let dataSet = intTestDictionaries[int b.KeyCount] 100 | let keySet = intKeySets[int b.KeyCount] 101 | 102 | for testKey in 0 .. testCount - 1 do 103 | let data = dataSet[testKey] 104 | let keys = keySet[testKey] 105 | 106 | for k in keys do 107 | acc <- acc + data[k] 108 | 109 | acc 110 | 111 | else 112 | let mutable acc = 0 113 | let dataSet = strTestDictionaries[int b.KeyCount] 114 | let keySet = strKeySets[int b.KeyCount] 115 | 116 | for testKey in 0 .. testCount - 1 do 117 | let data = dataSet[testKey] 118 | let keys = keySet[testKey] 119 | 120 | for k in keys do 121 | acc <- acc + data[k] 122 | 123 | acc 124 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/RobinHoodEviction.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | type RobinHoodEviction () = 15 | 16 | let intDictionaries = 17 | [| for countKey, _ in valueCounts -> 18 | [|for testKey in 0 .. testCount - 1 -> 19 | intDataSets[int countKey][testKey] 20 | |> Array.map KeyValuePair 21 | |> Dictionary 22 | |] 23 | |] 24 | 25 | let strDictionaries = 26 | [| for countKey, _ in valueCounts -> 27 | [|for testKey in 0 .. testCount - 1 -> 28 | strDataSets[int countKey][testKey] 29 | |> Array.map KeyValuePair 30 | |> Dictionary 31 | |] 32 | |] 33 | 34 | let intTestDictionaries = 35 | [| for countKey, _ in valueCounts -> 36 | [|for testKey in 0 .. testCount - 1 -> 37 | intDataSets[int countKey][testKey] 38 | |> RobinHoodEviction.Dictionary 39 | |] 40 | |] 41 | 42 | let strTestDictionaries = 43 | [| for countKey, _ in valueCounts -> 44 | [|for testKey in 0 .. testCount - 1 -> 45 | strDataSets[int countKey][testKey] 46 | |> RobinHoodEviction.Dictionary 47 | |] 48 | |] 49 | 50 | [] 51 | member val KeyType = KeyType.Int with get, set 52 | 53 | [] 59 | member val KeyCount = KeyCount.``10`` with get, set 60 | 61 | 62 | [] 63 | member b.Dictionary () = 64 | 65 | if b.KeyType = KeyType.Int then 66 | let mutable acc = 0 67 | let dataSet = intDictionaries[int b.KeyCount] 68 | let keySet = intKeySets[int b.KeyCount] 69 | 70 | for testKey in 0 .. testCount - 1 do 71 | let data = dataSet[testKey] 72 | let keys = keySet[testKey] 73 | 74 | for k in keys do 75 | acc <- acc + data[k] 76 | 77 | acc 78 | 79 | else 80 | let mutable acc = 0 81 | let dataSet = strDictionaries[int b.KeyCount] 82 | let keySet = strKeySets[int b.KeyCount] 83 | 84 | for testKey in 0 .. testCount - 1 do 85 | let data = dataSet[testKey] 86 | let keys = keySet[testKey] 87 | 88 | for k in keys do 89 | acc <- acc + data[k] 90 | 91 | acc 92 | 93 | 94 | [] 95 | member b.Test () = 96 | 97 | if b.KeyType = KeyType.Int then 98 | let mutable acc = 0 99 | let dataSet = intTestDictionaries[int b.KeyCount] 100 | let keySet = intKeySets[int b.KeyCount] 101 | 102 | for testKey in 0 .. testCount - 1 do 103 | let data = dataSet[testKey] 104 | let keys = keySet[testKey] 105 | 106 | for k in keys do 107 | acc <- acc + data[k] 108 | 109 | acc 110 | 111 | else 112 | let mutable acc = 0 113 | let dataSet = strTestDictionaries[int b.KeyCount] 114 | let keySet = strKeySets[int b.KeyCount] 115 | 116 | for testKey in 0 .. testCount - 1 do 117 | let data = dataSet[testKey] 118 | let keys = keySet[testKey] 119 | 120 | for k in keys do 121 | acc <- acc + data[k] 122 | 123 | acc 124 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/SOA.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | open System.Collections.Frozen 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | [] 15 | type SOA () = 16 | 17 | let intDictionaries = 18 | [| for countKey, _ in valueCounts -> 19 | [|for testKey in 0 .. testCount - 1 -> 20 | intDataSets[int countKey][testKey] 21 | |> Array.map KeyValuePair 22 | |> Dictionary 23 | |] 24 | |] 25 | 26 | let strDictionaries = 27 | [| for countKey, _ in valueCounts -> 28 | [|for testKey in 0 .. testCount - 1 -> 29 | strDataSets[int countKey][testKey] 30 | |> Array.map KeyValuePair 31 | |> Dictionary 32 | |] 33 | |] 34 | 35 | let structDictionaries = 36 | [| for countKey, _ in valueCounts -> 37 | [|for testKey in 0 .. testCount - 1 -> 38 | structDataSets[int countKey][testKey] 39 | |> Array.map KeyValuePair 40 | |> Dictionary 41 | |] 42 | |] 43 | 44 | let refDictionaries = 45 | [| for countKey, _ in valueCounts -> 46 | [|for testKey in 0 .. testCount - 1 -> 47 | refDataSets[int countKey][testKey] 48 | |> Array.map KeyValuePair 49 | |> Dictionary 50 | |] 51 | |] 52 | 53 | let intFrozenDictionaries = 54 | [| for countKey, _ in valueCounts -> 55 | [|for testKey in 0 .. testCount - 1 -> 56 | intDataSets[int countKey][testKey] 57 | |> Array.map KeyValuePair 58 | |> Dictionary 59 | |> fun x -> FrozenDictionary.ToFrozenDictionary(x) 60 | |] 61 | |] 62 | 63 | let strFrozenDictionaries = 64 | [| for countKey, _ in valueCounts -> 65 | [|for testKey in 0 .. testCount - 1 -> 66 | strDataSets[int countKey][testKey] 67 | |> Array.map KeyValuePair 68 | |> Dictionary 69 | |> fun x -> FrozenDictionary.ToFrozenDictionary(x) 70 | |] 71 | |] 72 | 73 | let structFrozenDictionaries = 74 | [| for countKey, _ in valueCounts -> 75 | [|for testKey in 0 .. testCount - 1 -> 76 | structDataSets[int countKey][testKey] 77 | |> Array.map KeyValuePair 78 | |> Dictionary 79 | |> fun x -> FrozenDictionary.ToFrozenDictionary(x) 80 | |] 81 | |] 82 | 83 | let refFrozenDictionaries = 84 | [| for countKey, _ in valueCounts -> 85 | [|for testKey in 0 .. testCount - 1 -> 86 | refDataSets[int countKey][testKey] 87 | |> Array.map KeyValuePair 88 | |> Dictionary 89 | |> fun x -> FrozenDictionary.ToFrozenDictionary(x) 90 | |] 91 | |] 92 | 93 | let intTestDictionaries = 94 | [| for countKey, _ in valueCounts -> 95 | [|for testKey in 0 .. testCount - 1 -> 96 | intDataSets[int countKey][testKey] 97 | |> SOA.StaticDict.create 98 | |] 99 | |] 100 | 101 | let strTestDictionaries = 102 | [| for countKey, _ in valueCounts -> 103 | [|for testKey in 0 .. testCount - 1 -> 104 | strDataSets[int countKey][testKey] 105 | |> SOA.StaticDict.create 106 | |] 107 | |] 108 | 109 | let structTestDictionaries = 110 | [| for countKey, _ in valueCounts -> 111 | [|for testKey in 0 .. testCount - 1 -> 112 | structDataSets[int countKey][testKey] 113 | |> SOA.StaticDict.create 114 | |] 115 | |] 116 | 117 | let refTestDictionaries = 118 | [| for countKey, _ in valueCounts -> 119 | [|for testKey in 0 .. testCount - 1 -> 120 | refDataSets[int countKey][testKey] 121 | |> SOA.StaticDict.create 122 | |] 123 | |] 124 | 125 | // [] 126 | [] 127 | member val KeyType = KeyType.Int with get, set 128 | 129 | [] 135 | member val KeyCount = KeyCount.``10`` with get, set 136 | 137 | 138 | [] 139 | member b.Dictionary () = 140 | 141 | if b.KeyType = KeyType.Int then 142 | let mutable acc = 0 143 | let dataSet = intDictionaries[int b.KeyCount] 144 | let keySet = intKeySets[int b.KeyCount] 145 | 146 | for testKey in 0 .. testCount - 1 do 147 | let data = dataSet[testKey] 148 | let keys = keySet[testKey] 149 | 150 | for k in keys do 151 | acc <- acc + data[k] 152 | 153 | acc 154 | 155 | elif b.KeyType = KeyType.String then 156 | let mutable acc = 0 157 | let dataSet = strDictionaries[int b.KeyCount] 158 | let keySet = strKeySets[int b.KeyCount] 159 | 160 | for testKey in 0 .. testCount - 1 do 161 | let data = dataSet[testKey] 162 | let keys = keySet[testKey] 163 | 164 | for k in keys do 165 | acc <- acc + data[k] 166 | 167 | acc 168 | 169 | elif b.KeyType = KeyType.Struct then 170 | let mutable acc = 0 171 | let dataSet = structDictionaries[int b.KeyCount] 172 | let keySet = structKeySets[int b.KeyCount] 173 | 174 | for testKey in 0 .. testCount - 1 do 175 | let data = dataSet[testKey] 176 | let keys = keySet[testKey] 177 | 178 | for k in keys do 179 | acc <- acc + data[k] 180 | 181 | acc 182 | 183 | else 184 | let mutable acc = 0 185 | let dataSet = refDictionaries[int b.KeyCount] 186 | let keySet = refKeySets[int b.KeyCount] 187 | 188 | for testKey in 0 .. testCount - 1 do 189 | let data = dataSet[testKey] 190 | let keys = keySet[testKey] 191 | 192 | for k in keys do 193 | acc <- acc + data[k] 194 | 195 | acc 196 | 197 | [] 198 | member b.FrozenDictionary () = 199 | 200 | if b.KeyType = KeyType.Int then 201 | let mutable acc = 0 202 | let dataSet = intFrozenDictionaries[int b.KeyCount] 203 | let keySet = intKeySets[int b.KeyCount] 204 | 205 | for testKey in 0 .. testCount - 1 do 206 | let data = dataSet[testKey] 207 | let keys = keySet[testKey] 208 | 209 | for k in keys do 210 | acc <- acc + data[k] 211 | 212 | acc 213 | 214 | elif b.KeyType = KeyType.String then 215 | let mutable acc = 0 216 | let dataSet = strFrozenDictionaries[int b.KeyCount] 217 | let keySet = strKeySets[int b.KeyCount] 218 | 219 | for testKey in 0 .. testCount - 1 do 220 | let data = dataSet[testKey] 221 | let keys = keySet[testKey] 222 | 223 | for k in keys do 224 | acc <- acc + data[k] 225 | 226 | acc 227 | 228 | elif b.KeyType = KeyType.Struct then 229 | let mutable acc = 0 230 | let dataSet = structFrozenDictionaries[int b.KeyCount] 231 | let keySet = structKeySets[int b.KeyCount] 232 | 233 | for testKey in 0 .. testCount - 1 do 234 | let data = dataSet[testKey] 235 | let keys = keySet[testKey] 236 | 237 | for k in keys do 238 | acc <- acc + data[k] 239 | 240 | acc 241 | 242 | else 243 | let mutable acc = 0 244 | let dataSet = refFrozenDictionaries[int b.KeyCount] 245 | let keySet = refKeySets[int b.KeyCount] 246 | 247 | for testKey in 0 .. testCount - 1 do 248 | let data = dataSet[testKey] 249 | let keys = keySet[testKey] 250 | 251 | for k in keys do 252 | acc <- acc + data[k] 253 | 254 | acc 255 | 256 | 257 | [] 258 | member b.Test () = 259 | 260 | if b.KeyType = KeyType.Int then 261 | let mutable acc = 0 262 | let dataSet = intTestDictionaries[int b.KeyCount] 263 | let keySet = intKeySets[int b.KeyCount] 264 | 265 | for testKey in 0 .. testCount - 1 do 266 | let data = dataSet[testKey] 267 | let keys = keySet[testKey] 268 | 269 | for k in keys do 270 | acc <- acc + data[k] 271 | 272 | acc 273 | 274 | elif b.KeyType = KeyType.String then 275 | let mutable acc = 0 276 | let dataSet = strTestDictionaries[int b.KeyCount] 277 | let keySet = strKeySets[int b.KeyCount] 278 | 279 | for testKey in 0 .. testCount - 1 do 280 | let data = dataSet[testKey] 281 | let keys = keySet[testKey] 282 | 283 | for k in keys do 284 | acc <- acc + data[k] 285 | 286 | acc 287 | 288 | elif b.KeyType = KeyType.Struct then 289 | let mutable acc = 0 290 | let dataSet = structTestDictionaries[int b.KeyCount] 291 | let keySet = structKeySets[int b.KeyCount] 292 | 293 | for testKey in 0 .. testCount - 1 do 294 | let data = dataSet[testKey] 295 | let keys = keySet[testKey] 296 | 297 | for k in keys do 298 | acc <- acc + data[k] 299 | 300 | acc 301 | 302 | else 303 | let mutable acc = 0 304 | let dataSet = refTestDictionaries[int b.KeyCount] 305 | let keySet = refKeySets[int b.KeyCount] 306 | 307 | for testKey in 0 .. testCount - 1 do 308 | let data = dataSet[testKey] 309 | let keys = keySet[testKey] 310 | 311 | for k in keys do 312 | acc <- acc + data[k] 313 | 314 | acc 315 | -------------------------------------------------------------------------------- /FastDictionaryTest.Benchmark/ZeroAllocation.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.Benchmark 2 | 3 | 4 | open System.Collections.Generic 5 | open BenchmarkDotNet.Diagnosers 6 | open BenchmarkDotNet.Attributes 7 | open FastDictionaryTest 8 | open FastDictionaryTest.Benchmark.Domain 9 | 10 | [] 11 | [] 14 | type ZeroAllocation () = 15 | 16 | let intDictionaries = 17 | [| for countKey, _ in valueCounts -> 18 | [|for testKey in 0 .. testCount - 1 -> 19 | intDataSets[int countKey][testKey] 20 | |> Array.map KeyValuePair 21 | |> Dictionary 22 | |] 23 | |] 24 | 25 | let strDictionaries = 26 | [| for countKey, _ in valueCounts -> 27 | [|for testKey in 0 .. testCount - 1 -> 28 | strDataSets[int countKey][testKey] 29 | |> Array.map KeyValuePair 30 | |> Dictionary 31 | |] 32 | |] 33 | 34 | let intTestDictionaries = 35 | [| for countKey, _ in valueCounts -> 36 | [|for testKey in 0 .. testCount - 1 -> 37 | intDataSets[int countKey][testKey] 38 | |> ZeroAllocation.Dictionary 39 | |] 40 | |] 41 | 42 | let strTestDictionaries = 43 | [| for countKey, _ in valueCounts -> 44 | [|for testKey in 0 .. testCount - 1 -> 45 | strDataSets[int countKey][testKey] 46 | |> ZeroAllocation.Dictionary 47 | |] 48 | |] 49 | 50 | [] 51 | member val KeyType = KeyType.Int with get, set 52 | 53 | [] 59 | member val KeyCount = KeyCount.``10`` with get, set 60 | 61 | 62 | [] 63 | member b.Dictionary () = 64 | 65 | if b.KeyType = KeyType.Int then 66 | let mutable acc = 0 67 | let dataSet = intDictionaries[int b.KeyCount] 68 | let keySet = intKeySets[int b.KeyCount] 69 | 70 | for testKey in 0 .. testCount - 1 do 71 | let data = dataSet[testKey] 72 | let keys = keySet[testKey] 73 | 74 | for k in keys do 75 | acc <- acc + data[k] 76 | 77 | acc 78 | 79 | else 80 | let mutable acc = 0 81 | let dataSet = strDictionaries[int b.KeyCount] 82 | let keySet = strKeySets[int b.KeyCount] 83 | 84 | for testKey in 0 .. testCount - 1 do 85 | let data = dataSet[testKey] 86 | let keys = keySet[testKey] 87 | 88 | for k in keys do 89 | acc <- acc + data[k] 90 | 91 | acc 92 | 93 | 94 | [] 95 | member b.Test () = 96 | 97 | if b.KeyType = KeyType.Int then 98 | let mutable acc = 0 99 | let dataSet = intTestDictionaries[int b.KeyCount] 100 | let keySet = intKeySets[int b.KeyCount] 101 | 102 | for testKey in 0 .. testCount - 1 do 103 | let data = dataSet[testKey] 104 | let keys = keySet[testKey] 105 | 106 | for k in keys do 107 | acc <- acc + data[k] 108 | 109 | acc 110 | 111 | else 112 | let mutable acc = 0 113 | let dataSet = strTestDictionaries[int b.KeyCount] 114 | let keySet = strKeySets[int b.KeyCount] 115 | 116 | for testKey in 0 .. testCount - 1 do 117 | let data = dataSet[testKey] 118 | let keys = keySet[testKey] 119 | 120 | for k in keys do 121 | acc <- acc + data[k] 122 | 123 | acc 124 | -------------------------------------------------------------------------------- /FastDictionaryTest.Scratchpad/FastDictionaryTest.Scratchpad.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | x64 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /FastDictionaryTest.Scratchpad/Program.fs: -------------------------------------------------------------------------------- 1 | [] 2 | type Chicken = 3 | { 4 | Name: string 5 | mutable Parent: Option 6 | } 7 | 8 | 9 | // For more information see https://aka.ms/fsharp-console-apps 10 | printfn "Hello from F#" 11 | 12 | let mutable c = { Name = "Clucky"; Parent = None } 13 | 14 | printfn $"{c}" 15 | 16 | let c2 = { Name = "C2"; Parent = Unchecked.defaultof<_> } 17 | c.Parent <- Some c2 18 | 19 | printfn $"{c}" 20 | 21 | () -------------------------------------------------------------------------------- /FastDictionaryTest.Tests/FastDictionaryTest.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | false 6 | false 7 | x64 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /FastDictionaryTest.Tests/Program.fs: -------------------------------------------------------------------------------- 1 | module Program = 2 | 3 | [] 4 | let main _ = 0 5 | -------------------------------------------------------------------------------- /FastDictionaryTest.Tests/UnitTest1.fs: -------------------------------------------------------------------------------- 1 | module FastDictionaryTest.Tests 2 | 3 | open System.Collections.Frozen 4 | open System.Collections.Generic 5 | open NUnit.Framework 6 | 7 | [] 8 | let Setup () = 9 | () 10 | 11 | type KeyCount = 12 | | ``10`` = 0 13 | | ``100`` = 1 14 | | ``1_000`` = 2 15 | | ``10_000`` = 3 16 | 17 | let valueCounts = [| 18 | KeyCount.``10`` , 10 19 | KeyCount.``100`` , 100 20 | KeyCount.``1_000`` , 1_000 21 | KeyCount.``10_000`` , 10_000 22 | |] 23 | 24 | let rng = System.Random 123 25 | let minKey = -100_000 26 | let maxKey = 100_000 27 | let maxValue = 100_000 28 | let testCount = 100 29 | let lookupCount = 100 30 | 31 | let intDataSets = 32 | [| for _, count in valueCounts -> 33 | [| for _ in 0 .. testCount - 1 -> 34 | let d = Dictionary() 35 | 36 | while d.Count < count do 37 | let k = rng.Next (minKey, maxKey) 38 | // Make the range of keys brutal for a naive Hashing function for mapping keys to slots 39 | // let k = ((rng.Next (minKey, maxKey)) <<< 16) 40 | let v = rng.Next maxValue 41 | d[k] <- v 42 | 43 | d 44 | |> Seq.map (|KeyValue|) 45 | |> Array.ofSeq 46 | |] 47 | |] 48 | 49 | // Get samples of random keys to look up for each data set 50 | let intKeySets = 51 | [| for keyCount, count in valueCounts -> 52 | [| for testKey in 0 .. testCount - 1 -> 53 | let data = intDataSets[int keyCount][testKey] 54 | [| for _ in 1 .. lookupCount -> 55 | // Next is exclusive on the upper bound 56 | fst data[rng.Next data.Length] |] 57 | |] 58 | |] 59 | 60 | let strDataSets = 61 | [| for _, count in valueCounts -> 62 | [| for _ in 0 .. testCount - 1 -> 63 | let d = Dictionary() 64 | 65 | while d.Count < count do 66 | let k = $"Key[{((rng.Next (minKey, maxKey)) <<< 16)}]" 67 | let v = rng.Next maxValue 68 | d[k] <- v 69 | 70 | d 71 | |> Seq.map (|KeyValue|) 72 | |> Array.ofSeq 73 | |] 74 | |] 75 | 76 | // Get samples of random keys to look up for each data set 77 | let strKeySets = 78 | [| for keyCount, count in valueCounts -> 79 | [| for testKey in 0 .. testCount - 1 -> 80 | let data = strDataSets[int keyCount][testKey] 81 | [| for _ in 1 .. lookupCount -> 82 | // Next is exclusive on the upper bound 83 | fst data[rng.Next data.Length] |] 84 | |] 85 | |] 86 | 87 | 88 | [] 89 | let ``Naive Dictionary matches`` () = 90 | 91 | (intDataSets, intKeySets) 92 | ||> Array.iter2 (fun data keys -> 93 | (data, keys) 94 | ||> Array.iter2 (fun data keys -> 95 | let testDictionary = Naive.Dictionary data 96 | let expectedValues = dict data 97 | 98 | for KeyValue (k, expectedValue) in expectedValues do 99 | let actualValue = testDictionary[k] 100 | Assert.AreEqual (expectedValue, actualValue) 101 | ) 102 | ) 103 | 104 | // 105 | // 106 | // [] 107 | // let ``ZeroAlloc Dictionary matches`` () = 108 | // 109 | // let testDictionary = ZeroAllocation.Dictionary data 110 | // 111 | // for KeyValue (k, expectedValue) in expectedValues do 112 | // let actualValue = testDictionary[k] 113 | // Assert.AreEqual (expectedValue, actualValue) 114 | // 115 | // 116 | // [] 117 | // let ``EmbeddedHead Dictionary matches`` () = 118 | // 119 | // let testDictionary = EmbeddedHead.Dictionary data 120 | // 121 | // for KeyValue (k, expectedValue) in expectedValues do 122 | // let actualValue = testDictionary[k] 123 | // Assert.AreEqual (expectedValue, actualValue) 124 | // 125 | // 126 | // [] 127 | // let ``LinearProbing Dictionary matches`` () = 128 | // 129 | // let testDictionary = LinearProbing.Dictionary data 130 | // 131 | // for KeyValue (k, expectedValue) in expectedValues do 132 | // let actualValue = testDictionary[k] 133 | // Assert.AreEqual (expectedValue, actualValue) 134 | // 135 | // 136 | // [] 137 | // let ``RobinHood Dictionary matches`` () = 138 | // 139 | // let testDictionary = RobinHood.Dictionary data 140 | // 141 | // for KeyValue (k, expectedValue) in expectedValues do 142 | // let actualValue = testDictionary[k] 143 | // Assert.AreEqual (expectedValue, actualValue) 144 | // 145 | // 146 | // [] 147 | // let ``RobinHoodEviction Dictionary matches`` () = 148 | // 149 | // let testDictionary = RobinHoodEviction.Dictionary data 150 | // 151 | // for KeyValue (k, expectedValue) in expectedValues do 152 | // let actualValue = testDictionary[k] 153 | // Assert.AreEqual (expectedValue, actualValue) 154 | // 155 | // 156 | // [] 157 | // let ``ByteList Dictionary matches`` () = 158 | // 159 | // let testDictionary = ByteList.Dictionary data 160 | // 161 | // for KeyValue (k, expectedValue) in expectedValues do 162 | // let actualValue = testDictionary[k] 163 | // Assert.AreEqual (expectedValue, actualValue) 164 | // 165 | // 166 | // [] 167 | // let ``ByteList RobinHood Dictionary matches`` () = 168 | // 169 | // let testDictionary = ByteListRobinHood.Dictionary data 170 | // 171 | // for KeyValue (k, expectedValue) in expectedValues do 172 | // let actualValue = testDictionary[k] 173 | // Assert.AreEqual (expectedValue, actualValue) 174 | // 175 | // 176 | // [] 177 | // let ``ByteList RobinHood Inline Dictionary matches`` () = 178 | // 179 | // let testDictionary = ByteListRobinHoodInline.Dictionary data 180 | // 181 | // for KeyValue (k, expectedValue) in expectedValues do 182 | // let actualValue = testDictionary[k] 183 | // Assert.AreEqual (expectedValue, actualValue) 184 | // 185 | 186 | [] 187 | let ``SOA Dictionary matches`` () = 188 | 189 | (intDataSets, intKeySets) 190 | ||> Array.iter2 (fun data keys -> 191 | (data, keys) 192 | ||> Array.iter2 (fun data keys -> 193 | let testDictionary = SOA.StaticDict.create data 194 | let expectedValues = dict data 195 | 196 | for k in keys do 197 | let actualValue = testDictionary[k] 198 | let expectedValue = expectedValues[k] 199 | Assert.AreEqual (expectedValue, actualValue) 200 | ) 201 | ) 202 | 203 | (strDataSets, strKeySets) 204 | ||> Array.iter2 (fun data keys -> 205 | (data, keys) 206 | ||> Array.iter2 (fun data keys -> 207 | let testDictionary = SOA.StaticDict.create data 208 | let expectedValues = dict data 209 | 210 | for k in keys do 211 | let actualValue = testDictionary[k] 212 | let expectedValue = expectedValues[k] 213 | Assert.AreEqual (expectedValue, actualValue) 214 | ) 215 | ) 216 | 217 | [] 218 | let ``Frozen Dictionary matches`` () = 219 | 220 | // (intDataSets, intKeySets) 221 | // ||> Array.iter2 (fun data keys -> 222 | // (data, keys) 223 | // ||> Array.iter2 (fun data keys -> 224 | // let testDictionary = 225 | // data 226 | // |> Array.map KeyValuePair 227 | // |> Dictionary 228 | // |> fun x -> FrozenDictionary.ToFrozenDictionary (x) 229 | // let expectedValues = dict data 230 | // 231 | // for k in keys do 232 | // let actualValue = testDictionary[k] 233 | // let expectedValue = expectedValues[k] 234 | // Assert.AreEqual (expectedValue, actualValue) 235 | // ) 236 | // ) 237 | 238 | (strDataSets, strKeySets) 239 | ||> Array.iter2 (fun data keys -> 240 | (data, keys) 241 | ||> Array.iter2 (fun data keys -> 242 | let testDictionary = 243 | data 244 | |> Array.map KeyValuePair 245 | |> Dictionary 246 | |> fun x -> FrozenDictionary.ToFrozenDictionary (x) 247 | let expectedValues = dict data 248 | 249 | for k in keys do 250 | if testDictionary.Count = 100 then 251 | () 252 | let actualValue = testDictionary[k] 253 | let expectedValue = expectedValues[k] 254 | Assert.AreEqual (expectedValue, actualValue) 255 | ) 256 | ) 257 | 258 | 259 | // [] 260 | // let ``ByteList RobinHood Vec128 Dictionary matches`` () = 261 | // 262 | // let testDictionary = ByteListRobinHoodVec128.Dictionary data 263 | // 264 | // for KeyValue (k, expectedValue) in expectedValues do 265 | // let actualValue = testDictionary[k] 266 | // Assert.AreEqual (expectedValue, actualValue) 267 | // 268 | // 269 | // [] 270 | // let ``FastByte Dictionary matches`` () = 271 | // 272 | // let testDictionary = CStyle.Dictionary data 273 | // 274 | // for KeyValue (k, expectedValue) in expectedValues do 275 | // let actualValue = testDictionary[k] 276 | // Assert.AreEqual (expectedValue, actualValue) 277 | -------------------------------------------------------------------------------- /FastDictionaryTest.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FastDictionaryTest", "FastDictionaryTest\FastDictionaryTest.fsproj", "{84C2E8E5-9DC8-4D9C-BF40-8B0F1D86F297}" 4 | EndProject 5 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FastDictionaryTest.Benchmark", "FastDictionaryTest.Benchmark\FastDictionaryTest.Benchmark.fsproj", "{66453787-A19B-41C1-AD6F-9F614D52A921}" 6 | EndProject 7 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FastDictionaryTest.Tests", "FastDictionaryTest.Tests\FastDictionaryTest.Tests.fsproj", "{CF30C2CB-F1E5-4EB5-99DE-739EC86BBDED}" 8 | EndProject 9 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FastDictionaryTest.Scratchpad", "FastDictionaryTest.Scratchpad\FastDictionaryTest.Scratchpad.fsproj", "{B6FB0028-6EA5-4773-A1E5-3AC1868FDFE2}" 10 | EndProject 11 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FastDictionary.Profile", "FastDictionary.Profile\FastDictionary.Profile.fsproj", "{0463C464-AC24-4184-BB99-FD0CEA41FDAC}" 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {84C2E8E5-9DC8-4D9C-BF40-8B0F1D86F297}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {84C2E8E5-9DC8-4D9C-BF40-8B0F1D86F297}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {84C2E8E5-9DC8-4D9C-BF40-8B0F1D86F297}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {84C2E8E5-9DC8-4D9C-BF40-8B0F1D86F297}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {66453787-A19B-41C1-AD6F-9F614D52A921}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {66453787-A19B-41C1-AD6F-9F614D52A921}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {66453787-A19B-41C1-AD6F-9F614D52A921}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {66453787-A19B-41C1-AD6F-9F614D52A921}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {CF30C2CB-F1E5-4EB5-99DE-739EC86BBDED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {CF30C2CB-F1E5-4EB5-99DE-739EC86BBDED}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {CF30C2CB-F1E5-4EB5-99DE-739EC86BBDED}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {CF30C2CB-F1E5-4EB5-99DE-739EC86BBDED}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {B6FB0028-6EA5-4773-A1E5-3AC1868FDFE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {B6FB0028-6EA5-4773-A1E5-3AC1868FDFE2}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {B6FB0028-6EA5-4773-A1E5-3AC1868FDFE2}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {B6FB0028-6EA5-4773-A1E5-3AC1868FDFE2}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {0463C464-AC24-4184-BB99-FD0CEA41FDAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {0463C464-AC24-4184-BB99-FD0CEA41FDAC}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {0463C464-AC24-4184-BB99-FD0CEA41FDAC}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {0463C464-AC24-4184-BB99-FD0CEA41FDAC}.Release|Any CPU.Build.0 = Release|Any CPU 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /FastDictionaryTest/CacheEquality.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.CacheEquality 2 | 3 | open System 4 | open System.Numerics 5 | open Microsoft.FSharp.NativeInterop 6 | open System.Collections.Generic 7 | 8 | #nowarn "9" "42" "51" 9 | 10 | module private Helpers = 11 | 12 | let inline retype<'T,'U> (x: 'T) : 'U = (# "" x: 'U #) 13 | 14 | type Entry<'Key, 'Value> = 15 | { 16 | HashCode: int 17 | Key: 'Key 18 | Value: 'Value 19 | } 20 | 21 | let stringComparer = 22 | { new IEqualityComparer with 23 | member _.Equals (a: string, b: string) = 24 | String.Equals (a, b) 25 | 26 | member _.GetHashCode (a: string) = 27 | let charSpan = MemoryExtensions.AsSpan a 28 | let mutable hash1 = (5381u <<< 16) + 5381u 29 | let mutable hash2 = hash1 30 | let mutable length = a.Length 31 | let mutable ptr : nativeptr = 32 | &&charSpan.GetPinnableReference() 33 | |> retype 34 | while length > 2 do 35 | length <- length - 4 36 | hash1 <- (BitOperations.RotateLeft (hash1, 5) + hash1) ^^^ (NativePtr.get ptr 0) 37 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ (NativePtr.get ptr 1) 38 | ptr <- NativePtr.add ptr 2 39 | 40 | if length > 0 then 41 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ (NativePtr.get ptr 0) 42 | 43 | int (hash1 + (hash2 * 1566083941u)) 44 | } 45 | 46 | open Helpers 47 | 48 | 49 | type Dictionary<'Key, 'Value when 'Key : equality> (entries: seq<'Key * 'Value>) = 50 | // If the type of 'Key is a ref type, we will want to cache the EqualityComparer 51 | let refComparer = 52 | if typeof<'Key>.IsValueType then 53 | Unchecked.defaultof<_> 54 | elif typeof<'Key> = typeof then 55 | stringComparer :?> IEqualityComparer<'Key> 56 | else 57 | EqualityComparer<'Key>.Default :> IEqualityComparer<'Key> 58 | 59 | // Track the number of items in Dictionary for resize 60 | let mutable count = 0 61 | // Create the Buckets with some initial capacity 62 | let mutable buckets : list>[] = Array.create 4 [] 63 | // BitShift necessary for mapping HashCode to SlotIdx using Fibonacci Hashing 64 | let mutable bucketBitShift = 32 - (System.Numerics.BitOperations.TrailingZeroCount buckets.Length) 65 | 66 | // This relies on the size of buckets being a power of 2 67 | let computeBucketIndex (hashCode: int) = 68 | let hashProduct = (uint hashCode) * 2654435769u 69 | int (hashProduct >>> bucketBitShift) 70 | 71 | 72 | let addEntry (key: 'Key) (value: 'Value) = 73 | 74 | if typeof<'Key>.IsValueType then 75 | let hashCode = EqualityComparer.Default.GetHashCode key 76 | 77 | let rec loop (acc: Entry<_,_> list) (remaining: Entry<_,_> list) = 78 | match remaining with 79 | | [] -> 80 | let newEntry = { HashCode = hashCode; Key = key; Value = value } 81 | // Increment the count since we have added an entry 82 | count <- count + 1 83 | newEntry :: acc 84 | 85 | | head::tail -> 86 | if head.HashCode = hashCode && EqualityComparer<'Key>.Default.Equals (head.Key, key) then 87 | // Do not increment the count in this case because we are just overwriting an 88 | // exising value 89 | let updatedEntry = { head with Value = value } 90 | (updatedEntry::acc) @ tail 91 | else 92 | loop (head::acc) tail 93 | 94 | let bucketIdx = computeBucketIndex hashCode 95 | let bucket = buckets[bucketIdx] 96 | let updatedBucket = loop [] bucket 97 | buckets[bucketIdx] <- updatedBucket 98 | 99 | else 100 | let hashCode = refComparer.GetHashCode key 101 | 102 | let rec loop (acc: Entry<_,_> list) (remaining: Entry<_,_> list) = 103 | match remaining with 104 | | [] -> 105 | let newEntry = { HashCode = hashCode; Key = key; Value = value } 106 | // Increment the count since we have added an entry 107 | count <- count + 1 108 | newEntry :: acc 109 | 110 | | head::tail -> 111 | if head.HashCode = hashCode && refComparer.Equals (head.Key, key) then 112 | // Do not increment the count in this case because we are just overwriting an 113 | // exising value 114 | let updatedEntry = { head with Value = value } 115 | (updatedEntry::acc) @ tail 116 | else 117 | loop (head::acc) tail 118 | 119 | let bucketIdx = computeBucketIndex hashCode 120 | let bucket = buckets[bucketIdx] 121 | let updatedBucket = loop [] bucket 122 | buckets[bucketIdx] <- updatedBucket 123 | 124 | 125 | let getValue (key: 'Key) = 126 | 127 | if typeof<'Key>.IsValueType then 128 | let hashCode = EqualityComparer.Default.GetHashCode key 129 | 130 | let rec loop (entry: Entry<_,_> list) = 131 | match entry with 132 | | [] -> 133 | raise (KeyNotFoundException()) 134 | | head::tail -> 135 | if head.HashCode = hashCode && EqualityComparer.Default.Equals (head.Key, key) then 136 | head.Value 137 | else 138 | loop tail 139 | 140 | let bucketIdx = computeBucketIndex hashCode 141 | let bucket = buckets[bucketIdx] 142 | loop bucket 143 | 144 | else 145 | let hashCode = refComparer.GetHashCode key 146 | 147 | let rec loop (entry: Entry<_,_> list) = 148 | match entry with 149 | | [] -> 150 | raise (KeyNotFoundException()) 151 | | head::tail -> 152 | if head.HashCode = hashCode && refComparer.Equals (head.Key, key) then 153 | head.Value 154 | else 155 | loop tail 156 | 157 | let bucketIdx = computeBucketIndex hashCode 158 | let bucket = buckets[bucketIdx] 159 | loop bucket 160 | 161 | 162 | let resize () = 163 | // Only resize when the fill is > 75% 164 | if count > (buckets.Length >>> 2) * 3 then 165 | let oldBuckets = buckets 166 | 167 | // Increase the size of the backing store 168 | buckets <- Array.create (buckets.Length <<< 1) [] 169 | bucketBitShift <- 32 - (BitOperations.TrailingZeroCount buckets.Length) 170 | count <- 0 171 | 172 | for bucket in oldBuckets do 173 | for entry in bucket do 174 | addEntry entry.Key entry.Value 175 | 176 | do 177 | for k, v in entries do 178 | addEntry k v 179 | resize() 180 | 181 | new () = Dictionary([]) 182 | 183 | member d.Item 184 | with get (key: 'Key) = getValue key 185 | -------------------------------------------------------------------------------- /FastDictionaryTest/EmbeddedHead.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.EmbeddedHead 2 | 3 | open System 4 | open System.Numerics 5 | open Microsoft.FSharp.NativeInterop 6 | open System.Collections.Generic 7 | 8 | #nowarn "9" "42" "51" 9 | 10 | module private Helpers = 11 | 12 | let inline retype<'T,'U> (x: 'T) : 'U = (# "" x: 'U #) 13 | 14 | [] 15 | let POSITIVE_INT_MASK = 0x7FFF_FFFF 16 | 17 | [] 18 | module HashCode = 19 | let empty = -1 20 | 21 | type RefEntry<'Key, 'Value>(hashCode: int, key: 'Key, value: 'Value) = 22 | member val HashCode = hashCode with get 23 | member val Key = key with get 24 | member val Value = value with get, set 25 | [] val mutable Tail : RefEntry<'Key, 'Value> 26 | 27 | 28 | [] 29 | type StructEntry<'Key, 'Value> = 30 | { 31 | mutable HashCode : int 32 | mutable Key : 'Key 33 | mutable Value : 'Value 34 | mutable Tail : RefEntry<'Key, 'Value> 35 | } 36 | member e.IsEmpty = e.HashCode = HashCode.empty 37 | 38 | module StructEntry = 39 | 40 | let empty<'Key, 'Value> = 41 | { 42 | HashCode = HashCode.empty 43 | Key = Unchecked.defaultof<'Key> 44 | Value = Unchecked.defaultof<'Value> 45 | Tail = Unchecked.defaultof> 46 | } 47 | 48 | let stringComparer = 49 | { new IEqualityComparer with 50 | member _.Equals (a: string, b: string) = 51 | String.Equals (a, b) 52 | 53 | member _.GetHashCode (a: string) = 54 | let charSpan = MemoryExtensions.AsSpan a 55 | let mutable hash1 = (5381u <<< 16) + 5381u 56 | let mutable hash2 = hash1 57 | let mutable length = a.Length 58 | let mutable ptr : nativeptr = 59 | &&charSpan.GetPinnableReference() 60 | |> retype 61 | while length > 2 do 62 | length <- length - 4 63 | hash1 <- (BitOperations.RotateLeft (hash1, 5) + hash1) ^^^ (NativePtr.get ptr 0) 64 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ (NativePtr.get ptr 1) 65 | ptr <- NativePtr.add ptr 2 66 | 67 | if length > 0 then 68 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ (NativePtr.get ptr 0) 69 | 70 | int (hash1 + (hash2 * 1566083941u)) 71 | } 72 | 73 | open Helpers 74 | 75 | 76 | type Dictionary<'Key, 'Value when 'Key : equality> (entries: seq<'Key * 'Value>) = 77 | // If the type of 'Key is a ref type, we will want to cache the EqualityComparer 78 | let refComparer = 79 | if typeof<'Key>.IsValueType then 80 | Unchecked.defaultof<_> 81 | elif typeof<'Key> = typeof then 82 | stringComparer :?> IEqualityComparer<'Key> 83 | else 84 | EqualityComparer<'Key>.Default :> IEqualityComparer<'Key> 85 | 86 | // Track the number of items in Dictionary for resize 87 | let mutable count = 0 88 | // Create the Buckets with some initial capacity 89 | let mutable buckets : StructEntry<'Key, 'Value>[] = Array.create 4 StructEntry.empty 90 | // BitShift necessary for mapping HashCode to SlotIdx using Fibonacci Hashing 91 | let mutable bucketBitShift = 32 - (BitOperations.TrailingZeroCount buckets.Length) 92 | 93 | // This relies on the size of buckets being a power of 2 94 | let computeBucketIndex (hashCode: int) = 95 | let hashProduct = (uint hashCode) * 2654435769u 96 | int (hashProduct >>> bucketBitShift) 97 | 98 | 99 | let addStructEntry (key: 'Key) (value: 'Value) = 100 | 101 | let rec refNodeLoop (hashCode: int) (rEntry: RefEntry<_,_>) = 102 | if rEntry.HashCode = hashCode && 103 | EqualityComparer.Default.Equals (rEntry.Key, key) then 104 | 105 | rEntry.Value <- value 106 | 107 | elif obj.ReferenceEquals (rEntry.Tail, null) then 108 | rEntry.Tail <- RefEntry (hashCode, key, value) 109 | count <- count + 1 110 | 111 | else 112 | refNodeLoop hashCode rEntry.Tail 113 | 114 | let hashCode = EqualityComparer.Default.GetHashCode key &&& POSITIVE_INT_MASK 115 | let bucketIdx = computeBucketIndex hashCode 116 | let sEntry = &buckets[bucketIdx] 117 | 118 | if sEntry.HashCode = HashCode.empty then 119 | sEntry.HashCode <- hashCode 120 | sEntry.Key <- key 121 | sEntry.Value <- value 122 | count <- count + 1 123 | 124 | elif sEntry.HashCode = hashCode && 125 | EqualityComparer.Default.Equals (sEntry.Key, key) then 126 | 127 | sEntry.Value <- value 128 | 129 | elif obj.ReferenceEquals (sEntry.Tail, null) then 130 | sEntry.Tail <- RefEntry (hashCode, key, value) 131 | count <- count + 1 132 | else 133 | refNodeLoop hashCode sEntry.Tail 134 | 135 | 136 | let addRefEntry (key: 'Key) (value: 'Value) = 137 | 138 | let rec refNodeLoop (hashCode: int) (rEntry: RefEntry<_,_>) = 139 | if rEntry.HashCode = hashCode && 140 | refComparer.Equals (rEntry.Key, key) then 141 | 142 | rEntry.Value <- value 143 | 144 | elif obj.ReferenceEquals (rEntry.Tail, null) then 145 | rEntry.Tail <- RefEntry (hashCode, key, value) 146 | count <- count + 1 147 | 148 | else 149 | refNodeLoop hashCode rEntry.Tail 150 | 151 | let hashCode = refComparer.GetHashCode key &&& POSITIVE_INT_MASK 152 | let bucketIdx = computeBucketIndex hashCode 153 | let sEntry = &buckets[bucketIdx] 154 | 155 | if sEntry.HashCode = HashCode.empty then 156 | sEntry.HashCode <- hashCode 157 | sEntry.Key <- key 158 | sEntry.Value <- value 159 | count <- count + 1 160 | 161 | elif sEntry.HashCode = hashCode && 162 | refComparer.Equals (sEntry.Key, key) then 163 | 164 | sEntry.Value <- value 165 | 166 | elif obj.ReferenceEquals (sEntry.Tail, null) then 167 | sEntry.Tail <- RefEntry (hashCode, key, value) 168 | count <- count + 1 169 | 170 | else 171 | refNodeLoop hashCode sEntry.Tail 172 | 173 | 174 | let addEntry (key: 'Key) (value: 'Value) = 175 | 176 | if typeof<'Key>.IsValueType then 177 | addStructEntry key value 178 | 179 | else 180 | addRefEntry key value 181 | 182 | 183 | let getStructValue (key: 'Key) = 184 | 185 | let rec loop (hashCode: int) (rEntry: RefEntry<'Key, 'Value>) = 186 | if rEntry.HashCode = hashCode && 187 | EqualityComparer.Default.Equals (rEntry.Key, key) then 188 | rEntry.Value 189 | 190 | else 191 | if obj.ReferenceEquals (rEntry.Tail, null) then 192 | raise (KeyNotFoundException()) 193 | else 194 | loop hashCode rEntry.Tail 195 | 196 | let hashCode = EqualityComparer.Default.GetHashCode key &&& POSITIVE_INT_MASK 197 | let bucketIdx = computeBucketIndex hashCode 198 | let sEntry = buckets[bucketIdx] 199 | 200 | if sEntry.HashCode = hashCode && 201 | EqualityComparer.Default.Equals (sEntry.Key, key) then 202 | sEntry.Value 203 | 204 | elif (obj.ReferenceEquals (sEntry.Tail, null)) then 205 | raise (KeyNotFoundException()) 206 | 207 | else 208 | loop hashCode sEntry.Tail 209 | 210 | 211 | let getRefValue (key: 'Key) = 212 | 213 | let rec refLoop (hashCode: int) (rEntry: RefEntry<'Key, 'Value>) = 214 | if rEntry.HashCode = hashCode && 215 | refComparer.Equals (rEntry.Key, key) then 216 | rEntry.Value 217 | else 218 | if obj.ReferenceEquals (rEntry.Tail, null) then 219 | raise (KeyNotFoundException()) 220 | else 221 | refLoop hashCode rEntry.Tail 222 | 223 | let hashCode = (refComparer.GetHashCode key) &&& POSITIVE_INT_MASK 224 | let bucketIdx = computeBucketIndex hashCode 225 | let sEntry = buckets[bucketIdx] 226 | 227 | if sEntry.HashCode = hashCode && 228 | refComparer.Equals (sEntry.Key, key) then 229 | sEntry.Value 230 | 231 | elif (obj.ReferenceEquals (sEntry.Tail, null)) then 232 | raise (KeyNotFoundException()) 233 | 234 | else 235 | refLoop hashCode sEntry.Tail 236 | 237 | 238 | let resize () = 239 | // Resize if our fill is >75% 240 | if count > (buckets.Length >>> 2) * 3 then 241 | let oldBuckets = buckets 242 | 243 | // Increase the size of the backing store 244 | buckets <- Array.create (buckets.Length <<< 1) StructEntry.empty 245 | bucketBitShift <- 32 - (BitOperations.TrailingZeroCount buckets.Length) 246 | count <- 0 247 | 248 | let rec refLoop (rEntry: RefEntry<'Key, 'Value>) = 249 | addEntry rEntry.Key rEntry.Value 250 | if not (obj.ReferenceEquals (rEntry.Tail, null)) then 251 | refLoop rEntry.Tail 252 | 253 | for sEntry in oldBuckets do 254 | if not (sEntry.HashCode = HashCode.empty) then 255 | addEntry sEntry.Key sEntry.Value 256 | if not (obj.ReferenceEquals (sEntry.Tail, null)) then 257 | refLoop sEntry.Tail 258 | 259 | 260 | do 261 | for key, value in entries do 262 | addEntry key value 263 | resize() 264 | 265 | new () = Dictionary<'Key, 'Value>([]) 266 | 267 | member d.Item 268 | with get (key: 'Key) = 269 | if typeof<'Key>.IsValueType then 270 | getStructValue key 271 | else 272 | getRefValue key 273 | -------------------------------------------------------------------------------- /FastDictionaryTest/FastDictionaryTest.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | true 6 | x64 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /FastDictionaryTest/FastTypeBranch.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.FastTypeBranch 2 | 3 | open System 4 | open System.Numerics 5 | open Microsoft.FSharp.NativeInterop 6 | open System.Collections.Generic 7 | 8 | #nowarn "9" "42" "51" 9 | 10 | module private Helpers = 11 | 12 | let inline retype<'T,'U> (x: 'T) : 'U = (# "" x: 'U #) 13 | 14 | type Entry<'Key, 'Value> = 15 | { 16 | HashCode: int 17 | Key: 'Key 18 | Value: 'Value 19 | } 20 | 21 | let stringComparer = 22 | { new IEqualityComparer with 23 | member _.Equals (a: string, b: string) = 24 | String.Equals (a, b) 25 | 26 | member _.GetHashCode (a: string) = 27 | let charSpan = MemoryExtensions.AsSpan a 28 | let mutable hash1 = (5381u <<< 16) + 5381u 29 | let mutable hash2 = hash1 30 | let mutable length = a.Length 31 | let mutable ptr : nativeptr = 32 | &&charSpan.GetPinnableReference() 33 | |> retype 34 | while length > 2 do 35 | length <- length - 4 36 | hash1 <- (BitOperations.RotateLeft (hash1, 5) + hash1) ^^^ (NativePtr.get ptr 0) 37 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ (NativePtr.get ptr 1) 38 | ptr <- NativePtr.add ptr 2 39 | 40 | if length > 0 then 41 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ (NativePtr.get ptr 0) 42 | 43 | int (hash1 + (hash2 * 1566083941u)) 44 | } 45 | 46 | open Helpers 47 | 48 | 49 | type Dictionary<'Key, 'Value when 'Key : equality> (entries: seq<'Key * 'Value>) = 50 | // If the type of 'Key is a ref type, we will want to cache the EqualityComparer 51 | let refComparer = 52 | if typeof<'Key>.IsValueType then 53 | Unchecked.defaultof<_> 54 | elif typeof<'Key> = typeof then 55 | stringComparer :?> IEqualityComparer<'Key> 56 | else 57 | EqualityComparer<'Key>.Default :> IEqualityComparer<'Key> 58 | 59 | // Track the number of items in Dictionary for resize 60 | let mutable count = 0 61 | // Create the Buckets with some initial capacity 62 | let mutable buckets : list>[] = Array.create 4 [] 63 | // BitShift necessary for mapping HashCode to SlotIdx using Fibonacci Hashing 64 | let mutable bucketBitShift = 32 - (BitOperations.TrailingZeroCount buckets.Length) 65 | 66 | // This relies on the size of buckets being a power of 2 67 | let computeBucketIndex (hashCode: int) = 68 | let hashProduct = (uint hashCode) * 2654435769u 69 | int (hashProduct >>> bucketBitShift) 70 | 71 | 72 | let addStructEntry (key: 'Key) (value: 'Value) = 73 | 74 | let rec loop (hashCode: int) (acc: Entry<_,_> list) (remaining: Entry<_,_> list) = 75 | match remaining with 76 | | [] -> 77 | let newEntry = { HashCode = hashCode; Key = key; Value = value } 78 | // Increment the count since we have added an entry 79 | count <- count + 1 80 | newEntry :: acc 81 | 82 | | head::tail -> 83 | if head.HashCode = hashCode && EqualityComparer<'Key>.Default.Equals (head.Key, key) then 84 | // Do not increment the count in this case because we are just overwriting an 85 | // exising value 86 | let updatedEntry = { head with Value = value } 87 | (updatedEntry::acc) @ tail 88 | else 89 | loop hashCode (head::acc) tail 90 | 91 | let hashCode = EqualityComparer.Default.GetHashCode key 92 | let bucketIdx = computeBucketIndex hashCode 93 | let bucket = buckets[bucketIdx] 94 | let updatedBucket = loop hashCode [] bucket 95 | buckets[bucketIdx] <- updatedBucket 96 | 97 | 98 | let addRefEntry (key: 'Key) (value: 'Value) = 99 | 100 | let rec loop (hashCode: int) (acc: Entry<_,_> list) (remaining: Entry<_,_> list) = 101 | match remaining with 102 | | [] -> 103 | let newEntry = { HashCode = hashCode; Key = key; Value = value } 104 | // Increment the count since we have added an entry 105 | count <- count + 1 106 | newEntry :: acc 107 | 108 | | head::tail -> 109 | if head.HashCode = hashCode && refComparer.Equals (head.Key, key) then 110 | // Do not increment the count in this case because we are just overwriting an 111 | // exising value 112 | let updatedEntry = { head with Value = value } 113 | (updatedEntry::acc) @ tail 114 | else 115 | loop hashCode (head::acc) tail 116 | 117 | let hashCode = refComparer.GetHashCode key 118 | let bucketIdx = computeBucketIndex hashCode 119 | let bucket = buckets[bucketIdx] 120 | let updatedBucket = loop hashCode [] bucket 121 | buckets[bucketIdx] <- updatedBucket 122 | 123 | 124 | let addEntry (key: 'Key) (value: 'Value) = 125 | 126 | if typeof<'Key>.IsValueType then 127 | addStructEntry key value 128 | else 129 | addRefEntry key value 130 | 131 | 132 | let getStructValue (key: 'Key) = 133 | let hashCode = EqualityComparer.Default.GetHashCode key 134 | 135 | let rec loop (entry: Entry<_,_> list) = 136 | match entry with 137 | | [] -> 138 | raise (KeyNotFoundException()) 139 | | head::tail -> 140 | if head.HashCode = hashCode && EqualityComparer.Default.Equals (head.Key, key) then 141 | head.Value 142 | else 143 | loop tail 144 | 145 | let bucketIdx = computeBucketIndex hashCode 146 | let bucket = buckets[bucketIdx] 147 | loop bucket 148 | 149 | 150 | let getRefValue (key: 'Key) = 151 | let hashCode = refComparer.GetHashCode key 152 | 153 | let rec loop (entry: Entry<_,_> list) = 154 | match entry with 155 | | [] -> 156 | raise (KeyNotFoundException()) 157 | | head::tail -> 158 | if head.HashCode = hashCode && refComparer.Equals (head.Key, key) then 159 | head.Value 160 | else 161 | loop tail 162 | 163 | let bucketIdx = computeBucketIndex hashCode 164 | let bucket = buckets[bucketIdx] 165 | loop bucket 166 | 167 | 168 | let resize () = 169 | // Only resize when the fill is > 75% 170 | if count > (buckets.Length >>> 2) * 3 then 171 | let oldBuckets = buckets 172 | 173 | // Increase the size of the backing store 174 | buckets <- Array.create (buckets.Length <<< 1) [] 175 | bucketBitShift <- 32 - (BitOperations.TrailingZeroCount buckets.Length) 176 | count <- 0 177 | 178 | for bucket in oldBuckets do 179 | for entry in bucket do 180 | addEntry entry.Key entry.Value 181 | 182 | do 183 | for k, v in entries do 184 | addEntry k v 185 | resize() 186 | 187 | new () = Dictionary([]) 188 | 189 | member d.Item 190 | with get (key: 'Key) = 191 | if typeof<'Key>.IsValueType then 192 | getStructValue key 193 | else 194 | getRefValue key 195 | -------------------------------------------------------------------------------- /FastDictionaryTest/FibonacciHashing.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.FibonacciHashing 2 | 3 | open System.Collections.Generic 4 | open System.Numerics 5 | 6 | module private Helpers = 7 | 8 | type Entry<'Key, 'Value> = 9 | { 10 | Key: 'Key 11 | Value: 'Value 12 | } 13 | 14 | open Helpers 15 | 16 | 17 | type Dictionary<'Key, 'Value when 'Key : equality> (entries: seq<'Key * 'Value>) = 18 | // Track the number of items in Dictionary for resize 19 | let mutable count = 0 20 | // Create the Buckets with some initial capacity 21 | let mutable buckets : list>[] = Array.create 4 [] 22 | // BitShift necessary for mapping HashCode to SlotIdx using Fibonacci Hashing 23 | let mutable slotBitShift = 32 - (BitOperations.TrailingZeroCount buckets.Length) 24 | 25 | // This relies on the size of buckets being a power of 2 26 | let computeBucketIndex (key: 'Key) = 27 | let h = (EqualityComparer.Default.GetHashCode key) 28 | let hashProduct = (uint h) * 2654435769u 29 | int (hashProduct >>> slotBitShift) 30 | 31 | 32 | let addEntry (key: 'Key) (value: 'Value) = 33 | 34 | let rec loop (acc: Entry<_,_> list) (remaining: Entry<_,_> list) = 35 | match remaining with 36 | | [] -> 37 | let newEntry = { Key = key; Value = value } 38 | // Increment the count since we have added an entry 39 | count <- count + 1 40 | newEntry :: acc 41 | | head::tail -> 42 | if EqualityComparer<'Key>.Default.Equals (head.Key, key) then 43 | // Do not increment the count in this case because we are just overwriting an 44 | // exising value 45 | let updatedEntry = { head with Value = value } 46 | (updatedEntry::acc) @ tail 47 | else 48 | loop (head::acc) tail 49 | 50 | let bucketIdx = computeBucketIndex key 51 | let bucket = buckets[bucketIdx] 52 | let updatedBucket = loop [] bucket 53 | buckets[bucketIdx] <- updatedBucket 54 | 55 | 56 | let getValue (key: 'Key) = 57 | 58 | let rec loop (entry: Entry<_,_> list) = 59 | match entry with 60 | | [] -> 61 | raise (KeyNotFoundException()) 62 | | head::tail -> 63 | if EqualityComparer.Default.Equals (head.Key, key) then 64 | head.Value 65 | else 66 | loop tail 67 | 68 | let bucketIdx = computeBucketIndex key 69 | loop buckets[bucketIdx] 70 | 71 | 72 | let resize () = 73 | // Only resize when the fill is > 75% 74 | if count > (buckets.Length >>> 2) * 3 then 75 | let oldBuckets = buckets 76 | 77 | // Increase the size of the backing store 78 | buckets <- Array.create (buckets.Length <<< 1) [] 79 | slotBitShift <- 32 - (System.Numerics.BitOperations.TrailingZeroCount buckets.Length) 80 | count <- 0 81 | 82 | for bucket in oldBuckets do 83 | for entry in bucket do 84 | addEntry entry.Key entry.Value 85 | 86 | do 87 | for k, v in entries do 88 | addEntry k v 89 | resize() 90 | 91 | new () = Dictionary([]) 92 | 93 | member d.Item 94 | with get (key: 'Key) = getValue key 95 | -------------------------------------------------------------------------------- /FastDictionaryTest/LinearProbing.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.LinearProbing 2 | 3 | open System 4 | open System.Numerics 5 | open Microsoft.FSharp.NativeInterop 6 | open System.Collections.Generic 7 | 8 | #nowarn "9" "42" "51" 9 | 10 | 11 | module private Helpers = 12 | 13 | let inline retype<'T,'U> (x: 'T) : 'U = (# "" x: 'U #) 14 | 15 | [] 16 | let POSITIVE_INT_MASK = 0x7FFF_FFFF 17 | 18 | [] 19 | module HashCode = 20 | let empty = -2 21 | let tombstone = -1 22 | 23 | [] 24 | type Bucket<'Key, 'Value> = 25 | { 26 | mutable HashCode : int 27 | mutable Key : 'Key 28 | mutable Value : 'Value 29 | } 30 | member s.IsTombstone = s.HashCode = HashCode.tombstone 31 | member s.IsEmpty = s.HashCode = HashCode.empty 32 | member s.IsEntry = s.HashCode >= 0 33 | member s.IsOccupied = s.HashCode >= -1 34 | member s.IsAvailable = s.HashCode < 0 35 | 36 | module Bucket = 37 | 38 | let empty<'Key, 'Value> = 39 | { 40 | HashCode = -1 41 | Key = Unchecked.defaultof<'Key> 42 | Value = Unchecked.defaultof<'Value> 43 | } 44 | 45 | let stringComparer = 46 | { new IEqualityComparer with 47 | member _.Equals (a: string, b: string) = 48 | String.Equals (a, b) 49 | 50 | member _.GetHashCode (a: string) = 51 | let charSpan = MemoryExtensions.AsSpan a 52 | let mutable hash1 = (5381u <<< 16) + 5381u 53 | let mutable hash2 = hash1 54 | let mutable length = a.Length 55 | let mutable ptr : nativeptr = 56 | &&charSpan.GetPinnableReference() 57 | |> retype 58 | while length > 2 do 59 | length <- length - 4 60 | hash1 <- (BitOperations.RotateLeft (hash1, 5) + hash1) ^^^ (NativePtr.get ptr 0) 61 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ (NativePtr.get ptr 1) 62 | ptr <- NativePtr.add ptr 2 63 | 64 | if length > 0 then 65 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ (NativePtr.get ptr 0) 66 | 67 | int (hash1 + (hash2 * 1566083941u)) 68 | } 69 | 70 | open Helpers 71 | 72 | 73 | type Dictionary<'Key, 'Value when 'Key : equality> (entries: seq<'Key * 'Value>) = 74 | // If the type of 'Key is a ref type, we will want to cache the EqualityComparer 75 | let refComparer = 76 | if typeof<'Key>.IsValueType then 77 | Unchecked.defaultof<_> 78 | elif typeof<'Key> = typeof then 79 | stringComparer :?> IEqualityComparer<'Key> 80 | else 81 | EqualityComparer<'Key>.Default :> IEqualityComparer<'Key> 82 | 83 | // Track the number of items in Dictionary for resize 84 | let mutable count = 0 85 | // Create the Buckets with some initial capacity 86 | let mutable buckets : Bucket<'Key, 'Value>[] = Array.create 4 Bucket.empty 87 | // BitShift necessary for mapping HashCode to BucketIdx using Fibonacci Hashing 88 | let mutable bucketBitShift = 32 - (System.Numerics.BitOperations.TrailingZeroCount buckets.Length) 89 | 90 | let computeBucketIndex (hashCode: int) = 91 | let hashProduct = uint hashCode * 2654435769u 92 | int (hashProduct >>> bucketBitShift) 93 | 94 | 95 | let addStructEntry (key: 'Key) (value: 'Value) = 96 | 97 | let rec loop (hashCode: int) (bucketIdx: int) = 98 | if bucketIdx < buckets.Length then 99 | let bucket = &buckets[bucketIdx] 100 | // Check if bucket is Empty or a Tombstone 101 | if bucket.IsAvailable then 102 | bucket.HashCode <- hashCode 103 | bucket.Key <- key 104 | bucket.Value <- value 105 | count <- count + 1 106 | else 107 | // If we reach here, we know the bucket is occupied 108 | if hashCode = bucket.HashCode && 109 | EqualityComparer.Default.Equals (key, bucket.Key) then 110 | bucket.Value <- value 111 | else 112 | loop hashCode (bucketIdx + 1) 113 | else 114 | // Start over looking from the beginning of the buckets 115 | loop hashCode 0 116 | 117 | let hashCode = EqualityComparer.Default.GetHashCode key &&& POSITIVE_INT_MASK 118 | let bucketIdx = computeBucketIndex hashCode 119 | loop hashCode bucketIdx 120 | 121 | 122 | let addRefEntry (key: 'Key) (value: 'Value) = 123 | 124 | let rec loop (hashCode: int) (bucketIdx: int) = 125 | if bucketIdx < buckets.Length then 126 | let bucket = &buckets[bucketIdx] 127 | // Check if bucket is Empty or a Tombstone 128 | if bucket.IsAvailable then 129 | bucket.HashCode <- hashCode 130 | bucket.Key <- key 131 | bucket.Value <- value 132 | count <- count + 1 133 | else 134 | // If we reach here, we know the bucket is occupied 135 | if hashCode = bucket.HashCode && 136 | refComparer.Equals (key, bucket.Key) then 137 | bucket.Value <- value 138 | else 139 | loop hashCode (bucketIdx + 1) 140 | else 141 | // Start over looking from the beginning of the buckets 142 | loop hashCode 0 143 | 144 | 145 | let hashCode = refComparer.GetHashCode key &&& POSITIVE_INT_MASK 146 | let bucketIdx = computeBucketIndex hashCode 147 | loop hashCode bucketIdx 148 | 149 | 150 | let addEntry (key: 'Key) (value: 'Value) = 151 | 152 | if typeof<'Key>.IsValueType then 153 | addStructEntry key value 154 | else 155 | addRefEntry key value 156 | 157 | 158 | // Use the stored refComparer to reduce overhead in creating new instances 159 | let getStructValue (key: 'Key) = 160 | 161 | let rec loop hashCode (bucketIdx: int) = 162 | // Make sure that we have not gone past the end of the backing array. 163 | // If we have we will want to continue our loop from the beginning of the array. 164 | if bucketIdx < buckets.Length then 165 | 166 | // Check that our HashCodes match and the Keys are equivalent 167 | if hashCode = buckets[bucketIdx].HashCode && 168 | EqualityComparer.Default.Equals (key, buckets[bucketIdx].Key) then 169 | buckets[bucketIdx].Value 170 | 171 | // If the Bucket is occupied then we want to move to the next Entry 172 | elif buckets[bucketIdx].IsOccupied then 173 | loop hashCode (bucketIdx + 1) 174 | 175 | // If the Bucket is empty then we have failed to find the Key we 176 | // were searching for 177 | else 178 | raise (KeyNotFoundException()) 179 | 180 | else 181 | // Loop around to the begging of the array 182 | loop hashCode 0 183 | 184 | let hashCode = EqualityComparer.Default.GetHashCode key &&& POSITIVE_INT_MASK 185 | let bucketIdx = computeBucketIndex hashCode 186 | loop hashCode bucketIdx 187 | 188 | 189 | // Use the default GetHashCode to enable the inlining of code for the JIT 190 | let getRefValue (key: 'Key) = 191 | 192 | let rec loop hashCode (bucketIdx: int) = 193 | if bucketIdx < buckets.Length then 194 | if hashCode = buckets[bucketIdx].HashCode && 195 | refComparer.Equals (key, buckets[bucketIdx].Key) then 196 | buckets[bucketIdx].Value 197 | 198 | elif buckets[bucketIdx].IsOccupied then 199 | loop hashCode (bucketIdx + 1) 200 | 201 | else 202 | raise (KeyNotFoundException()) 203 | 204 | else 205 | // Loop around to the begging of the array 206 | loop hashCode 0 207 | 208 | let hashCode = refComparer.GetHashCode key &&& POSITIVE_INT_MASK 209 | let bucketIdx = computeBucketIndex hashCode 210 | loop hashCode bucketIdx 211 | 212 | 213 | let resize () = 214 | // Resize if our fill is >75% 215 | if count > (buckets.Length >>> 2) * 3 then 216 | // if count > buckets.Length - 2 then 217 | let oldBuckets = buckets 218 | 219 | // Increase the size of the backing store 220 | buckets <- Array.create (buckets.Length <<< 1) Bucket.empty 221 | bucketBitShift <- 32 - (BitOperations.TrailingZeroCount buckets.Length) 222 | count <- 0 223 | 224 | for bucket in oldBuckets do 225 | if bucket.IsEntry then 226 | addEntry bucket.Key bucket.Value 227 | 228 | do 229 | for k, v in entries do 230 | addEntry k v 231 | resize() 232 | 233 | new () = Dictionary<'Key, 'Value>([]) 234 | 235 | member d.Item 236 | with get (key: 'Key) = 237 | 238 | // Switch on the type to enable the JIT to eliminate unused code and inline method call 239 | if typeof<'Key>.IsValueType then 240 | getStructValue key 241 | 242 | else 243 | getRefValue key 244 | -------------------------------------------------------------------------------- /FastDictionaryTest/Naive.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FastDictionaryTest.Naive 3 | 4 | module private Helpers = 5 | 6 | type Entry<'Key, 'Value> = 7 | { 8 | Key: 'Key 9 | Value: 'Value 10 | } 11 | 12 | open Helpers 13 | 14 | 15 | type Dictionary<'Key, 'Value when 'Key : equality> (entries: seq<'Key * 'Value>) = 16 | // Track the number of items in Dictionary for resize 17 | let mutable count = 0 18 | // Create the Buckets with some initial capacity 19 | let mutable buckets : list>[] = Array.create 4 [] 20 | // Map a 'Key to the bucket we expect it to be in 21 | let computeBucketIndex (key: 'Key) = 22 | let h = (hash key) &&& 0x7FFF_FFFF 23 | let bucketIdx = h % buckets.Length 24 | bucketIdx 25 | 26 | 27 | let addEntry (key: 'Key) (value: 'Value) = 28 | 29 | let rec loop (acc: Entry<_,_> list) (remaining: Entry<_,_> list) = 30 | match remaining with 31 | | [] -> 32 | let newEntry = { Key = key; Value = value } 33 | count <- count + 1 34 | newEntry :: acc 35 | | head::tail -> 36 | if head.Key = key then 37 | let updatedEntry = { head with Value = value } 38 | (updatedEntry::acc) @ tail 39 | else 40 | loop (head::acc) tail 41 | 42 | let bucketIdx = computeBucketIndex key 43 | let bucket = buckets[bucketIdx] 44 | let updatedBucket = loop [] bucket 45 | buckets[bucketIdx] <- updatedBucket 46 | 47 | 48 | let getValue (key: 'Key) = 49 | let bucketIdx = computeBucketIndex key 50 | buckets[bucketIdx] 51 | |> List.find (fun entry -> entry.Key = key) 52 | |> fun entry -> entry.Value 53 | 54 | 55 | let resize () = 56 | // Only resize when the fill is > 75% 57 | if count > (buckets.Length >>> 2) * 3 then 58 | let oldBuckets = buckets 59 | 60 | // Increase the size of the backing store 61 | buckets <- Array.create (buckets.Length <<< 1) [] 62 | count <- 0 63 | 64 | for bucket in oldBuckets do 65 | for entry in bucket do 66 | addEntry entry.Key entry.Value 67 | 68 | do 69 | for key, value in entries do 70 | addEntry key value 71 | resize() 72 | 73 | new () = Dictionary([]) 74 | 75 | member d.Item 76 | with get (key: 'Key) = getValue key 77 | -------------------------------------------------------------------------------- /FastDictionaryTest/RobinHood.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.RobinHood 2 | 3 | open System 4 | open System.Numerics 5 | open Microsoft.FSharp.NativeInterop 6 | open System.Collections.Generic 7 | 8 | #nowarn "9" "42" "51" 9 | 10 | 11 | module private Helpers = 12 | 13 | let inline retype<'T,'U> (x: 'T) : 'U = (# "" x: 'U #) 14 | 15 | [] 16 | let POSITIVE_INT_MASK = 0x7FFF_FFFF 17 | 18 | [] 19 | module HashCode = 20 | let empty = -2 21 | let tombstone = -1 22 | 23 | [] 24 | type Bucket<'Key, 'Value> = 25 | { 26 | mutable HashCode : int 27 | mutable Offset : int 28 | mutable Key : 'Key 29 | mutable Value : 'Value 30 | } 31 | member s.IsTombstone = s.HashCode = HashCode.tombstone 32 | member s.IsEmpty = s.HashCode = HashCode.empty 33 | member s.IsEntry = s.HashCode >= 0 34 | member s.IsOccupied = s.HashCode >= -1 35 | member s.IsAvailable = s.HashCode < 0 36 | 37 | module Bucket = 38 | 39 | let empty<'Key, 'Value> = 40 | { 41 | HashCode = -1 42 | Offset = 0 43 | Key = Unchecked.defaultof<'Key> 44 | Value = Unchecked.defaultof<'Value> 45 | } 46 | 47 | let stringComparer = 48 | { new IEqualityComparer with 49 | member _.Equals (a: string, b: string) = 50 | String.Equals (a, b) 51 | 52 | member _.GetHashCode (a: string) = 53 | let charSpan = MemoryExtensions.AsSpan a 54 | let mutable hash1 = (5381u <<< 16) + 5381u 55 | let mutable hash2 = hash1 56 | let mutable length = a.Length 57 | let mutable ptr : nativeptr = 58 | &&charSpan.GetPinnableReference() 59 | |> retype 60 | while length > 2 do 61 | length <- length - 4 62 | hash1 <- (BitOperations.RotateLeft (hash1, 5) + hash1) ^^^ (NativePtr.get ptr 0) 63 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ (NativePtr.get ptr 1) 64 | ptr <- NativePtr.add ptr 2 65 | 66 | if length > 0 then 67 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ (NativePtr.get ptr 0) 68 | 69 | int (hash1 + (hash2 * 1566083941u)) 70 | } 71 | 72 | open Helpers 73 | 74 | 75 | type Dictionary<'Key, 'Value when 'Key : equality> (entries: seq<'Key * 'Value>) = 76 | // If the type of 'Key is a ref type, we will want to cache the EqualityComparer 77 | let refComparer = 78 | if typeof<'Key>.IsValueType then 79 | Unchecked.defaultof<_> 80 | elif typeof<'Key> = typeof then 81 | stringComparer :?> IEqualityComparer<'Key> 82 | else 83 | EqualityComparer<'Key>.Default :> IEqualityComparer<'Key> 84 | 85 | // Track the number of items in Dictionary for resize 86 | let mutable count = 0 87 | // Create the Buckets with some initial capacity 88 | let mutable buckets : Bucket<'Key, 'Value>[] = Array.create 4 Bucket.empty 89 | // BitShift necessary for mapping HashCode to BucketIdx using Fibonacci Hashing 90 | let mutable bucketBitShift = 32 - (System.Numerics.BitOperations.TrailingZeroCount buckets.Length) 91 | 92 | 93 | let computeBucketIndex (hashCode: int) = 94 | let hashProduct = uint hashCode * 2654435769u 95 | int (hashProduct >>> bucketBitShift) 96 | 97 | 98 | let rec addStructEntry (key: 'Key) (value: 'Value) = 99 | 100 | let rec loop hashCode (offset: int) (bucketIdx: int) = 101 | if bucketIdx < buckets.Length then 102 | let bucket = &buckets[bucketIdx] 103 | // Check if bucket is Empty or a Tombstone 104 | if bucket.IsAvailable then 105 | bucket.Offset <- offset 106 | bucket.HashCode <- hashCode 107 | bucket.Key <- key 108 | bucket.Value <- value 109 | count <- count + 1 110 | else 111 | // If we reach here, we know the bucket is occupied 112 | if hashCode = bucket.HashCode && 113 | EqualityComparer.Default.Equals (key, bucket.Key) then 114 | bucket.Value <- value 115 | 116 | // If this new value is farther from it's Home than the current entry 117 | // take the entry for the new value and re-insert the prev entry 118 | elif bucket.Offset < offset then 119 | let prevKey = bucket.Key 120 | let prevValue = bucket.Value 121 | bucket.Offset <- offset 122 | bucket.HashCode <- hashCode 123 | bucket.Key <- key 124 | bucket.Value <- value 125 | addStructEntry prevKey prevValue 126 | else 127 | loop hashCode (offset + 1) (bucketIdx + 1) 128 | else 129 | // Start over looking from the beginning of the buckets 130 | loop hashCode (offset + 1) 0 131 | 132 | let hashCode = EqualityComparer.Default.GetHashCode key &&& POSITIVE_INT_MASK 133 | let bucketIdx = computeBucketIndex hashCode 134 | loop hashCode 0 bucketIdx 135 | 136 | 137 | let rec addRefEntry (key: 'Key) (value: 'Value) = 138 | 139 | let rec loop hashCode (offset: int) (bucketIdx: int) = 140 | if bucketIdx < buckets.Length then 141 | let bucket = &buckets[bucketIdx] 142 | // Check if bucket is Empty or a Tombstone 143 | if bucket.IsAvailable then 144 | bucket.Offset <- offset 145 | bucket.HashCode <- hashCode 146 | bucket.Key <- key 147 | bucket.Value <- value 148 | count <- count + 1 149 | else 150 | // If we reach here, we know the bucket is occupied 151 | if hashCode = bucket.HashCode && 152 | refComparer.Equals (key, bucket.Key) then 153 | bucket.Value <- value 154 | 155 | // If this new value is farther from it's Home than the current entry 156 | // take the entry for the new value and re-insert the prev entry 157 | elif bucket.Offset < offset then 158 | let prevKey = bucket.Key 159 | let prevValue = bucket.Value 160 | bucket.Offset <- offset 161 | bucket.HashCode <- hashCode 162 | bucket.Key <- key 163 | bucket.Value <- value 164 | addRefEntry prevKey prevValue 165 | else 166 | loop hashCode (offset + 1) (bucketIdx + 1) 167 | else 168 | // Start over looking from the beginning of the buckets 169 | loop hashCode (offset + 1) 0 170 | 171 | let hashCode = refComparer.GetHashCode key &&& POSITIVE_INT_MASK 172 | let bucketIdx = computeBucketIndex hashCode 173 | loop hashCode 0 bucketIdx 174 | 175 | 176 | let addEntry (key: 'Key) (value: 'Value) = 177 | 178 | if typeof<'Key>.IsValueType then 179 | addStructEntry key value 180 | else 181 | addRefEntry key value 182 | 183 | 184 | let getStructValue (key: 'Key) = 185 | let hashCode = EqualityComparer.Default.GetHashCode key &&& POSITIVE_INT_MASK 186 | 187 | let rec loop (bucketIdx: int) = 188 | if bucketIdx < buckets.Length then 189 | let bucket = buckets[bucketIdx] 190 | if bucket.IsEntry then 191 | if hashCode = bucket.HashCode && 192 | EqualityComparer.Default.Equals (key, bucket.Key) then 193 | bucket.Value 194 | 195 | else 196 | loop (bucketIdx + 1) 197 | 198 | elif bucket.IsTombstone then 199 | loop (bucketIdx + 1) 200 | 201 | else 202 | raise (KeyNotFoundException()) 203 | else 204 | loop 0 205 | 206 | let bucketIdx = computeBucketIndex hashCode 207 | loop bucketIdx 208 | 209 | 210 | let getRefValue (key: 'Key) = 211 | let hashCode = refComparer.GetHashCode key &&& POSITIVE_INT_MASK 212 | 213 | let rec loop (bucketIdx: int) = 214 | if bucketIdx < buckets.Length then 215 | let bucket = buckets[bucketIdx] 216 | if bucket.IsEntry then 217 | if hashCode = bucket.HashCode && 218 | refComparer.Equals (key, bucket.Key) then 219 | bucket.Value 220 | 221 | else 222 | loop (bucketIdx + 1) 223 | 224 | elif bucket.IsTombstone then 225 | loop (bucketIdx + 1) 226 | 227 | else 228 | raise (KeyNotFoundException()) 229 | else 230 | loop 0 231 | 232 | let bucketIdx = computeBucketIndex hashCode 233 | loop bucketIdx 234 | 235 | 236 | let resize () = 237 | // Resize if our fill is >75% 238 | if count > (buckets.Length >>> 2) * 3 then 239 | // if count > buckets.Length - 2 then 240 | let oldBuckets = buckets 241 | 242 | // Increase the size of the backing store 243 | buckets <- Array.create (buckets.Length <<< 1) Bucket.empty 244 | bucketBitShift <- 32 - (BitOperations.TrailingZeroCount buckets.Length) 245 | count <- 0 246 | 247 | for bucket in oldBuckets do 248 | if bucket.IsEntry then 249 | addEntry bucket.Key bucket.Value 250 | 251 | do 252 | for key, value in entries do 253 | addEntry key value 254 | resize() 255 | 256 | new () = Dictionary<'Key, 'Value>([]) 257 | 258 | member d.Item 259 | with get (key: 'Key) = 260 | if typeof<'Key>.IsValueType then 261 | getStructValue key 262 | else 263 | getRefValue key 264 | -------------------------------------------------------------------------------- /FastDictionaryTest/RobinHoodEviction.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.RobinHoodEviction 2 | 3 | open System 4 | open System.Numerics 5 | open Microsoft.FSharp.NativeInterop 6 | open System.Collections.Generic 7 | 8 | #nowarn "9" "42" "51" 9 | 10 | 11 | module private Helpers = 12 | 13 | let inline retype<'T,'U> (x: 'T) : 'U = (# "" x: 'U #) 14 | 15 | [] 16 | let POSITIVE_INT_MASK = 0x7FFF_FFFF 17 | 18 | [] 19 | module HashCode = 20 | let empty = -2 21 | let tombstone = -1 22 | 23 | [] 24 | type Bucket<'Key, 'Value> = 25 | { 26 | mutable HashCode : int 27 | mutable Offset : int 28 | mutable Key : 'Key 29 | mutable Value : 'Value 30 | } 31 | member s.IsTombstone = s.HashCode = HashCode.tombstone 32 | member s.IsEmpty = s.HashCode = HashCode.empty 33 | member s.IsEntry = s.HashCode >= 0 34 | member s.IsOccupied = s.HashCode >= -1 35 | member s.IsAvailable = s.HashCode < 0 36 | 37 | module Bucket = 38 | 39 | let empty<'Key, 'Value> = 40 | { 41 | HashCode = -1 42 | Offset = 0 43 | Key = Unchecked.defaultof<'Key> 44 | Value = Unchecked.defaultof<'Value> 45 | } 46 | 47 | let stringComparer = 48 | { new IEqualityComparer with 49 | member _.Equals (a: string, b: string) = 50 | String.Equals (a, b) 51 | 52 | member _.GetHashCode (a: string) = 53 | let charSpan = MemoryExtensions.AsSpan a 54 | let mutable hash1 = (5381u <<< 16) + 5381u 55 | let mutable hash2 = hash1 56 | let mutable length = a.Length 57 | let mutable ptr : nativeptr = 58 | &&charSpan.GetPinnableReference() 59 | |> retype 60 | while length > 2 do 61 | length <- length - 4 62 | hash1 <- (BitOperations.RotateLeft (hash1, 5) + hash1) ^^^ (NativePtr.get ptr 0) 63 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ (NativePtr.get ptr 1) 64 | ptr <- NativePtr.add ptr 2 65 | 66 | if length > 0 then 67 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ (NativePtr.get ptr 0) 68 | 69 | int (hash1 + (hash2 * 1566083941u)) 70 | } 71 | 72 | open Helpers 73 | 74 | 75 | type Dictionary<'Key, 'Value when 'Key : equality> (entries: seq<'Key * 'Value>) = 76 | // If the type of 'Key is a ref type, we will want to cache the EqualityComparer 77 | let refComparer = 78 | if typeof<'Key>.IsValueType then 79 | Unchecked.defaultof<_> 80 | elif typeof<'Key> = typeof then 81 | stringComparer :?> IEqualityComparer<'Key> 82 | else 83 | EqualityComparer<'Key>.Default :> IEqualityComparer<'Key> 84 | 85 | // Track the number of items in Dictionary for resize 86 | let mutable count = 0 87 | // Create the Buckets with some initial capacity 88 | let mutable buckets : Bucket<'Key, 'Value>[] = Array.create 4 Bucket.empty 89 | // BitShift necessary for mapping HashCode to BucketIdx using Fibonacci Hashing 90 | let mutable bucketBitShift = 32 - (BitOperations.TrailingZeroCount buckets.Length) 91 | 92 | // This relies on the number of buckets being a power of 2 93 | let computeHashCode (key: 'Key) = 94 | if typeof<'Key>.IsValueType then 95 | EqualityComparer.Default.GetHashCode key &&& POSITIVE_INT_MASK 96 | else 97 | refComparer.GetHashCode key &&& POSITIVE_INT_MASK 98 | 99 | 100 | let computeBucketIndex (hashCode: int) = 101 | let hashProduct = uint hashCode * 2654435769u 102 | int (hashProduct >>> bucketBitShift) 103 | 104 | 105 | let rec addStructEntry (key: 'Key) (value: 'Value) = 106 | 107 | let rec loop (offset: int) (hashCode: int) (bucketIdx: int) = 108 | if bucketIdx < buckets.Length then 109 | let bucket = &buckets[bucketIdx] 110 | // Check if bucket is Empty or a Tombstone 111 | if bucket.IsAvailable then 112 | bucket.Offset <- offset 113 | bucket.HashCode <- hashCode 114 | bucket.Key <- key 115 | bucket.Value <- value 116 | count <- count + 1 117 | else 118 | // If we reach here, we know the bucket is occupied 119 | if hashCode = bucket.HashCode && 120 | EqualityComparer.Default.Equals (key, bucket.Key) then 121 | bucket.Value <- value 122 | 123 | // We will evict the current entry under two conditions 124 | // 1. This is the home for the new Entry and the current Entry 125 | // is already away from its home 126 | // 2. The new Entry is farther from its home than the current Entry 127 | elif 128 | offset = 0 && bucket.Offset > 0 || 129 | ((bucket.Offset <> 0) && bucket.Offset < offset) then 130 | 131 | let prevKey = bucket.Key 132 | let prevValue = bucket.Value 133 | bucket.Offset <- offset 134 | bucket.HashCode <- hashCode 135 | bucket.Key <- key 136 | bucket.Value <- value 137 | addStructEntry prevKey prevValue 138 | else 139 | loop (offset + 1) hashCode (bucketIdx + 1) 140 | else 141 | // Start over looking from the beginning of the buckets 142 | loop (offset + 1) hashCode 0 143 | 144 | let hashCode = EqualityComparer.Default.GetHashCode key 145 | let bucketIdx = computeBucketIndex hashCode 146 | loop 0 hashCode bucketIdx 147 | 148 | 149 | let rec addRefEntry (key: 'Key) (value: 'Value) = 150 | 151 | let rec loop (offset: int) (hashCode: int) (bucketIdx: int) = 152 | if bucketIdx < buckets.Length then 153 | let bucket = &buckets[bucketIdx] 154 | // Check if bucket is Empty or a Tombstone 155 | if bucket.IsAvailable then 156 | bucket.Offset <- offset 157 | bucket.HashCode <- hashCode 158 | bucket.Key <- key 159 | bucket.Value <- value 160 | count <- count + 1 161 | else 162 | // If we reach here, we know the bucket is occupied 163 | if hashCode = bucket.HashCode && 164 | refComparer.Equals (key, bucket.Key) then 165 | bucket.Value <- value 166 | 167 | // We will evict the current entry under two conditions 168 | // 1. This is the home for the new Entry and the current Entry 169 | // is already away from its home 170 | // 2. The new Entry is farther from its home than the current Entry 171 | elif 172 | offset = 0 && bucket.Offset > 0 || 173 | ((bucket.Offset <> 0) && bucket.Offset < offset) then 174 | 175 | let prevKey = bucket.Key 176 | let prevValue = bucket.Value 177 | bucket.Offset <- offset 178 | bucket.HashCode <- hashCode 179 | bucket.Key <- key 180 | bucket.Value <- value 181 | addRefEntry prevKey prevValue 182 | else 183 | loop (offset + 1) hashCode (bucketIdx + 1) 184 | else 185 | // Start over looking from the beginning of the buckets 186 | loop (offset + 1) hashCode 0 187 | 188 | let hashCode = refComparer.GetHashCode key &&& POSITIVE_INT_MASK 189 | let bucketIdx = computeBucketIndex hashCode 190 | loop 0 hashCode bucketIdx 191 | 192 | 193 | let rec addEntry (key: 'Key) (value: 'Value) = 194 | 195 | if typeof<'Key>.IsValueType then 196 | addStructEntry key value 197 | 198 | else 199 | addRefEntry key value 200 | 201 | 202 | let getStructValue (key: 'Key) = 203 | 204 | let rec loop (hashCode: int) (bucketIdx: int) = 205 | if bucketIdx < buckets.Length then 206 | let bucket = buckets[bucketIdx] 207 | if bucket.IsEntry then 208 | if hashCode = bucket.HashCode && 209 | EqualityComparer.Default.Equals (key, bucket.Key) then 210 | bucket.Value 211 | 212 | else 213 | loop hashCode (bucketIdx + 1) 214 | 215 | elif bucket.IsTombstone then 216 | loop hashCode (bucketIdx + 1) 217 | 218 | else 219 | raise (KeyNotFoundException()) 220 | else 221 | loop hashCode 0 222 | 223 | let hashCode = computeHashCode key 224 | let bucketIdx = computeBucketIndex hashCode 225 | loop hashCode bucketIdx 226 | 227 | 228 | let getRefValue (key: 'Key) = 229 | 230 | let rec loop (hashCode: int) (bucketIdx: int) = 231 | if bucketIdx < buckets.Length then 232 | let bucket = buckets[bucketIdx] 233 | if bucket.IsEntry then 234 | if hashCode = bucket.HashCode && 235 | refComparer.Equals (key, bucket.Key) then 236 | bucket.Value 237 | 238 | else 239 | loop hashCode (bucketIdx + 1) 240 | 241 | elif bucket.IsTombstone then 242 | loop hashCode (bucketIdx + 1) 243 | 244 | else 245 | raise (KeyNotFoundException()) 246 | else 247 | loop hashCode 0 248 | 249 | let hashCode = computeHashCode key 250 | let bucketIdx = computeBucketIndex hashCode 251 | loop hashCode bucketIdx 252 | 253 | 254 | let resize () = 255 | // Resize if our fill is >75% 256 | if count > (buckets.Length >>> 2) * 3 then 257 | // if count > buckets.Length - 2 then 258 | let oldBuckets = buckets 259 | 260 | // Increase the size of the backing store 261 | buckets <- Array.create (buckets.Length <<< 1) Bucket.empty 262 | bucketBitShift <- 32 - (BitOperations.TrailingZeroCount buckets.Length) 263 | count <- 0 264 | 265 | for bucket in oldBuckets do 266 | if bucket.IsEntry then 267 | addEntry bucket.Key bucket.Value 268 | 269 | do 270 | for k, v in entries do 271 | addEntry k v 272 | resize() 273 | 274 | new () = Dictionary<'Key, 'Value>([]) 275 | 276 | member d.Item 277 | with get (key: 'Key) = 278 | if typeof<'Key>.IsValueType then 279 | getStructValue key 280 | else 281 | getRefValue key 282 | -------------------------------------------------------------------------------- /FastDictionaryTest/SOA.fs: -------------------------------------------------------------------------------- 1 | module FastDictionaryTest.SOA.StaticDict 2 | 3 | open FastDictionaryTest.SOA.Helpers 4 | 5 | let create (entries: seq<'Key * 'Value>) : StaticDict<'Key, 'Value> = 6 | 7 | match entries with 8 | | :? seq as entries -> 9 | retype (IntDict.create entries) 10 | | :? seq as entries -> 11 | retype (StrDict.create entries) 12 | | _ -> 13 | if typeof<'Key>.IsValueType then 14 | ValueDict.create entries 15 | else 16 | RefDict.create entries 17 | -------------------------------------------------------------------------------- /FastDictionaryTest/SOA/Helpers.fs: -------------------------------------------------------------------------------- 1 | module FastDictionaryTest.SOA.Helpers 2 | 3 | open System 4 | open System.Numerics 5 | open Microsoft.FSharp.NativeInterop 6 | open System.Collections.Generic 7 | 8 | #nowarn "9" "42" "51" 9 | 10 | [] 11 | type StaticDict<'Key, 'Value>() = 12 | abstract member Item : 'Key -> 'Value with get 13 | 14 | 15 | let inline retype<'T,'U> (x: 'T) : 'U = (# "" x: 'U #) 16 | 17 | [] 18 | module Next = 19 | let empty = Byte.MaxValue 20 | let tombstone = Byte.MaxValue - 1uy 21 | let last = 0uy 22 | 23 | let isTombstone next = next = tombstone 24 | let isEmpty next = next = empty 25 | let isEntry next = next < tombstone 26 | let isOccupied next = next <= tombstone 27 | let isAvailable next = next >= tombstone 28 | let isLast next = next = last 29 | -------------------------------------------------------------------------------- /FastDictionaryTest/SOA/IntDict.fs: -------------------------------------------------------------------------------- 1 | module FastDictionaryTest.SOA.IntDict 2 | 3 | open System.Collections.Generic 4 | open System.Numerics 5 | open FastDictionaryTest.SOA.Helpers 6 | 7 | #nowarn "9" "42" "51" 8 | 9 | module internal rec Helpers = 10 | 11 | [] 12 | type Acc<'Key, 'Value when 'Key : equality> = 13 | { 14 | mutable Count: int 15 | mutable Keys: 'Key[] 16 | mutable Values: 'Value[] 17 | mutable Nexts: byte[] 18 | mutable BucketBitShift: int 19 | mutable WrapAroundMask: int 20 | } 21 | static member init () = 22 | let initialCapacity = 4 23 | { 24 | Count = 0 25 | Keys = Array.zeroCreate 4 26 | Values = Array.zeroCreate 4 27 | Nexts = Array.create 4 Next.empty 28 | BucketBitShift = 32 - (BitOperations.TrailingZeroCount initialCapacity) 29 | WrapAroundMask = initialCapacity - 1 30 | } 31 | 32 | [] 33 | type IntData<'Value> = 34 | { 35 | Keys: int[] 36 | Values: 'Value[] 37 | Nexts: byte[] 38 | BucketBitShift: int 39 | WrapAroundMask: int 40 | } 41 | static member ofAcc (acc: Acc) = 42 | { 43 | Keys = acc.Keys 44 | Values = acc.Values 45 | Nexts = acc.Nexts 46 | BucketBitShift = acc.BucketBitShift 47 | WrapAroundMask = acc.WrapAroundMask 48 | } 49 | 50 | let computeBucketIndex bucketBitShift (hashCode: int) = 51 | let hashProduct = (uint hashCode) * 2654435769u 52 | int (hashProduct >>> bucketBitShift) 53 | 54 | 55 | let isTail (d: inref>) (bucketIdx: int) = 56 | let homeIdx = computeBucketIndex d.BucketBitShift d.Keys[bucketIdx] 57 | bucketIdx <> homeIdx 58 | 59 | 60 | let getParentBucketIdxLoop (d: inref>) (childBucketIdx: int) (ancestorIdx: int) = 61 | let nextIdx = (ancestorIdx + int d.Nexts[ancestorIdx]) &&& d.WrapAroundMask 62 | if nextIdx = childBucketIdx then 63 | ancestorIdx 64 | else 65 | getParentBucketIdxLoop &d childBucketIdx nextIdx 66 | 67 | 68 | let getParentBucketIdx (d: inref>) (hashCode: int) (childBucketIdx: int) = 69 | let initialIdx = computeBucketIndex d.BucketBitShift hashCode 70 | getParentBucketIdxLoop &d childBucketIdx initialIdx 71 | 72 | 73 | let distanceFromParent (d: inref>) (bucketIdx: int) = 74 | let parentIdx = getParentBucketIdx &d d.Keys[bucketIdx] bucketIdx 75 | d.Nexts[parentIdx] 76 | 77 | 78 | let setBucket (d: inref>) next key value bucketIdx = 79 | d.Nexts[bucketIdx] <- next 80 | d.Keys[bucketIdx] <- key 81 | d.Values[bucketIdx] <- value 82 | 83 | 84 | let removeFromList (d: inref>) (bucketIdx: int) = 85 | let parentBucketIdx = getParentBucketIdx &d d.Keys[bucketIdx] bucketIdx 86 | 87 | // If this is the Last element in a List, we just need to update the Parent's 88 | // NextOffset value to 0 and then re-add this entry 89 | if Next.isLast d.Nexts[bucketIdx] then 90 | d.Nexts[parentBucketIdx] <- 0uy 91 | 92 | // This element is in the middle of the list so we will remove it from the existing 93 | // list by having the Parent point to the former Grandchild, now child bucket 94 | else 95 | d.Nexts[parentBucketIdx] <- d.Nexts[parentBucketIdx] + d.Nexts[bucketIdx] 96 | 97 | 98 | let rec insertIntoNextEmptyBucket (d: byref>) (parentIdx: int) (key: int) (value: 'Value) (offset: byte) (bucketIdx: int) = 99 | if bucketIdx < d.Keys.Length then 100 | if Next.isAvailable d.Nexts[bucketIdx] then 101 | setBucket &d Next.last key value bucketIdx 102 | d.Count <- d.Count + 1 103 | d.Nexts[parentIdx] <- offset 104 | // Test if this is an entry that is not at its home bucket and is 105 | // closer to its home than this new entry will be. This is Robin Hood hashing 106 | elif (isTail &d bucketIdx) && offset > (distanceFromParent &d bucketIdx) then 107 | // Need to take a temporary copy for the purpose of re-insertion 108 | let prevKey = d.Keys[bucketIdx] 109 | let prevValue = d.Values[bucketIdx] 110 | removeFromList &d bucketIdx 111 | setBucket &d Next.last key value bucketIdx 112 | d.Nexts[parentIdx] <- offset 113 | addEntry &d prevKey prevValue 114 | else 115 | insertIntoNextEmptyBucket &d parentIdx key value (offset + 1uy) (bucketIdx + 1) 116 | 117 | else 118 | insertIntoNextEmptyBucket &d parentIdx key value offset 0 119 | 120 | 121 | let rec listSearch (d: byref>) (key: int) (value: 'Value) (bucketIdx: int) = 122 | // Check if we have found an existing Entry for the Key 123 | // If we have, we want to update the value 124 | if d.Keys[bucketIdx] = key then 125 | d.Values[bucketIdx] <- value 126 | 127 | // The Entry is not a match for Key so we need to check if we have come 128 | // to the end of the list. If we have, then we search for empty space 129 | // to add our new Key/Value and update the offset for the previous Last entry. 130 | elif Next.isLast d.Nexts[bucketIdx] then 131 | insertIntoNextEmptyBucket &d bucketIdx key value 1uy (bucketIdx + 1) 132 | 133 | // We are not at the end of the list so we compute the next BucketIdx and move 134 | // to the next Entry. We use a wrap around mask to ensure we don't go outside 135 | // the bounds of the array. 136 | else 137 | // Compute the next index which takes the wrap around logic into account 138 | let nextBucketIdx = (bucketIdx + (int d.Nexts[bucketIdx])) &&& d.WrapAroundMask 139 | listSearch &d key value nextBucketIdx 140 | 141 | 142 | let rec addEntry (d: byref>) (key: int) (value: 'Value) = 143 | 144 | let bucketIdx = computeBucketIndex d.BucketBitShift key 145 | // Check if bucket is Empty or a Tombstone 146 | if Next.isAvailable d.Nexts[bucketIdx] then 147 | setBucket &d Next.last key value bucketIdx 148 | d.Count <- d.Count + 1 149 | 150 | // If there is already an entry for this Key, overwrite the Value 151 | elif d.Keys[bucketIdx] = key then 152 | d.Values[bucketIdx] <- value 153 | 154 | // Check if the current Entry is part of a chain for a different 155 | // BucketIdx and should therefore be evicted 156 | elif isTail &d bucketIdx then 157 | // Move the current entry out of this position 158 | let prevKey = d.Keys[bucketIdx] 159 | let prevValue = d.Values[bucketIdx] 160 | removeFromList &d bucketIdx 161 | setBucket &d Next.last key value bucketIdx 162 | addEntry &d prevKey prevValue 163 | d.Count <- d.Count + 1 164 | 165 | // In this case, the current Entry is the head of a list that 166 | // we need to append to. We start searching for the tail of the list 167 | else 168 | listSearch &d key value bucketIdx 169 | 170 | 171 | let resize (d: byref>) = 172 | // Resize if our fill is >75% 173 | if d.Count > (d.Keys.Length >>> 2) * 3 then 174 | // if count > buckets.Length - 2 then 175 | let prevKeys = d.Keys 176 | let prevValues = d.Values 177 | let prevNexts = d.Nexts 178 | 179 | // Increase the size of the backing store 180 | let newSize = (prevKeys.Length <<< 1) 181 | d.Keys <- Array.zeroCreate newSize 182 | d.Values <- Array.zeroCreate newSize 183 | d.Nexts <- Array.create newSize Next.empty 184 | d.BucketBitShift <- 32 - (BitOperations.TrailingZeroCount newSize) 185 | d.WrapAroundMask <- newSize - 1 186 | d.Count <- 0 187 | 188 | for i in 0..prevKeys.Length - 1 do 189 | if Next.isEntry prevNexts[i] then 190 | addEntry &d prevKeys[i] prevValues[i] 191 | 192 | let raiseKeyNotFound key = 193 | raise (KeyNotFoundException $"Missing Key: {key}") 194 | 195 | let rec searchLoop (d: inref>) (key: int) (bucketIdx: int) = 196 | if key = d.Keys[bucketIdx] then 197 | d.Values[bucketIdx] 198 | 199 | elif Next.isLast d.Nexts[bucketIdx] then 200 | raiseKeyNotFound key 201 | 202 | else 203 | let nextBucketIdx = (bucketIdx + (int d.Nexts[bucketIdx])) &&& d.WrapAroundMask 204 | searchLoop &d key nextBucketIdx 205 | 206 | open Helpers 207 | 208 | 209 | type IntStaticDict<'Value> internal (d: IntData<'Value>) = 210 | 211 | inherit StaticDict() 212 | 213 | override _.Item 214 | with get (key: int) = 215 | let bucketIdx = computeBucketIndex d.BucketBitShift key 216 | 217 | if key = d.Keys[bucketIdx] then 218 | d.Values[bucketIdx] 219 | 220 | elif Next.isLast d.Nexts[bucketIdx] then 221 | raiseKeyNotFound key 222 | 223 | else 224 | let nextBucketIdx = (bucketIdx + (int d.Nexts[bucketIdx])) &&& d.WrapAroundMask 225 | searchLoop &d key nextBucketIdx 226 | 227 | 228 | let create (entries: seq) = 229 | let uniqueKeys = HashSet() 230 | for key, _ in entries do 231 | uniqueKeys.Add key |> ignore 232 | 233 | let mutable acc : Acc = Acc<_,_>.init() 234 | 235 | for key, value in entries do 236 | addEntry &acc key value 237 | resize &acc 238 | 239 | let d = IntData.ofAcc acc 240 | IntStaticDict d :> StaticDict 241 | -------------------------------------------------------------------------------- /FastDictionaryTest/SOA/RefDict.fs: -------------------------------------------------------------------------------- 1 | module FastDictionaryTest.SOA.RefDict 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.Numerics 6 | open FastDictionaryTest.SOA.Helpers 7 | 8 | #nowarn "9" "42" "51" 9 | 10 | module internal rec Helpers = 11 | 12 | [] 13 | type Acc<'Key, 'Value when 'Key : equality> = 14 | { 15 | Comparer: EqualityComparer<'Key> 16 | mutable Count: int 17 | mutable Keys: 'Key[] 18 | mutable Values: 'Value[] 19 | mutable HashCodes: int[] 20 | mutable Nexts: byte[] 21 | mutable BucketBitShift: int 22 | mutable WrapAroundMask: int 23 | } 24 | static member init () = 25 | let initialCapacity = 4 26 | { 27 | Comparer = EqualityComparer.Default 28 | Count = 0 29 | Keys = Array.zeroCreate 4 30 | Values = Array.zeroCreate 4 31 | HashCodes = Array.zeroCreate 4 32 | Nexts = Array.create 4 Next.empty 33 | BucketBitShift = 32 - (BitOperations.TrailingZeroCount initialCapacity) 34 | WrapAroundMask = initialCapacity - 1 35 | } 36 | 37 | [] 38 | type Data<'Key, 'Value when 'Key : equality> = 39 | { 40 | Comparer: EqualityComparer<'Key> 41 | Keys: 'Key[] 42 | Values: 'Value[] 43 | HashCodes: int[] 44 | Nexts: byte[] 45 | BucketBitShift: int 46 | WrapAroundMask: int 47 | } 48 | static member ofAcc (acc: Acc<'Key, 'Value>) = 49 | { 50 | Comparer = acc.Comparer 51 | Keys = acc.Keys 52 | Values = acc.Values 53 | HashCodes = acc.HashCodes 54 | Nexts = acc.Nexts 55 | BucketBitShift = acc.BucketBitShift 56 | WrapAroundMask = acc.WrapAroundMask 57 | } 58 | 59 | let computeBucketIndex bucketBitShift (hashCode: int) = 60 | let hashProduct = (uint hashCode) * 2654435769u 61 | int (hashProduct >>> bucketBitShift) 62 | 63 | 64 | let isTail (d: inref>) (bucketIdx: int) = 65 | let homeIdx = computeBucketIndex d.BucketBitShift d.HashCodes[bucketIdx] 66 | bucketIdx <> homeIdx 67 | 68 | 69 | let getParentBucketIdxLoop (d: inref>) (childBucketIdx: int) (ancestorIdx: int) = 70 | let nextIdx = (ancestorIdx + int d.Nexts[ancestorIdx]) &&& d.WrapAroundMask 71 | if nextIdx = childBucketIdx then 72 | ancestorIdx 73 | else 74 | getParentBucketIdxLoop &d childBucketIdx nextIdx 75 | 76 | 77 | let getParentBucketIdx (d: inref>) (hashCode: int) (childBucketIdx: int) = 78 | let initialIdx = computeBucketIndex d.BucketBitShift hashCode 79 | getParentBucketIdxLoop &d childBucketIdx initialIdx 80 | 81 | 82 | let distanceFromParent (d: inref>) (bucketIdx: int) = 83 | let parentIdx = getParentBucketIdx &d d.HashCodes[bucketIdx] bucketIdx 84 | d.Nexts[parentIdx] 85 | 86 | 87 | let setBucket (d: inref>) next hashCode key value bucketIdx = 88 | d.Nexts[bucketIdx] <- next 89 | d.HashCodes[bucketIdx] <- hashCode 90 | d.Keys[bucketIdx] <- key 91 | d.Values[bucketIdx] <- value 92 | 93 | 94 | let removeFromList (d: inref>) (bucketIdx: int) = 95 | let parentBucketIdx = getParentBucketIdx &d d.HashCodes[bucketIdx] bucketIdx 96 | 97 | // If this is the Last element in a List, we just need to update the Parent's 98 | // NextOffset value to 0 and then re-add this entry 99 | if Next.isLast d.Nexts[bucketIdx] then 100 | d.Nexts[parentBucketIdx] <- 0uy 101 | 102 | // This element is in the middle of the list so we will remove it from the existing 103 | // list by having the Parent point to the former Grandchild, now child bucket 104 | else 105 | d.Nexts[parentBucketIdx] <- d.Nexts[parentBucketIdx] + d.Nexts[bucketIdx] 106 | 107 | 108 | let rec insertIntoNextEmptyBucket (d: byref>) (parentIdx: int) (hashCode: int) (key: 'Key) (value: 'Value) (offset: byte) (bucketIdx: int) = 109 | if bucketIdx < d.Keys.Length then 110 | if Next.isAvailable d.Nexts[bucketIdx] then 111 | setBucket &d Next.last hashCode key value bucketIdx 112 | d.Count <- d.Count + 1 113 | d.Nexts[parentIdx] <- offset 114 | // Test if this is an entry that is not at its home bucket and is 115 | // closer to its home than this new entry will be. This is Robin Hood hashing 116 | elif (isTail &d bucketIdx) && offset > (distanceFromParent &d bucketIdx) then 117 | // Need to take a temporary copy for the purpose of re-insertion 118 | let prevHashCode = d.HashCodes[bucketIdx] 119 | let prevKey = d.Keys[bucketIdx] 120 | let prevValue = d.Values[bucketIdx] 121 | removeFromList &d bucketIdx 122 | setBucket &d Next.last hashCode key value bucketIdx 123 | d.Nexts[parentIdx] <- offset 124 | addEntry &d prevHashCode prevKey prevValue 125 | else 126 | insertIntoNextEmptyBucket &d parentIdx hashCode key value (offset + 1uy) (bucketIdx + 1) 127 | 128 | else 129 | insertIntoNextEmptyBucket &d parentIdx hashCode key value offset 0 130 | 131 | 132 | let rec listSearch (d: byref>) (hashCode: int) (key: 'Key) (value: 'Value) (bucketIdx: int) = 133 | // Check if we have found an existing Entry for the Key 134 | // If we have, we want to update the value 135 | if d.HashCodes[bucketIdx] = hashCode && 136 | d.Comparer.Equals (key, d.Keys[bucketIdx]) then 137 | d.Values[bucketIdx] <- value 138 | 139 | // The Entry is not a match for Key so we need to check if we have come 140 | // to the end of the list. If we have, then we search for empty space 141 | // to add our new Key/Value and update the offset for the previous Last entry. 142 | elif Next.isLast d.Nexts[bucketIdx] then 143 | 144 | insertIntoNextEmptyBucket &d bucketIdx hashCode key value 1uy (bucketIdx + 1) 145 | 146 | // We are not at the end of the list so we compute the next BucketIdx and move 147 | // to the next Entry. We use a wrap around mask to ensure we don't go outside 148 | // the bounds of the array. 149 | else 150 | // Compute the next index which takes the wrap around logic into account 151 | let nextBucketIdx = (bucketIdx + (int d.Nexts[bucketIdx])) &&& d.WrapAroundMask 152 | listSearch &d hashCode key value nextBucketIdx 153 | 154 | 155 | let rec addEntry (d: byref>) (hashCode: int) (key: 'Key) (value: 'Value) = 156 | 157 | let bucketIdx = computeBucketIndex d.BucketBitShift hashCode 158 | // Check if bucket is Empty or a Tombstone 159 | if Next.isAvailable d.Nexts[bucketIdx] then 160 | setBucket &d Next.last hashCode key value bucketIdx 161 | d.Count <- d.Count + 1 162 | 163 | // If there is already an entry for this Key, overwrite the Value 164 | elif d.HashCodes[bucketIdx] = hashCode && 165 | d.Comparer.Equals (key, d.Keys[bucketIdx]) then 166 | d.Values[bucketIdx] <- value 167 | 168 | // Check if the current Entry is part of a chain for a different 169 | // BucketIdx and should therefore be evicted 170 | elif isTail &d bucketIdx then 171 | // Move the current entry out of this position 172 | let prevHashCode = d.HashCodes[bucketIdx] 173 | let prevKey = d.Keys[bucketIdx] 174 | let prevValue = d.Values[bucketIdx] 175 | removeFromList &d bucketIdx 176 | setBucket &d Next.last hashCode key value bucketIdx 177 | addEntry &d prevHashCode prevKey prevValue 178 | d.Count <- d.Count + 1 179 | 180 | // In this case, the current Entry is the head of a list that 181 | // we need to append to. We start searching for the tail of the list 182 | else 183 | listSearch &d hashCode key value bucketIdx 184 | 185 | 186 | let resize (d: byref>) = 187 | // Resize if our fill is >75% 188 | if d.Count > (d.Keys.Length >>> 2) * 3 then 189 | // if count > buckets.Length - 2 then 190 | let prevKeys = d.Keys 191 | let prevValues = d.Values 192 | let prevHashCodes = d.HashCodes 193 | let prevNexts = d.Nexts 194 | 195 | // Increase the size of the backing store 196 | let newSize = (prevKeys.Length <<< 1) 197 | d.Keys <- Array.zeroCreate newSize 198 | d.Values <- Array.zeroCreate newSize 199 | d.HashCodes <- Array.zeroCreate newSize 200 | d.Nexts <- Array.create newSize Next.empty 201 | d.BucketBitShift <- 32 - (BitOperations.TrailingZeroCount newSize) 202 | d.WrapAroundMask <- newSize - 1 203 | d.Count <- 0 204 | 205 | for i in 0..prevKeys.Length - 1 do 206 | if Next.isEntry prevNexts[i] then 207 | addEntry &d prevHashCodes[i] prevKeys[i] prevValues[i] 208 | 209 | let raiseKeyNotFound hashcode key = 210 | raise (KeyNotFoundException $"Missing Key: {key} Hashcode: {hashcode}") 211 | 212 | let rec searchLoop (d: inref>) (hashCode: int) (key: 'Key) (bucketIdx: int) = 213 | if hashCode = d.HashCodes[bucketIdx] && 214 | d.Comparer.Equals (key, d.Keys[bucketIdx]) then 215 | d.Values[bucketIdx] 216 | 217 | elif Next.isLast d.Nexts[bucketIdx] then 218 | raiseKeyNotFound hashCode key 219 | 220 | else 221 | let nextBucketIdx = (bucketIdx + (int d.Nexts[bucketIdx])) &&& d.WrapAroundMask 222 | searchLoop &d hashCode key nextBucketIdx 223 | 224 | open Helpers 225 | 226 | 227 | type RefStaticDict<'Key, 'Value when 'Key : equality> internal (d: Data<'Key, 'Value>) = 228 | 229 | inherit StaticDict<'Key, 'Value>() 230 | 231 | override _.Item 232 | with get (key: 'Key) = 233 | let hashCode = d.Comparer.GetHashCode key 234 | let bucketIdx = computeBucketIndex d.BucketBitShift hashCode 235 | 236 | if hashCode = d.HashCodes[bucketIdx] && 237 | d.Comparer.Equals (key, d.Keys[bucketIdx]) then 238 | d.Values[bucketIdx] 239 | 240 | elif Next.isLast d.Nexts[bucketIdx] then 241 | raiseKeyNotFound hashCode key 242 | 243 | else 244 | let nextBucketIdx = (bucketIdx + (int d.Nexts[bucketIdx])) &&& d.WrapAroundMask 245 | searchLoop &d hashCode key nextBucketIdx 246 | 247 | let create (entries: seq<'Key * 'Value>) = 248 | let uniqueKeys = HashSet() 249 | for key, _ in entries do 250 | uniqueKeys.Add key |> ignore 251 | 252 | let mutable acc : Acc<'Key, 'Value> = Acc<_,_>.init() 253 | 254 | for key, value in entries do 255 | let hashCode = acc.Comparer.GetHashCode key 256 | addEntry &acc hashCode key value 257 | resize &acc 258 | 259 | let d = Data.ofAcc acc 260 | RefStaticDict d :> StaticDict<'Key, 'Value> 261 | -------------------------------------------------------------------------------- /FastDictionaryTest/SOA/StrDict.fs: -------------------------------------------------------------------------------- 1 | module FastDictionaryTest.SOA.StrDict 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.Numerics 6 | open Microsoft.FSharp.NativeInterop 7 | open FastDictionaryTest.SOA.Helpers 8 | 9 | #nowarn "9" "42" "51" 10 | 11 | module internal rec Helpers = 12 | 13 | [] 14 | type Acc<'Key, 'Value when 'Key : equality> = 15 | { 16 | mutable Count: int 17 | mutable Keys: 'Key[] 18 | mutable Values: 'Value[] 19 | mutable HashCodes: int[] 20 | mutable Nexts: byte[] 21 | mutable BucketBitShift: int 22 | mutable WrapAroundMask: int 23 | } 24 | static member init () = 25 | let initialCapacity = 4 26 | { 27 | Count = 0 28 | Keys = Array.zeroCreate 4 29 | Values = Array.zeroCreate 4 30 | HashCodes = Array.zeroCreate 4 31 | Nexts = Array.create 4 Next.empty 32 | BucketBitShift = 32 - (BitOperations.TrailingZeroCount initialCapacity) 33 | WrapAroundMask = initialCapacity - 1 34 | } 35 | 36 | [] 37 | type Range = 38 | { 39 | Start: int 40 | Length: int 41 | } 42 | 43 | [] 44 | type Data<'Value> = 45 | { 46 | KeyRanges: Range[] 47 | KeyChars: char[] 48 | Values: 'Value[] 49 | HashCodes: int[] 50 | Nexts: byte[] 51 | BucketBitShift: int 52 | WrapAroundMask: int 53 | } 54 | static member ofAcc (acc: Acc) = 55 | let keyChars = 56 | acc.Keys 57 | |> Array.collect (fun strKey -> 58 | if obj.ReferenceEquals (strKey, null) then 59 | [||] 60 | else 61 | strKey.ToCharArray()) 62 | 63 | let mutable i = 0 64 | 65 | let keyRanges = 66 | acc.Keys 67 | |> Array.map (fun strKey -> 68 | let nextStart = i 69 | if obj.ReferenceEquals (strKey, null) then 70 | { 71 | Start = nextStart 72 | Length = 0 73 | } 74 | else 75 | i <- i + strKey.Length 76 | { 77 | Start = nextStart 78 | Length = strKey.Length 79 | }) 80 | 81 | { 82 | KeyRanges = keyRanges 83 | KeyChars = keyChars 84 | Values = acc.Values 85 | HashCodes = acc.HashCodes 86 | Nexts = acc.Nexts 87 | BucketBitShift = acc.BucketBitShift 88 | WrapAroundMask = acc.WrapAroundMask 89 | } 90 | 91 | 92 | let strEquals (a: string, b: string) = 93 | a.AsSpan().SequenceEqual(b.AsSpan()) 94 | 95 | let strHashCode (a: string) : int = 96 | let mutable hash1 = (5381u <<< 16) + 5381u 97 | let mutable hash2 = hash1 98 | let mutable length = a.Length 99 | 100 | use ptr = fixed a 101 | 102 | // We are going to index from the end of the string back 103 | let mutable ptr32 : nativeptr = retype (NativePtr.add ptr (a.Length - 4)) 104 | while length > 3 do 105 | hash1 <- (BitOperations.RotateLeft (hash1, 5) + hash1) ^^^ uint (NativePtr.get ptr32 0) 106 | hash2 <- (BitOperations.RotateLeft (hash2, 5) + hash2) ^^^ uint (NativePtr.get ptr32 1) 107 | length <- length - 4 108 | ptr32 <- NativePtr.add ptr32 -2 109 | 110 | int (hash1 + (hash2 * 1566083941u)) 111 | 112 | let computeBucketIndex bucketBitShift (hashCode: int) = 113 | let hashProduct = (uint hashCode) * 2654435769u 114 | int (hashProduct >>> bucketBitShift) 115 | 116 | 117 | let isTail (d: inref>) (bucketIdx: int) = 118 | let homeIdx = computeBucketIndex d.BucketBitShift d.HashCodes[bucketIdx] 119 | bucketIdx <> homeIdx 120 | 121 | 122 | let getParentBucketIdxLoop (d: inref>) (childBucketIdx: int) (ancestorIdx: int) = 123 | let nextIdx = (ancestorIdx + int d.Nexts[ancestorIdx]) &&& d.WrapAroundMask 124 | if nextIdx = childBucketIdx then 125 | ancestorIdx 126 | else 127 | getParentBucketIdxLoop &d childBucketIdx nextIdx 128 | 129 | 130 | let getParentBucketIdx (d: inref>) (hashCode: int) (childBucketIdx: int) = 131 | let initialIdx = computeBucketIndex d.BucketBitShift hashCode 132 | getParentBucketIdxLoop &d childBucketIdx initialIdx 133 | 134 | 135 | let distanceFromParent (d: inref>) (bucketIdx: int) = 136 | let parentIdx = getParentBucketIdx &d d.HashCodes[bucketIdx] bucketIdx 137 | d.Nexts[parentIdx] 138 | 139 | 140 | let setBucket (d: inref>) next hashCode key value bucketIdx = 141 | d.Nexts[bucketIdx] <- next 142 | d.HashCodes[bucketIdx] <- hashCode 143 | d.Keys[bucketIdx] <- key 144 | d.Values[bucketIdx] <- value 145 | 146 | 147 | let removeFromList (d: inref>) (bucketIdx: int) = 148 | let parentBucketIdx = getParentBucketIdx &d d.HashCodes[bucketIdx] bucketIdx 149 | 150 | // If this is the Last element in a List, we just need to update the Parent's 151 | // NextOffset value to 0 and then re-add this entry 152 | if Next.isLast d.Nexts[bucketIdx] then 153 | d.Nexts[parentBucketIdx] <- 0uy 154 | 155 | // This element is in the middle of the list so we will remove it from the existing 156 | // list by having the Parent point to the former Grandchild, now child bucket 157 | else 158 | d.Nexts[parentBucketIdx] <- d.Nexts[parentBucketIdx] + d.Nexts[bucketIdx] 159 | 160 | 161 | let rec insertIntoNextEmptyBucket (d: byref>) (parentIdx: int) (hashCode: int) (key: string) (value: 'Value) (offset: byte) (bucketIdx: int) = 162 | if bucketIdx < d.Keys.Length then 163 | if Next.isAvailable d.Nexts[bucketIdx] then 164 | setBucket &d Next.last hashCode key value bucketIdx 165 | d.Count <- d.Count + 1 166 | d.Nexts[parentIdx] <- offset 167 | // Test if this is an entry that is not at its home bucket and is 168 | // closer to its home than this new entry will be. This is Robin Hood hashing 169 | elif (isTail &d bucketIdx) && offset > (distanceFromParent &d bucketIdx) then 170 | // Need to take a temporary copy for the purpose of re-insertion 171 | let prevHashCode = d.HashCodes[bucketIdx] 172 | let prevKey = d.Keys[bucketIdx] 173 | let prevValue = d.Values[bucketIdx] 174 | removeFromList &d bucketIdx 175 | setBucket &d Next.last hashCode key value bucketIdx 176 | d.Nexts[parentIdx] <- offset 177 | addEntry &d prevHashCode prevKey prevValue 178 | else 179 | insertIntoNextEmptyBucket &d parentIdx hashCode key value (offset + 1uy) (bucketIdx + 1) 180 | 181 | else 182 | insertIntoNextEmptyBucket &d parentIdx hashCode key value offset 0 183 | 184 | 185 | let rec listSearch (d: byref>) (hashCode: int) (key: string) (value: 'Value) (bucketIdx: int) = 186 | // Check if we have found an existing Entry for the Key 187 | // If we have, we want to update the value 188 | if d.HashCodes[bucketIdx] = hashCode && 189 | strEquals (d.Keys[bucketIdx], key) then 190 | d.Values[bucketIdx] <- value 191 | 192 | // The Entry is not a match for Key so we need to check if we have come 193 | // to the end of the list. If we have, then we search for empty space 194 | // to add our new Key/Value and update the offset for the previous Last entry. 195 | elif Next.isLast d.Nexts[bucketIdx] then 196 | 197 | insertIntoNextEmptyBucket &d bucketIdx hashCode key value 1uy (bucketIdx + 1) 198 | 199 | // We are not at the end of the list so we compute the next BucketIdx and move 200 | // to the next Entry. We use a wrap around mask to ensure we don't go outside 201 | // the bounds of the array. 202 | else 203 | // Compute the next index which takes the wrap around logic into account 204 | let nextBucketIdx = (bucketIdx + (int d.Nexts[bucketIdx])) &&& d.WrapAroundMask 205 | listSearch &d hashCode key value nextBucketIdx 206 | 207 | 208 | let rec addEntry (d: byref>) (hashCode: int) (key: string) (value: 'Value) = 209 | 210 | let bucketIdx = computeBucketIndex d.BucketBitShift hashCode 211 | // Check if bucket is Empty or a Tombstone 212 | if Next.isAvailable d.Nexts[bucketIdx] then 213 | setBucket &d Next.last hashCode key value bucketIdx 214 | d.Count <- d.Count + 1 215 | 216 | // If there is already an entry for this Key, overwrite the Value 217 | elif d.HashCodes[bucketIdx] = hashCode && 218 | strEquals (d.Keys[bucketIdx], key) then 219 | d.Values[bucketIdx] <- value 220 | 221 | // Check if the current Entry is part of a chain for a different 222 | // BucketIdx and should therefore be evicted 223 | elif isTail &d bucketIdx then 224 | // Move the current entry out of this position 225 | let prevHashCode = d.HashCodes[bucketIdx] 226 | let prevKey = d.Keys[bucketIdx] 227 | let prevValue = d.Values[bucketIdx] 228 | removeFromList &d bucketIdx 229 | setBucket &d Next.last hashCode key value bucketIdx 230 | addEntry &d prevHashCode prevKey prevValue 231 | d.Count <- d.Count + 1 232 | 233 | // In this case, the current Entry is the head of a list that 234 | // we need to append to. We start searching for the tail of the list 235 | else 236 | listSearch &d hashCode key value bucketIdx 237 | 238 | 239 | let resize (d: byref>) = 240 | // Resize if our fill is >75% 241 | if d.Count > (d.Keys.Length >>> 2) * 3 then 242 | // if count > buckets.Length - 2 then 243 | let prevKeys = d.Keys 244 | let prevValues = d.Values 245 | let prevHashCodes = d.HashCodes 246 | let prevNexts = d.Nexts 247 | 248 | // Increase the size of the backing store 249 | let newSize = (prevKeys.Length <<< 1) 250 | d.Keys <- Array.zeroCreate newSize 251 | d.Values <- Array.zeroCreate newSize 252 | d.HashCodes <- Array.zeroCreate newSize 253 | d.Nexts <- Array.create newSize Next.empty 254 | d.BucketBitShift <- 32 - (BitOperations.TrailingZeroCount newSize) 255 | d.WrapAroundMask <- newSize - 1 256 | d.Count <- 0 257 | 258 | for i in 0..prevKeys.Length - 1 do 259 | if Next.isEntry prevNexts[i] then 260 | addEntry &d prevHashCodes[i] prevKeys[i] prevValues[i] 261 | 262 | let raiseKeyNotFound hashcode key = 263 | raise (KeyNotFoundException $"Missing Key: {key} Hashcode: {hashcode}") 264 | 265 | let rec searchLoop (d: inref>) (hashCode: int) (key: string) (bucketIdx: int) = 266 | let strRange = d.KeyRanges[bucketIdx] 267 | let keyChars = d.KeyChars.AsSpan(strRange.Start, strRange.Length) 268 | if hashCode = d.HashCodes[bucketIdx] && 269 | (key.AsSpan().SequenceEqual(keyChars)) then 270 | d.Values[bucketIdx] 271 | 272 | elif Next.isLast d.Nexts[bucketIdx] then 273 | raiseKeyNotFound hashCode key 274 | 275 | else 276 | let nextBucketIdx = (bucketIdx + (int d.Nexts[bucketIdx])) &&& d.WrapAroundMask 277 | searchLoop &d hashCode key nextBucketIdx 278 | 279 | open Helpers 280 | 281 | 282 | type StrStaticDict<'Value> internal (d: Data<'Value>) = 283 | 284 | inherit StaticDict() 285 | 286 | override _.Item 287 | with get (key: string) = 288 | let hashCode = strHashCode key 289 | let bucketIdx = computeBucketIndex d.BucketBitShift hashCode 290 | let strRange = d.KeyRanges[bucketIdx] 291 | let strChars = d.KeyChars.AsSpan(strRange.Start, strRange.Length) 292 | 293 | if hashCode = d.HashCodes[bucketIdx] && 294 | (key.AsSpan().SequenceEqual strChars) then 295 | d.Values[bucketIdx] 296 | 297 | elif Next.isLast d.Nexts[bucketIdx] then 298 | raiseKeyNotFound hashCode key 299 | 300 | else 301 | let nextBucketIdx = (bucketIdx + (int d.Nexts[bucketIdx])) &&& d.WrapAroundMask 302 | searchLoop &d hashCode key nextBucketIdx 303 | 304 | let create (entries: seq) = 305 | let uniqueKeys = HashSet() 306 | for key, _ in entries do 307 | uniqueKeys.Add key |> ignore 308 | 309 | let mutable acc : Acc = Acc<_,_>.init() 310 | 311 | for key, value in entries do 312 | let hashCode = strHashCode key 313 | addEntry &acc hashCode key value 314 | resize &acc 315 | 316 | let d = Data.ofAcc acc 317 | 318 | StrStaticDict d :> StaticDict 319 | -------------------------------------------------------------------------------- /FastDictionaryTest/SOA/ValueDict.fs: -------------------------------------------------------------------------------- 1 | module FastDictionaryTest.SOA.ValueDict 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.Numerics 6 | open FastDictionaryTest.SOA.Helpers 7 | 8 | #nowarn "9" "42" "51" 9 | 10 | module internal rec Helpers = 11 | 12 | [] 13 | type Acc<'Key, 'Value when 'Key : equality> = 14 | { 15 | mutable Count: int 16 | mutable Keys: 'Key[] 17 | mutable Values: 'Value[] 18 | mutable HashCodes: int[] 19 | mutable Nexts: byte[] 20 | mutable BucketBitShift: int 21 | mutable WrapAroundMask: int 22 | } 23 | static member init () = 24 | let initialCapacity = 4 25 | { 26 | Count = 0 27 | Keys = Array.zeroCreate 4 28 | Values = Array.zeroCreate 4 29 | HashCodes = Array.zeroCreate 4 30 | Nexts = Array.create 4 Next.empty 31 | BucketBitShift = 32 - (BitOperations.TrailingZeroCount initialCapacity) 32 | WrapAroundMask = initialCapacity - 1 33 | } 34 | 35 | [] 36 | type Data<'Key, 'Value when 'Key : equality> = 37 | { 38 | Keys: 'Key[] 39 | Values: 'Value[] 40 | HashCodes: int[] 41 | Nexts: byte[] 42 | BucketBitShift: int 43 | WrapAroundMask: int 44 | } 45 | static member ofAcc (acc: Acc<'Key, 'Value>) = 46 | { 47 | Keys = acc.Keys 48 | Values = acc.Values 49 | HashCodes = acc.HashCodes 50 | Nexts = acc.Nexts 51 | BucketBitShift = acc.BucketBitShift 52 | WrapAroundMask = acc.WrapAroundMask 53 | } 54 | 55 | let computeBucketIndex bucketBitShift (hashCode: int) = 56 | let hashProduct = (uint hashCode) * 2654435769u 57 | int (hashProduct >>> bucketBitShift) 58 | 59 | 60 | let isTail (d: inref>) (bucketIdx: int) = 61 | let homeIdx = computeBucketIndex d.BucketBitShift d.HashCodes[bucketIdx] 62 | bucketIdx <> homeIdx 63 | 64 | 65 | let getParentBucketIdxLoop (d: inref>) (childBucketIdx: int) (ancestorIdx: int) = 66 | let nextIdx = (ancestorIdx + int d.Nexts[ancestorIdx]) &&& d.WrapAroundMask 67 | if nextIdx = childBucketIdx then 68 | ancestorIdx 69 | else 70 | getParentBucketIdxLoop &d childBucketIdx nextIdx 71 | 72 | 73 | let getParentBucketIdx (d: inref>) (hashCode: int) (childBucketIdx: int) = 74 | let initialIdx = computeBucketIndex d.BucketBitShift hashCode 75 | getParentBucketIdxLoop &d childBucketIdx initialIdx 76 | 77 | 78 | let distanceFromParent (d: inref>) (bucketIdx: int) = 79 | let parentIdx = getParentBucketIdx &d d.HashCodes[bucketIdx] bucketIdx 80 | d.Nexts[parentIdx] 81 | 82 | 83 | let setBucket (d: inref>) next hashCode key value bucketIdx = 84 | d.Nexts[bucketIdx] <- next 85 | d.HashCodes[bucketIdx] <- hashCode 86 | d.Keys[bucketIdx] <- key 87 | d.Values[bucketIdx] <- value 88 | 89 | 90 | let removeFromList (d: inref>) (bucketIdx: int) = 91 | let parentBucketIdx = getParentBucketIdx &d d.HashCodes[bucketIdx] bucketIdx 92 | 93 | // If this is the Last element in a List, we just need to update the Parent's 94 | // NextOffset value to 0 and then re-add this entry 95 | if Next.isLast d.Nexts[bucketIdx] then 96 | d.Nexts[parentBucketIdx] <- 0uy 97 | 98 | // This element is in the middle of the list so we will remove it from the existing 99 | // list by having the Parent point to the former Grandchild, now child bucket 100 | else 101 | d.Nexts[parentBucketIdx] <- d.Nexts[parentBucketIdx] + d.Nexts[bucketIdx] 102 | 103 | 104 | let rec insertIntoNextEmptyBucket (d: byref>) (parentIdx: int) (hashCode: int) (key: 'Key) (value: 'Value) (offset: byte) (bucketIdx: int) = 105 | if bucketIdx < d.Keys.Length then 106 | if Next.isAvailable d.Nexts[bucketIdx] then 107 | setBucket &d Next.last hashCode key value bucketIdx 108 | d.Count <- d.Count + 1 109 | d.Nexts[parentIdx] <- offset 110 | // Test if this is an entry that is not at its home bucket and is 111 | // closer to its home than this new entry will be. This is Robin Hood hashing 112 | elif (isTail &d bucketIdx) && offset > (distanceFromParent &d bucketIdx) then 113 | // Need to take a temporary copy for the purpose of re-insertion 114 | let prevHashCode = d.HashCodes[bucketIdx] 115 | let prevKey = d.Keys[bucketIdx] 116 | let prevValue = d.Values[bucketIdx] 117 | removeFromList &d bucketIdx 118 | setBucket &d Next.last hashCode key value bucketIdx 119 | d.Nexts[parentIdx] <- offset 120 | addEntry &d prevHashCode prevKey prevValue 121 | else 122 | insertIntoNextEmptyBucket &d parentIdx hashCode key value (offset + 1uy) (bucketIdx + 1) 123 | 124 | else 125 | insertIntoNextEmptyBucket &d parentIdx hashCode key value offset 0 126 | 127 | 128 | let rec listSearch (d: byref>) (hashCode: int) (key: 'Key) (value: 'Value) (bucketIdx: int) = 129 | // Check if we have found an existing Entry for the Key 130 | // If we have, we want to update the value 131 | if d.HashCodes[bucketIdx] = hashCode && 132 | EqualityComparer.Default.Equals (key, d.Keys[bucketIdx]) then 133 | d.Values[bucketIdx] <- value 134 | 135 | // The Entry is not a match for Key so we need to check if we have come 136 | // to the end of the list. If we have, then we search for empty space 137 | // to add our new Key/Value and update the offset for the previous Last entry. 138 | elif Next.isLast d.Nexts[bucketIdx] then 139 | 140 | insertIntoNextEmptyBucket &d bucketIdx hashCode key value 1uy (bucketIdx + 1) 141 | 142 | // We are not at the end of the list so we compute the next BucketIdx and move 143 | // to the next Entry. We use a wrap around mask to ensure we don't go outside 144 | // the bounds of the array. 145 | else 146 | // Compute the next index which takes the wrap around logic into account 147 | let nextBucketIdx = (bucketIdx + (int d.Nexts[bucketIdx])) &&& d.WrapAroundMask 148 | listSearch &d hashCode key value nextBucketIdx 149 | 150 | 151 | let rec addEntry (d: byref>) (hashCode: int) (key: 'Key) (value: 'Value) = 152 | 153 | let bucketIdx = computeBucketIndex d.BucketBitShift hashCode 154 | // Check if bucket is Empty or a Tombstone 155 | if Next.isAvailable d.Nexts[bucketIdx] then 156 | setBucket &d Next.last hashCode key value bucketIdx 157 | d.Count <- d.Count + 1 158 | 159 | // If there is already an entry for this Key, overwrite the Value 160 | elif d.HashCodes[bucketIdx] = hashCode && 161 | EqualityComparer.Default.Equals (key, d.Keys[bucketIdx]) then 162 | d.Values[bucketIdx] <- value 163 | 164 | // Check if the current Entry is part of a chain for a different 165 | // BucketIdx and should therefore be evicted 166 | elif isTail &d bucketIdx then 167 | // Move the current entry out of this position 168 | let prevHashCode = d.HashCodes[bucketIdx] 169 | let prevKey = d.Keys[bucketIdx] 170 | let prevValue = d.Values[bucketIdx] 171 | removeFromList &d bucketIdx 172 | setBucket &d Next.last hashCode key value bucketIdx 173 | addEntry &d prevHashCode prevKey prevValue 174 | d.Count <- d.Count + 1 175 | 176 | // In this case, the current Entry is the head of a list that 177 | // we need to append to. We start searching for the tail of the list 178 | else 179 | listSearch &d hashCode key value bucketIdx 180 | 181 | 182 | let resize (d: byref>) = 183 | // Resize if our fill is >75% 184 | if d.Count > (d.Keys.Length >>> 2) * 3 then 185 | // if count > buckets.Length - 2 then 186 | let prevKeys = d.Keys 187 | let prevValues = d.Values 188 | let prevHashCodes = d.HashCodes 189 | let prevNexts = d.Nexts 190 | 191 | // Increase the size of the backing store 192 | let newSize = (prevKeys.Length <<< 1) 193 | d.Keys <- Array.zeroCreate newSize 194 | d.Values <- Array.zeroCreate newSize 195 | d.HashCodes <- Array.zeroCreate newSize 196 | d.Nexts <- Array.create newSize Next.empty 197 | d.BucketBitShift <- 32 - (BitOperations.TrailingZeroCount newSize) 198 | d.WrapAroundMask <- newSize - 1 199 | d.Count <- 0 200 | 201 | for i in 0..prevKeys.Length - 1 do 202 | if Next.isEntry prevNexts[i] then 203 | addEntry &d prevHashCodes[i] prevKeys[i] prevValues[i] 204 | 205 | let raiseKeyNotFound hashcode key = 206 | raise (KeyNotFoundException $"Missing Key: {key} Hashcode: {hashcode}") 207 | 208 | let rec searchLoop (d: inref>) (hashCode: int) (key: 'Key) (bucketIdx: int) = 209 | if hashCode = d.HashCodes[bucketIdx] && 210 | EqualityComparer.Default.Equals (key, d.Keys[bucketIdx]) then 211 | d.Values[bucketIdx] 212 | 213 | elif Next.isLast d.Nexts[bucketIdx] then 214 | raiseKeyNotFound hashCode key 215 | 216 | else 217 | let nextBucketIdx = (bucketIdx + (int d.Nexts[bucketIdx])) &&& d.WrapAroundMask 218 | searchLoop &d hashCode key nextBucketIdx 219 | 220 | open Helpers 221 | 222 | 223 | type RefStaticDict<'Key, 'Value when 'Key : equality> internal (d: Data<'Key, 'Value>) = 224 | 225 | inherit StaticDict<'Key, 'Value>() 226 | 227 | override _.Item 228 | with get (key: 'Key) = 229 | let hashCode = EqualityComparer.Default.GetHashCode key 230 | let bucketIdx = computeBucketIndex d.BucketBitShift hashCode 231 | 232 | if hashCode = d.HashCodes[bucketIdx] && 233 | EqualityComparer.Default.Equals (key, d.Keys[bucketIdx]) then 234 | d.Values[bucketIdx] 235 | 236 | elif Next.isLast d.Nexts[bucketIdx] then 237 | raiseKeyNotFound hashCode key 238 | 239 | else 240 | let nextBucketIdx = (bucketIdx + (int d.Nexts[bucketIdx])) &&& d.WrapAroundMask 241 | searchLoop &d hashCode key nextBucketIdx 242 | 243 | let create (entries: seq<'Key * 'Value>) = 244 | let uniqueKeys = HashSet() 245 | for key, _ in entries do 246 | uniqueKeys.Add key |> ignore 247 | 248 | let mutable acc : Acc<'Key, 'Value> = Acc<_,_>.init() 249 | 250 | for key, value in entries do 251 | let hashCode = EqualityComparer.Default.GetHashCode key 252 | addEntry &acc hashCode key value 253 | resize &acc 254 | 255 | let d = Data.ofAcc acc 256 | RefStaticDict d :> StaticDict<'Key, 'Value> 257 | -------------------------------------------------------------------------------- /FastDictionaryTest/ZeroAllocation.fs: -------------------------------------------------------------------------------- 1 | namespace FastDictionaryTest.ZeroAllocation 2 | 3 | open System.Collections.Generic 4 | 5 | module private Helpers = 6 | 7 | type Entry<'Key, 'Value> = 8 | { 9 | Key: 'Key 10 | Value: 'Value 11 | } 12 | 13 | open Helpers 14 | 15 | 16 | type Dictionary<'Key, 'Value when 'Key : equality> (entries: seq<'Key * 'Value>) = 17 | // Track the number of items in Dictionary for resize 18 | let mutable count = 0 19 | // Create the Buckets with some initial capacity 20 | let mutable buckets : list>[] = Array.create 4 [] 21 | // Map a 'Key to the bucket we expect it to be in 22 | let computeBucketIndex (key: 'Key) = 23 | // Mask off top bit to ensure positive integer 24 | let h = (EqualityComparer.Default.GetHashCode key) &&& 0x7FFF_FFFF 25 | let bucketIdx = h % buckets.Length 26 | bucketIdx 27 | 28 | 29 | let addEntry (key: 'Key) (value: 'Value) = 30 | 31 | let rec loop (acc: Entry<_,_> list) (remaining: Entry<_,_> list) = 32 | match remaining with 33 | | [] -> 34 | let newEntry = { Key = key; Value = value } 35 | // Increment the count since we have added an entry 36 | count <- count + 1 37 | newEntry :: acc 38 | | head::tail -> 39 | if EqualityComparer.Default.Equals (head.Key, key) then 40 | // Do not increment the count in this case because we are just overwriting an 41 | // exising value 42 | let updatedEntry = { head with Value = value } 43 | (updatedEntry::acc) @ tail 44 | else 45 | loop (head::acc) tail 46 | 47 | let bucketIdx = computeBucketIndex key 48 | let bucket = buckets[bucketIdx] 49 | let updatedBucket = loop [] bucket 50 | buckets[bucketIdx] <- updatedBucket 51 | 52 | 53 | let getValue (key: 'Key) = 54 | 55 | let rec loop (entry: Entry<_,_> list) = 56 | match entry with 57 | | [] -> 58 | raise (KeyNotFoundException()) 59 | | head::tail -> 60 | if EqualityComparer.Default.Equals (head.Key, key) then 61 | head.Value 62 | else 63 | loop tail 64 | 65 | let bucketIdx = computeBucketIndex key 66 | loop buckets[bucketIdx] 67 | 68 | 69 | let resize () = 70 | // Only resize when the fill is > 75% 71 | if count > (buckets.Length >>> 2) * 3 then 72 | let oldBuckets = buckets 73 | 74 | // Increase the size of the backing store 75 | buckets <- Array.create (buckets.Length <<< 1) [] 76 | count <- 0 77 | 78 | for bucket in oldBuckets do 79 | for entry in bucket do 80 | addEntry entry.Key entry.Value 81 | 82 | do 83 | for k, v in entries do 84 | addEntry k v 85 | resize() 86 | 87 | new () = Dictionary([]) 88 | 89 | member d.Item 90 | with get (key: 'Key) = getValue key 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fast Dictionary in F# 2 | 3 | This is a repo of my experimentation with writing a Static Dictionary (i.e. ReadOnlyDictionary) with lookup performance that is as fast as possible, with reasonable memory usage. The current fastest version is a Byte-List Separate Chaining with Robin Hood hashing to attempt to keep Keys that hash to the same bucket are as close as possible. 4 | 5 | Most of the ideas used here come from a CppCon talk given in 2018 by [Malte Skarupke](https://probablydance.com/) titled [C++Now 2018: You Can Do Better than std::unordered_map: New Improvements to Hash Table Performance](https://www.youtube.com/watch?v=M2fKMP47slQ&t=136s). This repo is my attempt to implement several of the ideas in F# to see what performance could be achieved. 6 | 7 | ## Results 8 | 9 | Here are the results for the current best performing `Dictionary` version. 10 | 11 | ``` 12 | // * Summary * 13 | 14 | BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22621.1702) 15 | 13th Gen Intel Core i9-13900K, 1 CPU, 32 logical and 24 physical cores 16 | .NET SDK=8.0.100-preview.3.23178.7 17 | [Host] : .NET 8.0.0 (8.0.23.17408), X64 RyuJIT AVX2 DEBUG 18 | DefaultJob : .NET 8.0.0 (8.0.23.17408), X64 RyuJIT AVX2 19 | ``` 20 | 21 | | Method | KeyType | KeyCount | Mean | Error | StdDev | Code Size | BranchInstructions/Op | CacheMisses/Op | BranchMispredictions/Op | Allocated | 22 | |-------------------- |-------- |--------- |----------:|---------:|---------:|----------:|----------------------:|---------------:|------------------------:|----------:| 23 | | Dictionary | Int | 10 | 55.26 us | 0.584 us | 0.546 us | 1,566 B | 113,966 | 86 | 2,983 | - | 24 | | 'Frozen Dictionary' | Int | 10 | 111.99 us | 0.294 us | 0.260 us | 1,247 B | 338,890 | 25 | 8,779 | - | 25 | | 'Static Dict' | Int | 10 | 21.47 us | 0.147 us | 0.130 us | 1,129 B | 75,423 | 20 | 974 | - | 26 | | Dictionary | Int | 100 | 66.01 us | 0.505 us | 0.473 us | 1,566 B | 115,166 | 39 | 3,580 | - | 27 | | 'Frozen Dictionary' | Int | 100 | 23.14 us | 0.241 us | 0.201 us | 1,247 B | 111,234 | 4 | 344 | - | 28 | | 'Static Dict' | Int | 100 | 16.45 us | 0.314 us | 0.374 us | 1,129 B | 69,543 | 3 | 470 | - | 29 | | Dictionary | Int | 1_000 | 66.22 us | 0.205 us | 0.182 us | 1,566 B | 114,626 | 38 | 3,454 | - | 30 | | 'Frozen Dictionary' | Int | 1_000 | 33.35 us | 0.368 us | 0.326 us | 1,247 B | 114,353 | 33 | 880 | - | 31 | | 'Static Dict' | Int | 1_000 | 21.54 us | 0.132 us | 0.117 us | 1,129 B | 72,319 | 10 | 823 | - | 32 | | Dictionary | Int | 10_000 | 109.51 us | 1.246 us | 1.104 us | 1,566 B | 116,149 | 2,081 | 3,761 | - | 33 | | 'Frozen Dictionary' | Int | 10_000 | 67.62 us | 1.309 us | 1.161 us | 1,247 B | 114,929 | 2,527 | 778 | - | 34 | | 'Static Dict' | Int | 10_000 | 44.55 us | 0.871 us | 0.855 us | 1,129 B | 74,966 | 945 | 1,476 | - | 35 | | Dictionary | String | 10 | 170.37 us | 1.455 us | 1.215 us | 1,551 B | 444,723 | 27 | 8,670 | - | 36 | | 'Frozen Dictionary' | String | 10 | 130.26 us | 1.043 us | 0.925 us | 1,247 B | 417,279 | 21 | 7,581 | - | 37 | | 'Static Dict' | String | 10 | 90.23 us | 0.687 us | 0.536 us | 1,129 B | 325,809 | 15 | 3,841 | - | 38 | | Dictionary | String | 100 | 185.48 us | 2.765 us | 2.587 us | 1,551 B | 446,424 | 77 | 9,919 | - | 39 | | 'Frozen Dictionary' | String | 100 | 98.59 us | 1.119 us | 1.047 us | 1,247 B | 376,587 | 59 | 3,083 | - | 40 | | 'Static Dict' | String | 100 | 92.69 us | 1.137 us | 1.063 us | 1,129 B | 320,451 | 29 | 3,539 | - | 41 | | Dictionary | String | 1_000 | 247.58 us | 3.099 us | 2.898 us | 1,551 B | 447,108 | 1,523 | 9,563 | - | 42 | | 'Frozen Dictionary' | String | 1_000 | 153.22 us | 1.506 us | 1.335 us | 1,247 B | 357,688 | 1,861 | 2,888 | - | 43 | | 'Static Dict' | String | 1_000 | 137.71 us | 1.561 us | 1.460 us | 1,129 B | 325,975 | 646 | 3,944 | - | 44 | | Dictionary | String | 10_000 | 365.60 us | 6.803 us | 6.031 us | 1,551 B | 450,305 | 6,617 | 10,426 | - | 45 | | 'Frozen Dictionary' | String | 10_000 | 245.21 us | 2.819 us | 2.354 us | 1,247 B | 383,241 | 7,225 | 4,193 | - | 46 | | 'Static Dict' | String | 10_000 | 197.43 us | 1.163 us | 1.031 us | 1,129 B | 332,395 | 6,683 | 5,179 | - | 47 | 48 | ## Running Benchmarks 49 | 50 | To run the benchmakrs for yourself, open a Terminal instance with Admin privledges so that you can get the hardware counter results. Navigate your terminal to the `FastDictionaryTest.Benchmarks` direction. The following command you will want to use is: 51 | 52 | ``` 53 | dotnet run -c Release --task benchmark --suite 54 | ``` 55 | 56 | The possible values for `` are: 57 | - Baseline 58 | - Naive 59 | - ZeroAllocation 60 | - FibonacciHashing 61 | - CacheEquality 62 | - FastTypeBranch 63 | - EmbeddedHead 64 | - LinearProbing 65 | - RobinHood 66 | - RobinhoodEviction 67 | - ByteList 68 | - ByteListRobinHood 69 | - ByteListRobinHoodVec128 70 | - ByteListRobinHoodInline 71 | - CStyle 72 | - SOA -------------------------------------------------------------------------------- /presentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | marp: true 3 | theme: uncover 4 | class: invert 5 | math: mathjax 6 | --- 7 | 8 | # Writng a Fast Dictionary 9 | 10 | By: Matthew Crews 11 | 12 | ___ 13 | 14 | ## Why? 15 | 16 | * Sometimes you need a Dictionary 17 | * Learning Opprotunity 18 | * 19 | 20 | --- 21 | 22 | ## Code 23 | 24 | ```fsharp 25 | let func a b = a + b 26 | ``` 27 | 28 | --- 29 | 30 | ## Math! 31 | 32 | $\mathcal{0}(n\log{n})$ 33 | 34 | OR, multi-line expression. 35 | 36 | $$ 37 | \begin{align} 38 | x &= 1 + \frac{1}{2} \\ 39 | &= 1.5 40 | \end{align} 41 | $$ --------------------------------------------------------------------------------