Swap Dict implementation to ankerl dense unordered

ankerl::dense_unordered is a very fast hash map that is built to be an index map.
This enables extra optimizations compared to just wrapping a regular hash map.
As such, I think this map is very well suited for our index map impl in Roc.
I also think this dictionary implementation is simpler overall.
On top of that, this removes the need for SIMD instructions for peak performance.

Benchmarks of the C++ version and other C++ hash maps are here: https://martin.ankerl.com/2022/08/27/hashmap-bench-01/
Though this has clear bias of being written by the author of ankerl::dense_unordered,
the results all look correct and the benchmarks thorough.
This commit is contained in:
Brendan Hansknecht 2023-12-06 21:06:44 -08:00
parent eadd0e82ce
commit 51ec4311b5
No known key found for this signature in database
GPG Key ID: A199D0660F95F948
3 changed files with 431 additions and 363 deletions

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,8 @@ interface Set
exposes [
Set,
empty,
withCapacity,
reserve,
single,
walk,
walkUntil,
@ -45,7 +47,7 @@ Set k := Dict.Dict k {} where k implements Hash & Eq
},
]
isEq : Set k, Set k -> Bool where k implements Hash & Eq
isEq : Set k, Set k -> Bool
isEq = \xs, ys ->
if len xs != len ys then
Bool.false
@ -56,7 +58,7 @@ isEq = \xs, ys ->
else
Break Bool.false
hashSet : hasher, Set k -> hasher where k implements Hash & Eq, hasher implements Hasher
hashSet : hasher, Set k -> hasher where hasher implements Hasher
hashSet = \hasher, @Set inner -> Hash.hash hasher inner
toInspectorSet : Set k -> Inspector f where k implements Inspect & Hash & Eq, f implements InspectFormatter
@ -74,13 +76,18 @@ toInspectorSet = \set ->
empty : {} -> Set *
empty = \{} -> @Set (Dict.empty {})
## Return a dictionary with space allocated for a number of entries. This
## Return a set with space allocated for a number of entries. This
## may provide a performance optimization if you know how many entries will be
## inserted.
withCapacity : Nat -> Set *
withCapacity = \cap ->
@Set (Dict.withCapacity cap)
# Enlarge the set for at least capacity additional elements
reserve : Set k, Nat -> Set k
reserve = \@Set dict, requested ->
@Set (Dict.reserve dict requested)
## Creates a new `Set` with a single value.
## ```
## singleItemSet = Set.single "Apple"
@ -88,7 +95,7 @@ withCapacity = \cap ->
##
## expect countValues == 1
## ```
single : k -> Set k where k implements Hash & Eq
single : k -> Set k
single = \key ->
Dict.single key {} |> @Set
@ -104,7 +111,7 @@ single = \key ->
##
## expect countValues == 3
## ```
insert : Set k, k -> Set k where k implements Hash & Eq
insert : Set k, k -> Set k
insert = \@Set dict, key ->
Dict.insert dict key {} |> @Set
@ -189,7 +196,7 @@ expect
## expect has10 == Bool.false
## expect has20 == Bool.true
## ```
remove : Set k, k -> Set k where k implements Hash & Eq
remove : Set k, k -> Set k
remove = \@Set dict, key ->
Dict.remove dict key |> @Set
@ -208,7 +215,7 @@ remove = \@Set dict, key ->
## expect hasApple == Bool.true
## expect hasBanana == Bool.false
## ```
contains : Set k, k -> Bool where k implements Hash & Eq
contains : Set k, k -> Bool
contains = \@Set dict, key ->
Dict.contains dict key
@ -221,7 +228,7 @@ contains = \@Set dict, key ->
##
## expect Set.toList numbers == values
## ```
toList : Set k -> List k where k implements Hash & Eq
toList : Set k -> List k
toList = \@Set dict ->
Dict.keys dict
@ -235,7 +242,7 @@ toList = \@Set dict ->
##
## expect Set.fromList [Pear, Apple, Banana] == values
## ```
fromList : List k -> Set k where k implements Hash & Eq
fromList : List k -> Set k
fromList = \list ->
list
|> List.map \k -> (k, {})
@ -252,7 +259,7 @@ fromList = \list ->
##
## expect Set.union set1 set2 == Set.fromList [Left, Right]
## ```
union : Set k, Set k -> Set k where k implements Hash & Eq
union : Set k, Set k -> Set k
union = \@Set dict1, @Set dict2 ->
Dict.insertAll dict1 dict2 |> @Set
@ -265,7 +272,7 @@ union = \@Set dict1, @Set dict2 ->
##
## expect Set.intersection set1 set2 == Set.single Left
## ```
intersection : Set k, Set k -> Set k where k implements Hash & Eq
intersection : Set k, Set k -> Set k
intersection = \@Set dict1, @Set dict2 ->
Dict.keepShared dict1 dict2 |> @Set
@ -279,7 +286,7 @@ intersection = \@Set dict1, @Set dict2 ->
##
## expect Set.difference first second == Set.fromList [Up, Down]
## ```
difference : Set k, Set k -> Set k where k implements Hash & Eq
difference : Set k, Set k -> Set k
difference = \@Set dict1, @Set dict2 ->
Dict.removeAll dict1 dict2 |> @Set
@ -302,14 +309,14 @@ difference = \@Set dict1, @Set dict2 ->
##
## expect result == 2
## ```
walk : Set k, state, (state, k -> state) -> state where k implements Hash & Eq
walk : Set k, state, (state, k -> state) -> state
walk = \@Set dict, state, step ->
Dict.walk dict state (\s, k, _ -> step s k)
## Convert each value in the set to something new, by calling a conversion
## function on each of them which receives the old value. Then return a
## new set containing the converted values.
map : Set a, (a -> b) -> Set b where a implements Hash & Eq, b implements Hash & Eq
map : Set a, (a -> b) -> Set b
map = \set, transform ->
init = withCapacity (capacity set)
@ -321,7 +328,7 @@ map = \set, transform ->
## (using [Set.union]) into one set.
##
## You may know a similar function named `concatMap` in other languages.
joinMap : Set a, (a -> Set b) -> Set b where a implements Hash & Eq, b implements Hash & Eq
joinMap : Set a, (a -> Set b) -> Set b
joinMap = \set, transform ->
init = withCapacity (capacity set) # Might be a pessimization
@ -343,7 +350,7 @@ joinMap = \set, transform ->
##
## expect result == FoundTheAnswer
## ```
walkUntil : Set k, state, (state, k -> [Continue state, Break state]) -> state where k implements Hash & Eq
walkUntil : Set k, state, (state, k -> [Continue state, Break state]) -> state
walkUntil = \@Set dict, state, step ->
Dict.walkUntil dict state (\s, k, _ -> step s k)

View File

@ -1486,6 +1486,7 @@ define_builtins! {
26 DICT_JOINMAP: "joinMap"
27 DICT_KEEP_IF: "keepIf"
28 DICT_DROP_IF: "dropIf"
29 DICT_RESERVE: "reserve"
}
9 SET: "Set" => {
0 SET_SET: "Set" exposed_type=true // the Set.Set type alias
@ -1510,6 +1511,8 @@ define_builtins! {
19 SET_JOIN_MAP: "joinMap"
20 SET_KEEP_IF: "keepIf"
21 SET_DROP_IF: "dropIf"
22 SET_WITH_CAPACITY: "withCapacity"
23 SET_RESERVE: "reserve"
}
10 BOX: "Box" => {
0 BOX_BOX_TYPE: "Box" exposed_apply_type=true // the Box.Box opaque type