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