From ca87dd44555f9878c8725daef5928587283c08f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?linus=20bj=C3=B6rnstam?= Date: Thu, 16 Apr 2026 19:54:39 +0200 Subject: [PATCH 01/22] Move to org readme --- PersistentMap/Readme.md | 89 ----------------------------------------- 1 file changed, 89 deletions(-) delete mode 100644 PersistentMap/Readme.md diff --git a/PersistentMap/Readme.md b/PersistentMap/Readme.md deleted file mode 100644 index 81517f1..0000000 --- a/PersistentMap/Readme.md +++ /dev/null @@ -1,89 +0,0 @@ -# PersistentMap - -A high-performance, persistent (immutable) B-Tree map implementation for .NET, designed for scenarios requiring efficient snapshots and transactional updates. - -## Features - -* **Persistent (Immutable) by Default:** Operations on `PersistentMap` return a new instance, sharing structure with the previous version. This makes it trivial to keep historical snapshots or implement undo/redo. -* **Transient (Mutable) Phase:** Supports a `TransientMap` for high-performance batch updates. This allows you to perform multiple mutations (Set/Remove) without the overhead of allocating new path nodes for every single operation, similar to Clojure's transients or Scala's builders. -* **Optimized B-Tree:** Uses a B-Tree structure optimized for modern CPU caches and SIMD instructions (AVX2/AVX512) for key prefix scanning. -* **Custom Key Strategies:** Flexible `IKeyStrategy` interface allows defining custom comparison and prefix generation logic (e.g., for strings, integers, or custom types). - -## Usage - -### When should I use this? - -Never, probably. This was just a fun little project. If you want a really fast immutable sorted map you should consider it. Despite this map being faster than LanguageExt.HashMap for some key types, you should definitely use that if you don't need a sorted collection. It is well tested and does not have any problems key collisions, which will slow this map down by a lot. - -It is also faster for just about every key that isn't a more-than-30-char-with-few-common-prefixes string. - - -### Basic Persistent Operations - -```csharp -using PersistentMap; - -// 1. Create an empty map with a strategy (e.g., for strings) -var map0 = PersistentMap.Empty(new UnicodeStrategy()); - -// 2. Add items (returns a new map) -var map1 = map0.Set("key1", "value1"); -var map2 = map1.Set("key2", "value2"); - -// map0 is still empty -// map1 has "key1" -// map2 has "key1" and "key2" - -// 3. Remove items -var map3 = map2.Remove("key1"); -// map3 has only "key2" -``` - -### Efficient Batch Updates (Transients) - -When you need to perform many updates at once (e.g., initial load, bulk import), use `ToTransient()` to switch to a mutable mode, and `ToPersistent()` to seal it back. - -```csharp -// 1. Start with a persistent map -var initialMap = PersistentMap.Empty(new IntStrategy()); - -// 2. Convert to transient (mutable) -var transientMap = initialMap.ToTransient(); - -// 3. Perform batch mutations (in-place, fast) -for (int i = 0; i < 10000; i++) -{ - transientMap.Set(i, $"Value {i}"); -} - -// 4. Convert back to persistent (immutable) -// This "seals" the current state. The transient map rolls its transaction ID, -// so subsequent writes to 'transientMap' won't affect 'finalMap'. -var finalMap = transientMap.ToPersistent(); -``` - -## Key Strategies - -The library uses `IKeyStrategy` to handle key comparisons and optimization. - -* **`UnicodeStrategy`**: Optimized for `string` keys. Uses SIMD to pack the first 8 bytes of the string into a `long` prefix for fast scanning. -* **`IntStrategy`**: Optimized for `int` keys. - -You can implement `IKeyStrategy` for your own types. - -## Performance Notes - -* **Structure Sharing:** `PersistentMap` shares unchanged nodes between versions, minimizing memory overhead. -* **Transients:** `TransientMap` uses an internal `OwnerId` (transaction ID) to track ownership. Nodes created within the same transaction are mutated in-place. `ToPersistent()` ensures that any future writes to the transient map will copy nodes instead of mutating the shared ones. This leads to very fast building times compared to using persistent updates. -* **SIMD:** The `PrefixScanner` uses AVX2/AVX512 (if available) to scan node keys efficiently. - -### Key strategies - -For string keys, the prefix optimization lets the library have really fast lookups. For mostly-ascii string keys, we are faster than most persistent hash maps once you pass a certain key size or collection size depending on implementation strategy. The B tree is shallow and has fewer cache misses, meaning it can be faster than either deep trees or hash maps despite doing linear searches. - -## Project Structure - -* `PersistentMap.cs`: The main immutable map implementation. -* `TransientMap.cs`: The mutable builder for batch operations. -* `Nodes.cs`: Internal B-Tree node definitions. -* `KeyStrategies.cs`: implementations of key comparison and prefixing. From b96e9671c8b5c9fb379a56960ce13484de7ca520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?linus=20bj=C3=B6rnstam?= Date: Fri, 17 Apr 2026 13:38:23 +0200 Subject: [PATCH 02/22] Revert "Move to org readme" This reverts commit ca87dd44555f9878c8725daef5928587283c08f7. --- PersistentMap/Readme.md | 89 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 PersistentMap/Readme.md diff --git a/PersistentMap/Readme.md b/PersistentMap/Readme.md new file mode 100644 index 0000000..81517f1 --- /dev/null +++ b/PersistentMap/Readme.md @@ -0,0 +1,89 @@ +# PersistentMap + +A high-performance, persistent (immutable) B-Tree map implementation for .NET, designed for scenarios requiring efficient snapshots and transactional updates. + +## Features + +* **Persistent (Immutable) by Default:** Operations on `PersistentMap` return a new instance, sharing structure with the previous version. This makes it trivial to keep historical snapshots or implement undo/redo. +* **Transient (Mutable) Phase:** Supports a `TransientMap` for high-performance batch updates. This allows you to perform multiple mutations (Set/Remove) without the overhead of allocating new path nodes for every single operation, similar to Clojure's transients or Scala's builders. +* **Optimized B-Tree:** Uses a B-Tree structure optimized for modern CPU caches and SIMD instructions (AVX2/AVX512) for key prefix scanning. +* **Custom Key Strategies:** Flexible `IKeyStrategy` interface allows defining custom comparison and prefix generation logic (e.g., for strings, integers, or custom types). + +## Usage + +### When should I use this? + +Never, probably. This was just a fun little project. If you want a really fast immutable sorted map you should consider it. Despite this map being faster than LanguageExt.HashMap for some key types, you should definitely use that if you don't need a sorted collection. It is well tested and does not have any problems key collisions, which will slow this map down by a lot. + +It is also faster for just about every key that isn't a more-than-30-char-with-few-common-prefixes string. + + +### Basic Persistent Operations + +```csharp +using PersistentMap; + +// 1. Create an empty map with a strategy (e.g., for strings) +var map0 = PersistentMap.Empty(new UnicodeStrategy()); + +// 2. Add items (returns a new map) +var map1 = map0.Set("key1", "value1"); +var map2 = map1.Set("key2", "value2"); + +// map0 is still empty +// map1 has "key1" +// map2 has "key1" and "key2" + +// 3. Remove items +var map3 = map2.Remove("key1"); +// map3 has only "key2" +``` + +### Efficient Batch Updates (Transients) + +When you need to perform many updates at once (e.g., initial load, bulk import), use `ToTransient()` to switch to a mutable mode, and `ToPersistent()` to seal it back. + +```csharp +// 1. Start with a persistent map +var initialMap = PersistentMap.Empty(new IntStrategy()); + +// 2. Convert to transient (mutable) +var transientMap = initialMap.ToTransient(); + +// 3. Perform batch mutations (in-place, fast) +for (int i = 0; i < 10000; i++) +{ + transientMap.Set(i, $"Value {i}"); +} + +// 4. Convert back to persistent (immutable) +// This "seals" the current state. The transient map rolls its transaction ID, +// so subsequent writes to 'transientMap' won't affect 'finalMap'. +var finalMap = transientMap.ToPersistent(); +``` + +## Key Strategies + +The library uses `IKeyStrategy` to handle key comparisons and optimization. + +* **`UnicodeStrategy`**: Optimized for `string` keys. Uses SIMD to pack the first 8 bytes of the string into a `long` prefix for fast scanning. +* **`IntStrategy`**: Optimized for `int` keys. + +You can implement `IKeyStrategy` for your own types. + +## Performance Notes + +* **Structure Sharing:** `PersistentMap` shares unchanged nodes between versions, minimizing memory overhead. +* **Transients:** `TransientMap` uses an internal `OwnerId` (transaction ID) to track ownership. Nodes created within the same transaction are mutated in-place. `ToPersistent()` ensures that any future writes to the transient map will copy nodes instead of mutating the shared ones. This leads to very fast building times compared to using persistent updates. +* **SIMD:** The `PrefixScanner` uses AVX2/AVX512 (if available) to scan node keys efficiently. + +### Key strategies + +For string keys, the prefix optimization lets the library have really fast lookups. For mostly-ascii string keys, we are faster than most persistent hash maps once you pass a certain key size or collection size depending on implementation strategy. The B tree is shallow and has fewer cache misses, meaning it can be faster than either deep trees or hash maps despite doing linear searches. + +## Project Structure + +* `PersistentMap.cs`: The main immutable map implementation. +* `TransientMap.cs`: The mutable builder for batch operations. +* `Nodes.cs`: Internal B-Tree node definitions. +* `KeyStrategies.cs`: implementations of key comparison and prefixing. From 0bb8daca1a8f19e128a8c8934fdb9ca00d38e4ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?linus=20bj=C3=B6rnstam?= Date: Fri, 17 Apr 2026 13:39:08 +0200 Subject: [PATCH 03/22] Reapply "Move to org readme" This reverts commit b96e9671c8b5c9fb379a56960ce13484de7ca520. --- PersistentMap/Readme.md | 89 ----------------------------------------- 1 file changed, 89 deletions(-) delete mode 100644 PersistentMap/Readme.md diff --git a/PersistentMap/Readme.md b/PersistentMap/Readme.md deleted file mode 100644 index 81517f1..0000000 --- a/PersistentMap/Readme.md +++ /dev/null @@ -1,89 +0,0 @@ -# PersistentMap - -A high-performance, persistent (immutable) B-Tree map implementation for .NET, designed for scenarios requiring efficient snapshots and transactional updates. - -## Features - -* **Persistent (Immutable) by Default:** Operations on `PersistentMap` return a new instance, sharing structure with the previous version. This makes it trivial to keep historical snapshots or implement undo/redo. -* **Transient (Mutable) Phase:** Supports a `TransientMap` for high-performance batch updates. This allows you to perform multiple mutations (Set/Remove) without the overhead of allocating new path nodes for every single operation, similar to Clojure's transients or Scala's builders. -* **Optimized B-Tree:** Uses a B-Tree structure optimized for modern CPU caches and SIMD instructions (AVX2/AVX512) for key prefix scanning. -* **Custom Key Strategies:** Flexible `IKeyStrategy` interface allows defining custom comparison and prefix generation logic (e.g., for strings, integers, or custom types). - -## Usage - -### When should I use this? - -Never, probably. This was just a fun little project. If you want a really fast immutable sorted map you should consider it. Despite this map being faster than LanguageExt.HashMap for some key types, you should definitely use that if you don't need a sorted collection. It is well tested and does not have any problems key collisions, which will slow this map down by a lot. - -It is also faster for just about every key that isn't a more-than-30-char-with-few-common-prefixes string. - - -### Basic Persistent Operations - -```csharp -using PersistentMap; - -// 1. Create an empty map with a strategy (e.g., for strings) -var map0 = PersistentMap.Empty(new UnicodeStrategy()); - -// 2. Add items (returns a new map) -var map1 = map0.Set("key1", "value1"); -var map2 = map1.Set("key2", "value2"); - -// map0 is still empty -// map1 has "key1" -// map2 has "key1" and "key2" - -// 3. Remove items -var map3 = map2.Remove("key1"); -// map3 has only "key2" -``` - -### Efficient Batch Updates (Transients) - -When you need to perform many updates at once (e.g., initial load, bulk import), use `ToTransient()` to switch to a mutable mode, and `ToPersistent()` to seal it back. - -```csharp -// 1. Start with a persistent map -var initialMap = PersistentMap.Empty(new IntStrategy()); - -// 2. Convert to transient (mutable) -var transientMap = initialMap.ToTransient(); - -// 3. Perform batch mutations (in-place, fast) -for (int i = 0; i < 10000; i++) -{ - transientMap.Set(i, $"Value {i}"); -} - -// 4. Convert back to persistent (immutable) -// This "seals" the current state. The transient map rolls its transaction ID, -// so subsequent writes to 'transientMap' won't affect 'finalMap'. -var finalMap = transientMap.ToPersistent(); -``` - -## Key Strategies - -The library uses `IKeyStrategy` to handle key comparisons and optimization. - -* **`UnicodeStrategy`**: Optimized for `string` keys. Uses SIMD to pack the first 8 bytes of the string into a `long` prefix for fast scanning. -* **`IntStrategy`**: Optimized for `int` keys. - -You can implement `IKeyStrategy` for your own types. - -## Performance Notes - -* **Structure Sharing:** `PersistentMap` shares unchanged nodes between versions, minimizing memory overhead. -* **Transients:** `TransientMap` uses an internal `OwnerId` (transaction ID) to track ownership. Nodes created within the same transaction are mutated in-place. `ToPersistent()` ensures that any future writes to the transient map will copy nodes instead of mutating the shared ones. This leads to very fast building times compared to using persistent updates. -* **SIMD:** The `PrefixScanner` uses AVX2/AVX512 (if available) to scan node keys efficiently. - -### Key strategies - -For string keys, the prefix optimization lets the library have really fast lookups. For mostly-ascii string keys, we are faster than most persistent hash maps once you pass a certain key size or collection size depending on implementation strategy. The B tree is shallow and has fewer cache misses, meaning it can be faster than either deep trees or hash maps despite doing linear searches. - -## Project Structure - -* `PersistentMap.cs`: The main immutable map implementation. -* `TransientMap.cs`: The mutable builder for batch operations. -* `Nodes.cs`: Internal B-Tree node definitions. -* `KeyStrategies.cs`: implementations of key comparison and prefixing. From 662843116ea597a3f89a717b5115ea408f1ede66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?linus=20bj=C3=B6rnstam?= Date: Fri, 17 Apr 2026 15:05:05 +0200 Subject: [PATCH 04/22] updated readme --- PersistentMap/Readme.org | 140 ++++++++++++++++++ .../AgainstLanguageExt/integerBenchmarks.cs | 39 ++--- 2 files changed, 160 insertions(+), 19 deletions(-) diff --git a/PersistentMap/Readme.org b/PersistentMap/Readme.org index 46352d3..6fe43a3 100644 --- a/PersistentMap/Readme.org +++ b/PersistentMap/Readme.org @@ -107,6 +107,146 @@ var onlyInA = mapA.Except(mapB); var symmetricDiff = mapA.SymmetricExcept(mapB); #+end_src +** Benchmarks +This is going to be all over the place, but here is a small comparison to other immutable sequences. Due to how the prefix optimization works, this persistent map will be the absolutely most performant when there is high entropy in the first 8 bytes of the key. The following is pretty much the best scenario we can have since we probably only look at the first 8 characters (this is for reading a value). + +#+begin_src +| Method | CollectionSize | KeySize | Mean | Error | StdDev | Median | Gen0 | Allocated | +|-----------------|----------------|----------|--------------:|-------------:|-------------:|--------------:|-----------:|----------:| +| PersistentMap | **1024** | **10** | **25.61 ns** | **0.479 ns** | **0.425 ns** | **25.56 ns** | **0.0043** | **72 B** | +| Sys.Sorted | 1024 | 10 | 153.18 ns | 1.605 ns | 1.423 ns | 153.14 ns | - | - | +| LangExt.HashMap | 1024 | 10 | 24.80 ns | 0.073 ns | 0.065 ns | 24.79 ns | - | - | +| LangExtSorted | 1024 | 10 | 176.90 ns | 1.196 ns | 1.061 ns | 176.48 ns | - | - | +| PersistentMap | **1024** | **100** | **26.43 ns** | **0.245 ns** | **0.217 ns** | **26.35 ns** | **0.0043** | **72 B** | +| SysSorted | 1024 | 100 | 154.77 ns | 1.977 ns | 1.849 ns | 153.74 ns | - | - | +| LangExt.HashMap | 1024 | 100 | 66.30 ns | 0.054 ns | 0.051 ns | 66.31 ns | - | - | +| LangExtSorted | 1024 | 100 | 177.28 ns | 1.516 ns | 1.344 ns | 177.14 ns | - | - | +| PersistentMap | **1024** | **1000** | **26.17 ns** | **0.480 ns** | **0.449 ns** | **26.07 ns** | **0.0043** | **72 B** | +| SysSorted | 1024 | 1000 | 155.68 ns | 1.229 ns | 1.090 ns | 155.26 ns | - | - | +| LangExt.HashMap | 1024 | 1000 | 491.97 ns | 0.927 ns | 0.774 ns | 492.14 ns | - | - | +| LangExtSorted | 1024 | 1000 | 181.58 ns | 1.030 ns | 0.913 ns | 181.54 ns | - | - | +| PersistentMap | **131072** | **10** | **109.34 ns** | **2.200 ns** | **5.795 ns** | **107.97 ns** | **0.0072** | **120 B** | +| SysSorted | 131072 | 10 | 460.22 ns | 9.101 ns | 12.457 ns | 453.61 ns | - | - | +| LangExt.HashMap | 131072 | 10 | 60.35 ns | 1.346 ns | 3.904 ns | 59.40 ns | - | - | +| LangExtSorted | 131072 | 10 | 555.17 ns | 15.764 ns | 46.233 ns | 539.79 ns | - | - | +| PersistentMap | **131072** | **100** | **147.30 ns** | **2.954 ns** | **8.283 ns** | **145.49 ns** | **0.0072** | **120 B** | +| SysSorted | 131072 | 100 | 556.39 ns | 13.929 ns | 41.070 ns | 544.95 ns | - | - | +| LangExt.HashMap | 131072 | 100 | 162.81 ns | 2.056 ns | 1.823 ns | 162.78 ns | - | - | +| LangExtSorted | 131072 | 100 | 605.15 ns | 12.352 ns | 35.040 ns | 595.60 ns | - | - | +| PersistentMap | **131072** | **1000** | **170.16 ns** | **3.417 ns** | **5.894 ns** | **170.33 ns** | **0.0072** | **120 B** | +| SysSorted | 131072 | 1000 | 625.78 ns | 12.406 ns | 33.541 ns | 618.61 ns | - | - | +| LangExt.HashMap | 131072 | 1000 | 763.75 ns | 14.928 ns | 26.919 ns | 763.61 ns | - | - | +| LangExtSorted | 131072 | 1000 | 692.92 ns | 21.200 ns | 62.177 ns | 673.69 ns | - | - | + + +#+end_src + +To look at pure overhead, here is a benchmark using integers as keys. This is also a good fit for this BTree, since it can utilize a key strategy that compares integers using AVX. The hash based alternatives are going to have a huge advantage here. As you can see, reading a single value isn't great, and setting a single value after building the btree is also pretty awful (setting many should probably be done using transients). Iterating a building (using transients. The only valid comparison here is probably to MS SortedDict) is however plenty fast. + + +#+begin_src +| Method | N | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|-------------------------------------|--------|-----------------:|-----------------:|---------------:|----------:|----------:|--------:|------------:| +| 'Build: PersistentMap (Transient)' | 100 | 3,764.63 ns | 335.100 ns | 18.368 ns | 0.3929 | 0.0038 | - | 6632 B | +| 'Build: MS Sorted (Builder)' | 100 | 3,096.11 ns | 361.221 ns | 19.800 ns | 0.2899 | 0.0038 | - | 4864 B | +| 'Build: LanguageExt Map (AVL)' | 100 | 6,967.02 ns | 1,549.676 ns | 84.943 ns | 2.2736 | 0.0229 | - | 38144 B | +| 'Build: LanguageExt HashMap' | 100 | 4,594.07 ns | 1,650.289 ns | 90.458 ns | 1.9684 | 0.0076 | - | 33024 B | +| 'Read: PersistentMap' | 100 | 1,596.68 ns | 941.760 ns | 51.621 ns | 0.4292 | - | - | 7200 B | +| 'Read: MS Sorted' | 100 | 474.54 ns | 399.684 ns | 21.908 ns | - | - | - | - | +| 'Read: LanguageExt Map' | 100 | 1,311.31 ns | 396.858 ns | 21.753 ns | - | - | - | - | +| 'Read: LanguageExt HashMap' | 100 | 641.22 ns | 4.436 ns | 0.243 ns | - | - | - | - | +| 'Iterate: PersistentMap' | 100 | 135.41 ns | 5.953 ns | 0.326 ns | - | - | - | - | +| 'Iterate: MS Sorted' | 100 | 372.31 ns | 15.959 ns | 0.875 ns | - | - | - | - | +| 'Iterate: LanguageExt Map' | 100 | 287.33 ns | 89.684 ns | 4.916 ns | 0.0019 | - | - | 32 B | +| 'Iterate: LanguageExt HashMap' | 100 | 781.56 ns | 40.469 ns | 2.218 ns | 0.0648 | - | - | 1088 B | +| 'Set: PersistentMap' | 100 | 85.68 ns | 25.237 ns | 1.383 ns | 0.1142 | 0.0007 | - | 1912 B | +| 'Set: MS Sorted' | 100 | 66.44 ns | 5.990 ns | 0.328 ns | 0.0229 | - | - | 384 B | +| 'Set: LanguageExt Map' | 100 | 60.04 ns | 27.661 ns | 1.516 ns | 0.0219 | - | - | 368 B | +| 'Set: LanguageExt HashMap' | 100 | 36.62 ns | 3.649 ns | 0.200 ns | 0.0206 | - | - | 344 B | +| 'Build: PersistentMap (Transient)' | 1000 | 49,445.56 ns | 8,020.473 ns | 439.629 ns | 3.1738 | 0.2441 | - | 53096 B | +| 'Build: MS Sorted (Builder)' | 1000 | 50,163.19 ns | 2,022.398 ns | 110.854 ns | 2.8687 | 0.4272 | - | 48064 B | +| 'Build: LanguageExt Map (AVL)' | 1000 | 103,877.98 ns | 10,669.198 ns | 584.815 ns | 34.6680 | 3.1738 | - | 580688 B | +| 'Build: LanguageExt HashMap' | 1000 | 124,339.17 ns | 27,843.925 ns | 1,526.219 ns | 45.4102 | 3.2959 | - | 760096 B | +| 'Read: PersistentMap' | 1000 | 17,671.71 ns | 16,074.235 ns | 881.083 ns | 4.3030 | - | - | 72000 B | +| 'Read: MS Sorted' | 1000 | 7,911.72 ns | 6,398.764 ns | 350.738 ns | - | - | - | - | +| 'Read: LanguageExt Map' | 1000 | 20,187.52 ns | 2,218.583 ns | 121.608 ns | - | - | - | - | +| 'Read: LanguageExt HashMap' | 1000 | 9,740.28 ns | 590.689 ns | 32.378 ns | - | - | - | - | +| 'Iterate: PersistentMap' | 1000 | 1,217.47 ns | 41.521 ns | 2.276 ns | - | - | - | - | +| 'Iterate: MS Sorted' | 1000 | 3,875.47 ns | 243.628 ns | 13.354 ns | - | - | - | - | +| 'Iterate: LanguageExt Map' | 1000 | 2,862.82 ns | 259.120 ns | 14.203 ns | - | - | - | 32 B | +| 'Iterate: LanguageExt HashMap' | 1000 | 11,974.93 ns | 2,791.930 ns | 153.035 ns | 1.9226 | - | - | 32320 B | +| 'Set: PersistentMap' | 1000 | 121.01 ns | 71.460 ns | 3.917 ns | 0.1142 | 0.0007 | - | 1912 B | +| 'Set: MS Sorted' | 1000 | 91.62 ns | 18.682 ns | 1.024 ns | 0.0315 | - | - | 528 B | +| 'Set: LanguageExt Map' | 1000 | 82.26 ns | 14.918 ns | 0.818 ns | 0.0305 | - | - | 512 B | +| 'Set: LanguageExt HashMap' | 1000 | 57.02 ns | 5.549 ns | 0.304 ns | 0.0367 | - | - | 616 B | +| 'Build: PersistentMap (Transient)' | 100000 | 10,808,233.62 ns | 745,888.473 ns | 40,884.664 ns | 296.8750 | 218.7500 | - | 5185832 B | +| 'Build: MS Sorted (Builder)' | 100000 | 16,655,882.43 ns | 273,417.248 ns | 14,986.922 ns | 281.2500 | 250.0000 | - | 4800064 B | +| 'Build: LanguageExt Map (AVL)' | 100000 | 39,932,734.83 ns | 8,226,697.957 ns | 450,933.077 ns | 5333.3333 | 3333.3333 | - | 89959040 B | +| 'Build: LanguageExt HashMap' | 100000 | 21,220,179.10 ns | 4,851,159.671 ns | 265,908.432 ns | 5781.2500 | 2968.7500 | 31.2500 | 96555422 B | +| 'Read: PersistentMap' | 100000 | 7,359,807.97 ns | 593,641.656 ns | 32,539.502 ns | 710.9375 | - | - | 12000000 B | +| 'Read: MS Sorted' | 100000 | 8,428,009.48 ns | 2,943,716.723 ns | 161,355.047 ns | - | - | - | - | +| 'Read: LanguageExt Map' | 100000 | 10,268,884.43 ns | 1,035,387.251 ns | 56,753.069 ns | - | - | - | - | +| 'Read: LanguageExt HashMap' | 100000 | 1,936,555.07 ns | 19,847.031 ns | 1,087.883 ns | - | - | - | - | +| 'Iterate: PersistentMap' | 100000 | 151,028.79 ns | 4,471.641 ns | 245.106 ns | - | - | - | - | +| 'Iterate: MS Sorted' | 100000 | 1,068,072.16 ns | 24,123.759 ns | 1,322.305 ns | - | - | - | - | +| 'Iterate: LanguageExt Map' | 100000 | 837,677.39 ns | 10,559.659 ns | 578.811 ns | - | - | - | 32 B | +| 'Iterate: LanguageExt HashMap' | 100000 | 1,226,773.82 ns | 87,367.624 ns | 4,788.914 ns | 64.4531 | - | - | 1082432 B | +| 'Set: PersistentMap' | 100000 | 208.61 ns | 225.596 ns | 12.366 ns | 0.1984 | 0.0024 | - | 3320 B | +| 'Set: MS Sorted' | 100000 | 138.82 ns | 18.977 ns | 1.040 ns | 0.0458 | - | - | 768 B | +| 'Set: LanguageExt Map' | 100000 | 128.28 ns | 47.447 ns | 2.601 ns | 0.0448 | - | - | 752 B | +| 'Set: LanguageExt HashMap' | 100000 | 84.33 ns | 7.125 ns | 0.391 ns | 0.0583 | - | - | 976 B | + + +#+end_src + +Lastly, here is a comparison of how things look compared to itself for when the prefixes are turned off for strings. This relies on regular linear string searches. This is however STILL a pretty good benchmark for all ordered dicts, since the strings are random, meaning the string comparison can stop almost immediately. For real world keys, all hash based dicts will be better, with everything regarding getting or setting a single key. + + +#+begin_src +| Method | N | KeyLength | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | +|--------------------------- |------ |---------- |----------------:|-----------------:|--------------:|---------:|---------:|----------:| +| 'Build: NiceBTree' | 10000 | 10 | 2,037,851.45 ns | 60,065.478 ns | 3,292.392 ns | 35.1563 | 15.6250 | 644600 B | +| 'Build: MS HashDict' | 10000 | 10 | 1,647,876.61 ns | 105,992.785 ns | 5,809.822 ns | 37.1094 | 15.6250 | 640096 B | +| 'Build: MS SortedDict' | 10000 | 10 | 3,853,709.48 ns | 606,862.929 ns | 33,264.205 ns | 31.2500 | 11.7188 | 560112 B | +| 'Build: LangExt HashMap' | 10000 | 10 | 1,612,117.07 ns | 313,091.665 ns | 17,161.611 ns | 472.6563 | 154.2969 | 7919328 B | +| 'Build: LangExt Map' | 10000 | 10 | 5,363,298.26 ns | 582,836.469 ns | 31,947.234 ns | 507.8125 | 203.1250 | 8594784 B | +| 'Read: NiceBTree' | 10000 | 10 | 36.30 ns | 0.918 ns | 0.050 ns | - | - | - | +| 'Read: MS HashDict' | 10000 | 10 | 12.66 ns | 0.431 ns | 0.024 ns | - | - | - | +| 'Read: MS SortedDict' | 10000 | 10 | 233.59 ns | 26.034 ns | 1.427 ns | - | - | - | +| 'Read: LangExt HashMap' | 10000 | 10 | 28.61 ns | 0.254 ns | 0.014 ns | - | - | - | +| 'Read: LangExt Map' | 10000 | 10 | 268.13 ns | 3.301 ns | 0.181 ns | - | - | - | +| 'Iterate: NiceBTree' | 10000 | 10 | 12,630.95 ns | 759.122 ns | 41.610 ns | - | - | - | +| 'Iterate: MS HashDict' | 10000 | 10 | 151,314.44 ns | 22,323.733 ns | 1,223.639 ns | - | - | - | +| 'Iterate: MS SortedDict' | 10000 | 10 | 57,402.20 ns | 1,498.945 ns | 82.162 ns | - | - | - | +| 'Iterate: LangExt HashMap' | 10000 | 10 | 148,980.47 ns | 21,649.428 ns | 1,186.678 ns | 10.0098 | - | 170712 B | +| 'Iterate: LangExt Map' | 10000 | 10 | 34,428.07 ns | 5,647.779 ns | 309.574 ns | - | - | 32 B | +| 'Update: NiceBTree' | 10000 | 10 | 303.01 ns | 107.216 ns | 5.877 ns | 0.2027 | 0.0024 | 3392 B | +| 'Update: MS HashDict' | 10000 | 10 | 48.36 ns | 1.275 ns | 0.070 ns | 0.0100 | - | 168 B | +| 'Update: MS SortedDict' | 10000 | 10 | 137.47 ns | 33.015 ns | 1.810 ns | 0.0196 | - | 328 B | +| 'Update: LangExt HashMap' | 10000 | 10 | 102.57 ns | 7.196 ns | 0.394 ns | 0.0502 | 0.0001 | 840 B | +| 'Update: LangExt Map' | 10000 | 10 | 122.54 ns | 8.691 ns | 0.476 ns | 0.0186 | - | 312 B | +| 'Build: NiceBTree' | 10000 | 50 | 2,020,984.87 ns | 91,788.936 ns | 5,031.261 ns | 35.1563 | 11.7188 | 624248 B | +| 'Build: MS HashDict' | 10000 | 50 | 1,811,186.24 ns | 23,842.593 ns | 1,306.893 ns | 37.1094 | 15.6250 | 640096 B | +| 'Build: MS SortedDict' | 10000 | 50 | 3,883,214.25 ns | 198,364.955 ns | 10,873.053 ns | 31.2500 | 15.6250 | 560112 B | +| 'Build: LangExt HashMap' | 10000 | 50 | 1,784,616.64 ns | 248,685.113 ns | 13,631.270 ns | 472.6563 | 154.2969 | 7926712 B | +| 'Build: LangExt Map' | 10000 | 50 | 5,248,030.22 ns | 1,486,577.018 ns | 81,484.303 ns | 507.8125 | 203.1250 | 8544720 B | +| 'Read: NiceBTree' | 10000 | 50 | 40.64 ns | 0.574 ns | 0.031 ns | - | - | - | +| 'Read: MS HashDict' | 10000 | 50 | 29.91 ns | 2.182 ns | 0.120 ns | - | - | - | +| 'Read: MS SortedDict' | 10000 | 50 | 255.55 ns | 4.315 ns | 0.237 ns | - | - | - | +| 'Read: LangExt HashMap' | 10000 | 50 | 47.61 ns | 4.373 ns | 0.240 ns | - | - | - | +| 'Read: LangExt Map' | 10000 | 50 | 255.68 ns | 8.932 ns | 0.490 ns | - | - | - | +| 'Iterate: NiceBTree' | 10000 | 50 | 12,718.71 ns | 1,727.345 ns | 94.682 ns | - | - | - | +| 'Iterate: MS HashDict' | 10000 | 50 | 170,815.59 ns | 70,087.036 ns | 3,841.707 ns | - | - | - | +| 'Iterate: MS SortedDict' | 10000 | 50 | 68,982.58 ns | 9,267.855 ns | 508.002 ns | - | - | - | +| 'Iterate: LangExt HashMap' | 10000 | 50 | 144,442.27 ns | 96,636.820 ns | 5,296.990 ns | 9.7656 | - | 165600 B | +| 'Iterate: LangExt Map' | 10000 | 50 | 35,082.49 ns | 8,851.428 ns | 485.177 ns | - | - | 32 B | +| 'Update: NiceBTree' | 10000 | 50 |yy 393.56 ns | 101.149 ns | 5.544 ns | 0.2027 | 0.0024 | 3392 B | +| 'Update: MS HashDict' | 10000 | 50 | 114.57 ns | 17.939 ns | 0.983 ns | 0.0215 | - | 360 B | +| 'Update: MS SortedDict' | 10000 | 50 | 65.51 ns | 0.969 ns | 0.053 ns | 0.0129 | - | 216 B | +| 'Update: LangExt HashMap' | 10000 | 50 | 103.28 ns | 11.740 ns | 0.644 ns | 0.0535 | - | 896 B | +| 'Update: LangExt Map' | 10000 | 50 | 67.62 ns | 2.649 ns | 0.145 ns | 0.0119 | - | 200 B | + +#+end_src ** Architecture Notes: Key Strategies NiceBtree uses =IKeyStrategy= to map generic keys (like =string= or =double=) into sortable =long= prefixes. This achieves two things: 1. Enables AVX512/AVX2 vector instructions to search internal nodes simultaneously. diff --git a/benchmarks/AgainstLanguageExt/integerBenchmarks.cs b/benchmarks/AgainstLanguageExt/integerBenchmarks.cs index 2b43cb9..4b91b17 100644 --- a/benchmarks/AgainstLanguageExt/integerBenchmarks.cs +++ b/benchmarks/AgainstLanguageExt/integerBenchmarks.cs @@ -64,7 +64,7 @@ public class ImmutableCollectionBenchmarks // the cost of pure immutable inserts vs your Transient/Builder. // ========================================================= - [Benchmark(Description = "Build: NiceBTree (Transient)")] + [Benchmark(Description = "Build: PersistentMap (Transient)")] public int Build_NiceBTree() { var t = BaseOrderedMap.CreateTransient(_strategy); @@ -72,6 +72,14 @@ public class ImmutableCollectionBenchmarks return t.Count; } +[Benchmark(Description = "Build: PersistentMap (Persistent)")] + public int Build_PersistentBTree() + { + var t = PersistentMap.Empty(_strategy); + for (int i = 0; i < N; i++) t.Set(_keys[i], _values[i]); + return t.Count; + } + [Benchmark(Description = "Build: MS Sorted (Builder)")] public int Build_MsSorted() { @@ -110,11 +118,9 @@ public class ImmutableCollectionBenchmarks [Benchmark(Description = "Read: NiceBTree")] public int Read_NiceBTree() { - int found = 0; - for (int i = 0; i < N; i++) - { - if (_niceMap.TryGetValue(_keys[i], out _)) found++; - } + var found = 1; + if (_niceMap.TryGetValue(_keys[N/2], out _)) found++; + return found; } @@ -122,10 +128,8 @@ public class ImmutableCollectionBenchmarks public int Read_MsSorted() { int found = 0; - for (int i = 0; i < N; i++) - { - if (_msSortedMap.ContainsKey(_keys[i])) found++; - } + if (_msSortedMap.ContainsKey(_keys[N/2])) found++; + return found; } @@ -133,11 +137,9 @@ public class ImmutableCollectionBenchmarks public int Read_LanguageExt_Map() { int found = 0; - for (int i = 0; i < N; i++) - { // Find returns Option, IsSome checks if it exists - if (_leMap.Find(_keys[i]).IsSome) found++; - } + if (_leMap.Find(_keys[N/2]).IsSome) found++; + return found; } @@ -145,10 +147,9 @@ public class ImmutableCollectionBenchmarks public int Read_LanguageExt_HashMap() { int found = 0; - for (int i = 0; i < N; i++) - { - if (_leHashMap.Find(_keys[i]).IsSome) found++; - } + + if (_leHashMap.Find(_keys[N/2]).IsSome) found++; + return found; } @@ -216,4 +217,4 @@ public class ImmutableCollectionBenchmarks { return _leHashMap.SetItem(_keys[N / 2], -1); } -} \ No newline at end of file +} From c3b9268cf1d56fca0d7b43b8210ab66aaae6f299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?linus=20bj=C3=B6rnstam?= Date: Fri, 17 Apr 2026 15:10:28 +0200 Subject: [PATCH 05/22] Changed to make it fit in the browser --- PersistentMap/Readme.org | 236 +++++++++++++++++++-------------------- 1 file changed, 118 insertions(+), 118 deletions(-) diff --git a/PersistentMap/Readme.org b/PersistentMap/Readme.org index 6fe43a3..f608d5f 100644 --- a/PersistentMap/Readme.org +++ b/PersistentMap/Readme.org @@ -111,32 +111,32 @@ var symmetricDiff = mapA.SymmetricExcept(mapB); This is going to be all over the place, but here is a small comparison to other immutable sequences. Due to how the prefix optimization works, this persistent map will be the absolutely most performant when there is high entropy in the first 8 bytes of the key. The following is pretty much the best scenario we can have since we probably only look at the first 8 characters (this is for reading a value). #+begin_src -| Method | CollectionSize | KeySize | Mean | Error | StdDev | Median | Gen0 | Allocated | -|-----------------|----------------|----------|--------------:|-------------:|-------------:|--------------:|-----------:|----------:| -| PersistentMap | **1024** | **10** | **25.61 ns** | **0.479 ns** | **0.425 ns** | **25.56 ns** | **0.0043** | **72 B** | -| Sys.Sorted | 1024 | 10 | 153.18 ns | 1.605 ns | 1.423 ns | 153.14 ns | - | - | -| LangExt.HashMap | 1024 | 10 | 24.80 ns | 0.073 ns | 0.065 ns | 24.79 ns | - | - | -| LangExtSorted | 1024 | 10 | 176.90 ns | 1.196 ns | 1.061 ns | 176.48 ns | - | - | -| PersistentMap | **1024** | **100** | **26.43 ns** | **0.245 ns** | **0.217 ns** | **26.35 ns** | **0.0043** | **72 B** | -| SysSorted | 1024 | 100 | 154.77 ns | 1.977 ns | 1.849 ns | 153.74 ns | - | - | -| LangExt.HashMap | 1024 | 100 | 66.30 ns | 0.054 ns | 0.051 ns | 66.31 ns | - | - | -| LangExtSorted | 1024 | 100 | 177.28 ns | 1.516 ns | 1.344 ns | 177.14 ns | - | - | -| PersistentMap | **1024** | **1000** | **26.17 ns** | **0.480 ns** | **0.449 ns** | **26.07 ns** | **0.0043** | **72 B** | -| SysSorted | 1024 | 1000 | 155.68 ns | 1.229 ns | 1.090 ns | 155.26 ns | - | - | -| LangExt.HashMap | 1024 | 1000 | 491.97 ns | 0.927 ns | 0.774 ns | 492.14 ns | - | - | -| LangExtSorted | 1024 | 1000 | 181.58 ns | 1.030 ns | 0.913 ns | 181.54 ns | - | - | -| PersistentMap | **131072** | **10** | **109.34 ns** | **2.200 ns** | **5.795 ns** | **107.97 ns** | **0.0072** | **120 B** | -| SysSorted | 131072 | 10 | 460.22 ns | 9.101 ns | 12.457 ns | 453.61 ns | - | - | -| LangExt.HashMap | 131072 | 10 | 60.35 ns | 1.346 ns | 3.904 ns | 59.40 ns | - | - | -| LangExtSorted | 131072 | 10 | 555.17 ns | 15.764 ns | 46.233 ns | 539.79 ns | - | - | -| PersistentMap | **131072** | **100** | **147.30 ns** | **2.954 ns** | **8.283 ns** | **145.49 ns** | **0.0072** | **120 B** | -| SysSorted | 131072 | 100 | 556.39 ns | 13.929 ns | 41.070 ns | 544.95 ns | - | - | -| LangExt.HashMap | 131072 | 100 | 162.81 ns | 2.056 ns | 1.823 ns | 162.78 ns | - | - | -| LangExtSorted | 131072 | 100 | 605.15 ns | 12.352 ns | 35.040 ns | 595.60 ns | - | - | -| PersistentMap | **131072** | **1000** | **170.16 ns** | **3.417 ns** | **5.894 ns** | **170.33 ns** | **0.0072** | **120 B** | -| SysSorted | 131072 | 1000 | 625.78 ns | 12.406 ns | 33.541 ns | 618.61 ns | - | - | -| LangExt.HashMap | 131072 | 1000 | 763.75 ns | 14.928 ns | 26.919 ns | 763.61 ns | - | - | -| LangExtSorted | 131072 | 1000 | 692.92 ns | 21.200 ns | 62.177 ns | 673.69 ns | - | - | +| Method | CollectionSize | KeySize | Mean | Gen0 | Allocated | +|-----------------|----------------|----------|--------------:|-----------:|----------:| +| PersistentMap | **1024** | **10** | **25.61 ns** | **0.0043** | **72 B** | +| Sys.Sorted | 1024 | 10 | 153.18 ns | - | - | +| LangExt.HashMap | 1024 | 10 | 24.80 ns | - | - | +| LangExtSorted | 1024 | 10 | 176.90 ns | - | - | +| PersistentMap | **1024** | **100** | **26.43 ns** | **0.0043** | **72 B** | +| SysSorted | 1024 | 100 | 154.77 ns | - | - | +| LangExt.HashMap | 1024 | 100 | 66.30 ns | - | - | +| LangExtSorted | 1024 | 100 | 177.28 ns | - | - | +| PersistentMap | **1024** | **1000** | **26.17 ns** | **0.0043** | **72 B** | +| SysSorted | 1024 | 1000 | 155.68 ns | - | - | +| LangExt.HashMap | 1024 | 1000 | 491.97 ns | - | - | +| LangExtSorted | 1024 | 1000 | 181.58 ns | - | - | +| PersistentMap | **131072** | **10** | **109.34 ns** | **0.0072** | **120 B** | +| SysSorted | 131072 | 10 | 460.22 ns | - | - | +| LangExt.HashMap | 131072 | 10 | 60.35 ns | - | - | +| LangExtSorted | 131072 | 10 | 555.17 ns | - | - | +| PersistentMap | **131072** | **100** | **147.30 ns** | **0.0072** | **120 B** | +| SysSorted | 131072 | 100 | 556.39 ns | - | - | +| LangExt.HashMap | 131072 | 100 | 162.81 ns | - | - | +| LangExtSorted | 131072 | 100 | 605.15 ns | - | - | +| PersistentMap | **131072** | **1000** | **170.16 ns** | **0.0072** | **120 B** | +| SysSorted | 131072 | 1000 | 625.78 ns | - | - | +| LangExt.HashMap | 131072 | 1000 | 763.75 ns | - | - | +| LangExtSorted | 131072 | 1000 | 692.92 ns | - | - | #+end_src @@ -145,56 +145,56 @@ To look at pure overhead, here is a benchmark using integers as keys. This is al #+begin_src -| Method | N | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|-------------------------------------|--------|-----------------:|-----------------:|---------------:|----------:|----------:|--------:|------------:| -| 'Build: PersistentMap (Transient)' | 100 | 3,764.63 ns | 335.100 ns | 18.368 ns | 0.3929 | 0.0038 | - | 6632 B | -| 'Build: MS Sorted (Builder)' | 100 | 3,096.11 ns | 361.221 ns | 19.800 ns | 0.2899 | 0.0038 | - | 4864 B | -| 'Build: LanguageExt Map (AVL)' | 100 | 6,967.02 ns | 1,549.676 ns | 84.943 ns | 2.2736 | 0.0229 | - | 38144 B | -| 'Build: LanguageExt HashMap' | 100 | 4,594.07 ns | 1,650.289 ns | 90.458 ns | 1.9684 | 0.0076 | - | 33024 B | -| 'Read: PersistentMap' | 100 | 1,596.68 ns | 941.760 ns | 51.621 ns | 0.4292 | - | - | 7200 B | -| 'Read: MS Sorted' | 100 | 474.54 ns | 399.684 ns | 21.908 ns | - | - | - | - | -| 'Read: LanguageExt Map' | 100 | 1,311.31 ns | 396.858 ns | 21.753 ns | - | - | - | - | -| 'Read: LanguageExt HashMap' | 100 | 641.22 ns | 4.436 ns | 0.243 ns | - | - | - | - | -| 'Iterate: PersistentMap' | 100 | 135.41 ns | 5.953 ns | 0.326 ns | - | - | - | - | -| 'Iterate: MS Sorted' | 100 | 372.31 ns | 15.959 ns | 0.875 ns | - | - | - | - | -| 'Iterate: LanguageExt Map' | 100 | 287.33 ns | 89.684 ns | 4.916 ns | 0.0019 | - | - | 32 B | -| 'Iterate: LanguageExt HashMap' | 100 | 781.56 ns | 40.469 ns | 2.218 ns | 0.0648 | - | - | 1088 B | -| 'Set: PersistentMap' | 100 | 85.68 ns | 25.237 ns | 1.383 ns | 0.1142 | 0.0007 | - | 1912 B | -| 'Set: MS Sorted' | 100 | 66.44 ns | 5.990 ns | 0.328 ns | 0.0229 | - | - | 384 B | -| 'Set: LanguageExt Map' | 100 | 60.04 ns | 27.661 ns | 1.516 ns | 0.0219 | - | - | 368 B | -| 'Set: LanguageExt HashMap' | 100 | 36.62 ns | 3.649 ns | 0.200 ns | 0.0206 | - | - | 344 B | -| 'Build: PersistentMap (Transient)' | 1000 | 49,445.56 ns | 8,020.473 ns | 439.629 ns | 3.1738 | 0.2441 | - | 53096 B | -| 'Build: MS Sorted (Builder)' | 1000 | 50,163.19 ns | 2,022.398 ns | 110.854 ns | 2.8687 | 0.4272 | - | 48064 B | -| 'Build: LanguageExt Map (AVL)' | 1000 | 103,877.98 ns | 10,669.198 ns | 584.815 ns | 34.6680 | 3.1738 | - | 580688 B | -| 'Build: LanguageExt HashMap' | 1000 | 124,339.17 ns | 27,843.925 ns | 1,526.219 ns | 45.4102 | 3.2959 | - | 760096 B | -| 'Read: PersistentMap' | 1000 | 17,671.71 ns | 16,074.235 ns | 881.083 ns | 4.3030 | - | - | 72000 B | -| 'Read: MS Sorted' | 1000 | 7,911.72 ns | 6,398.764 ns | 350.738 ns | - | - | - | - | -| 'Read: LanguageExt Map' | 1000 | 20,187.52 ns | 2,218.583 ns | 121.608 ns | - | - | - | - | -| 'Read: LanguageExt HashMap' | 1000 | 9,740.28 ns | 590.689 ns | 32.378 ns | - | - | - | - | -| 'Iterate: PersistentMap' | 1000 | 1,217.47 ns | 41.521 ns | 2.276 ns | - | - | - | - | -| 'Iterate: MS Sorted' | 1000 | 3,875.47 ns | 243.628 ns | 13.354 ns | - | - | - | - | -| 'Iterate: LanguageExt Map' | 1000 | 2,862.82 ns | 259.120 ns | 14.203 ns | - | - | - | 32 B | -| 'Iterate: LanguageExt HashMap' | 1000 | 11,974.93 ns | 2,791.930 ns | 153.035 ns | 1.9226 | - | - | 32320 B | -| 'Set: PersistentMap' | 1000 | 121.01 ns | 71.460 ns | 3.917 ns | 0.1142 | 0.0007 | - | 1912 B | -| 'Set: MS Sorted' | 1000 | 91.62 ns | 18.682 ns | 1.024 ns | 0.0315 | - | - | 528 B | -| 'Set: LanguageExt Map' | 1000 | 82.26 ns | 14.918 ns | 0.818 ns | 0.0305 | - | - | 512 B | -| 'Set: LanguageExt HashMap' | 1000 | 57.02 ns | 5.549 ns | 0.304 ns | 0.0367 | - | - | 616 B | -| 'Build: PersistentMap (Transient)' | 100000 | 10,808,233.62 ns | 745,888.473 ns | 40,884.664 ns | 296.8750 | 218.7500 | - | 5185832 B | -| 'Build: MS Sorted (Builder)' | 100000 | 16,655,882.43 ns | 273,417.248 ns | 14,986.922 ns | 281.2500 | 250.0000 | - | 4800064 B | -| 'Build: LanguageExt Map (AVL)' | 100000 | 39,932,734.83 ns | 8,226,697.957 ns | 450,933.077 ns | 5333.3333 | 3333.3333 | - | 89959040 B | -| 'Build: LanguageExt HashMap' | 100000 | 21,220,179.10 ns | 4,851,159.671 ns | 265,908.432 ns | 5781.2500 | 2968.7500 | 31.2500 | 96555422 B | -| 'Read: PersistentMap' | 100000 | 7,359,807.97 ns | 593,641.656 ns | 32,539.502 ns | 710.9375 | - | - | 12000000 B | -| 'Read: MS Sorted' | 100000 | 8,428,009.48 ns | 2,943,716.723 ns | 161,355.047 ns | - | - | - | - | -| 'Read: LanguageExt Map' | 100000 | 10,268,884.43 ns | 1,035,387.251 ns | 56,753.069 ns | - | - | - | - | -| 'Read: LanguageExt HashMap' | 100000 | 1,936,555.07 ns | 19,847.031 ns | 1,087.883 ns | - | - | - | - | -| 'Iterate: PersistentMap' | 100000 | 151,028.79 ns | 4,471.641 ns | 245.106 ns | - | - | - | - | -| 'Iterate: MS Sorted' | 100000 | 1,068,072.16 ns | 24,123.759 ns | 1,322.305 ns | - | - | - | - | -| 'Iterate: LanguageExt Map' | 100000 | 837,677.39 ns | 10,559.659 ns | 578.811 ns | - | - | - | 32 B | -| 'Iterate: LanguageExt HashMap' | 100000 | 1,226,773.82 ns | 87,367.624 ns | 4,788.914 ns | 64.4531 | - | - | 1082432 B | -| 'Set: PersistentMap' | 100000 | 208.61 ns | 225.596 ns | 12.366 ns | 0.1984 | 0.0024 | - | 3320 B | -| 'Set: MS Sorted' | 100000 | 138.82 ns | 18.977 ns | 1.040 ns | 0.0458 | - | - | 768 B | -| 'Set: LanguageExt Map' | 100000 | 128.28 ns | 47.447 ns | 2.601 ns | 0.0448 | - | - | 752 B | -| 'Set: LanguageExt HashMap' | 100000 | 84.33 ns | 7.125 ns | 0.391 ns | 0.0583 | - | - | 976 B | +| Method | N | Mean | Gen0 | Gen1 | Gen2 | Allocated | +|-------------------------------------|--------|-----------------:|----------:|----------:|--------:|------------:| +| 'Build: PersistentMap (Transient)' | 100 | 3,764.63 ns | 0.3929 | 0.0038 | - | 6632 B | +| 'Build: MS Sorted (Builder)' | 100 | 3,096.11 ns | 0.2899 | 0.0038 | - | 4864 B | +| 'Build: LanguageExt Map (AVL)' | 100 | 6,967.02 ns | 2.2736 | 0.0229 | - | 38144 B | +| 'Build: LanguageExt HashMap' | 100 | 4,594.07 ns | 1.9684 | 0.0076 | - | 33024 B | +| 'Read: PersistentMap' | 100 | 1,596.68 ns | 0.4292 | - | - | 7200 B | +| 'Read: MS Sorted' | 100 | 474.54 ns | - | - | - | - | +| 'Read: LanguageExt Map' | 100 | 1,311.31 ns | - | - | - | - | +| 'Read: LanguageExt HashMap' | 100 | 641.22 ns | - | - | - | - | +| 'Iterate: PersistentMap' | 100 | 135.41 ns | - | - | - | - | +| 'Iterate: MS Sorted' | 100 | 372.31 ns | - | - | - | - | +| 'Iterate: LanguageExt Map' | 100 | 287.33 ns | 0.0019 | - | - | 32 B | +| 'Iterate: LanguageExt HashMap' | 100 | 781.56 ns | 0.0648 | - | - | 1088 B | +| 'Set: PersistentMap' | 100 | 85.68 ns | 0.1142 | 0.0007 | - | 1912 B | +| 'Set: MS Sorted' | 100 | 66.44 ns | 0.0229 | - | - | 384 B | +| 'Set: LanguageExt Map' | 100 | 60.04 ns | 0.0219 | - | - | 368 B | +| 'Set: LanguageExt HashMap' | 100 | 36.62 ns | 0.0206 | - | - | 344 B | +| 'Build: PersistentMap (Transient)' | 1000 | 49,445.56 ns | 3.1738 | 0.2441 | - | 53096 B | +| 'Build: MS Sorted (Builder)' | 1000 | 50,163.19 ns | 2.8687 | 0.4272 | - | 48064 B | +| 'Build: LanguageExt Map (AVL)' | 1000 | 103,877.98 ns | 34.6680 | 3.1738 | - | 580688 B | +| 'Build: LanguageExt HashMap' | 1000 | 124,339.17 ns | 45.4102 | 3.2959 | - | 760096 B | +| 'Read: PersistentMap' | 1000 | 17,671.71 ns | 4.3030 | - | - | 72000 B | +| 'Read: MS Sorted' | 1000 | 7,911.72 ns | - | - | - | - | +| 'Read: LanguageExt Map' | 1000 | 20,187.52 ns | - | - | - | - | +| 'Read: LanguageExt HashMap' | 1000 | 9,740.28 ns | - | - | - | - | +| 'Iterate: PersistentMap' | 1000 | 1,217.47 ns | - | - | - | - | +| 'Iterate: MS Sorted' | 1000 | 3,875.47 ns | - | - | - | - | +| 'Iterate: LanguageExt Map' | 1000 | 2,862.82 ns | - | - | - | 32 B | +| 'Iterate: LanguageExt HashMap' | 1000 | 11,974.93 ns | 1.9226 | - | - | 32320 B | +| 'Set: PersistentMap' | 1000 | 121.01 ns | 0.1142 | 0.0007 | - | 1912 B | +| 'Set: MS Sorted' | 1000 | 91.62 ns | 0.0315 | - | - | 528 B | +| 'Set: LanguageExt Map' | 1000 | 82.26 ns | 0.0305 | - | - | 512 B | +| 'Set: LanguageExt HashMap' | 1000 | 57.02 ns | 0.0367 | - | - | 616 B | +| 'Build: PersistentMap (Transient)' | 100000 | 10,808,233.62 ns | 296.8750 | 218.7500 | - | 5185832 B | +| 'Build: MS Sorted (Builder)' | 100000 | 16,655,882.43 ns | 281.2500 | 250.0000 | - | 4800064 B | +| 'Build: LanguageExt Map (AVL)' | 100000 | 39,932,734.83 ns | 5333.3333 | 3333.3333 | - | 89959040 B | +| 'Build: LanguageExt HashMap' | 100000 | 21,220,179.10 ns | 5781.2500 | 2968.7500 | 31.2500 | 96555422 B | +| 'Read: PersistentMap' | 100000 | 7,359,807.97 ns | 710.9375 | - | - | 12000000 B | +| 'Read: MS Sorted' | 100000 | 8,428,009.48 ns | - | - | - | - | +| 'Read: LanguageExt Map' | 100000 | 10,268,884.43 ns | - | - | - | - | +| 'Read: LanguageExt HashMap' | 100000 | 1,936,555.07 ns | - | - | - | - | +| 'Iterate: PersistentMap' | 100000 | 151,028.79 ns | - | - | - | - | +| 'Iterate: MS Sorted' | 100000 | 1,068,072.16 ns | - | - | - | - | +| 'Iterate: LanguageExt Map' | 100000 | 837,677.39 ns | - | - | - | 32 B | +| 'Iterate: LanguageExt HashMap' | 100000 | 1,226,773.82 ns | 64.4531 | - | - | 1082432 B | +| 'Set: PersistentMap' | 100000 | 208.61 ns | 0.1984 | 0.0024 | - | 3320 B | +| 'Set: MS Sorted' | 100000 | 138.82 ns | 0.0458 | - | - | 768 B | +| 'Set: LanguageExt Map' | 100000 | 128.28 ns | 0.0448 | - | - | 752 B | +| 'Set: LanguageExt HashMap' | 100000 | 84.33 ns | 0.0583 | - | - | 976 B | #+end_src @@ -203,48 +203,48 @@ Lastly, here is a comparison of how things look compared to itself for when the #+begin_src -| Method | N | KeyLength | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | -|--------------------------- |------ |---------- |----------------:|-----------------:|--------------:|---------:|---------:|----------:| -| 'Build: NiceBTree' | 10000 | 10 | 2,037,851.45 ns | 60,065.478 ns | 3,292.392 ns | 35.1563 | 15.6250 | 644600 B | -| 'Build: MS HashDict' | 10000 | 10 | 1,647,876.61 ns | 105,992.785 ns | 5,809.822 ns | 37.1094 | 15.6250 | 640096 B | -| 'Build: MS SortedDict' | 10000 | 10 | 3,853,709.48 ns | 606,862.929 ns | 33,264.205 ns | 31.2500 | 11.7188 | 560112 B | -| 'Build: LangExt HashMap' | 10000 | 10 | 1,612,117.07 ns | 313,091.665 ns | 17,161.611 ns | 472.6563 | 154.2969 | 7919328 B | -| 'Build: LangExt Map' | 10000 | 10 | 5,363,298.26 ns | 582,836.469 ns | 31,947.234 ns | 507.8125 | 203.1250 | 8594784 B | -| 'Read: NiceBTree' | 10000 | 10 | 36.30 ns | 0.918 ns | 0.050 ns | - | - | - | -| 'Read: MS HashDict' | 10000 | 10 | 12.66 ns | 0.431 ns | 0.024 ns | - | - | - | -| 'Read: MS SortedDict' | 10000 | 10 | 233.59 ns | 26.034 ns | 1.427 ns | - | - | - | -| 'Read: LangExt HashMap' | 10000 | 10 | 28.61 ns | 0.254 ns | 0.014 ns | - | - | - | -| 'Read: LangExt Map' | 10000 | 10 | 268.13 ns | 3.301 ns | 0.181 ns | - | - | - | -| 'Iterate: NiceBTree' | 10000 | 10 | 12,630.95 ns | 759.122 ns | 41.610 ns | - | - | - | -| 'Iterate: MS HashDict' | 10000 | 10 | 151,314.44 ns | 22,323.733 ns | 1,223.639 ns | - | - | - | -| 'Iterate: MS SortedDict' | 10000 | 10 | 57,402.20 ns | 1,498.945 ns | 82.162 ns | - | - | - | -| 'Iterate: LangExt HashMap' | 10000 | 10 | 148,980.47 ns | 21,649.428 ns | 1,186.678 ns | 10.0098 | - | 170712 B | -| 'Iterate: LangExt Map' | 10000 | 10 | 34,428.07 ns | 5,647.779 ns | 309.574 ns | - | - | 32 B | -| 'Update: NiceBTree' | 10000 | 10 | 303.01 ns | 107.216 ns | 5.877 ns | 0.2027 | 0.0024 | 3392 B | -| 'Update: MS HashDict' | 10000 | 10 | 48.36 ns | 1.275 ns | 0.070 ns | 0.0100 | - | 168 B | -| 'Update: MS SortedDict' | 10000 | 10 | 137.47 ns | 33.015 ns | 1.810 ns | 0.0196 | - | 328 B | -| 'Update: LangExt HashMap' | 10000 | 10 | 102.57 ns | 7.196 ns | 0.394 ns | 0.0502 | 0.0001 | 840 B | -| 'Update: LangExt Map' | 10000 | 10 | 122.54 ns | 8.691 ns | 0.476 ns | 0.0186 | - | 312 B | -| 'Build: NiceBTree' | 10000 | 50 | 2,020,984.87 ns | 91,788.936 ns | 5,031.261 ns | 35.1563 | 11.7188 | 624248 B | -| 'Build: MS HashDict' | 10000 | 50 | 1,811,186.24 ns | 23,842.593 ns | 1,306.893 ns | 37.1094 | 15.6250 | 640096 B | -| 'Build: MS SortedDict' | 10000 | 50 | 3,883,214.25 ns | 198,364.955 ns | 10,873.053 ns | 31.2500 | 15.6250 | 560112 B | -| 'Build: LangExt HashMap' | 10000 | 50 | 1,784,616.64 ns | 248,685.113 ns | 13,631.270 ns | 472.6563 | 154.2969 | 7926712 B | -| 'Build: LangExt Map' | 10000 | 50 | 5,248,030.22 ns | 1,486,577.018 ns | 81,484.303 ns | 507.8125 | 203.1250 | 8544720 B | -| 'Read: NiceBTree' | 10000 | 50 | 40.64 ns | 0.574 ns | 0.031 ns | - | - | - | -| 'Read: MS HashDict' | 10000 | 50 | 29.91 ns | 2.182 ns | 0.120 ns | - | - | - | -| 'Read: MS SortedDict' | 10000 | 50 | 255.55 ns | 4.315 ns | 0.237 ns | - | - | - | -| 'Read: LangExt HashMap' | 10000 | 50 | 47.61 ns | 4.373 ns | 0.240 ns | - | - | - | -| 'Read: LangExt Map' | 10000 | 50 | 255.68 ns | 8.932 ns | 0.490 ns | - | - | - | -| 'Iterate: NiceBTree' | 10000 | 50 | 12,718.71 ns | 1,727.345 ns | 94.682 ns | - | - | - | -| 'Iterate: MS HashDict' | 10000 | 50 | 170,815.59 ns | 70,087.036 ns | 3,841.707 ns | - | - | - | -| 'Iterate: MS SortedDict' | 10000 | 50 | 68,982.58 ns | 9,267.855 ns | 508.002 ns | - | - | - | -| 'Iterate: LangExt HashMap' | 10000 | 50 | 144,442.27 ns | 96,636.820 ns | 5,296.990 ns | 9.7656 | - | 165600 B | -| 'Iterate: LangExt Map' | 10000 | 50 | 35,082.49 ns | 8,851.428 ns | 485.177 ns | - | - | 32 B | -| 'Update: NiceBTree' | 10000 | 50 |yy 393.56 ns | 101.149 ns | 5.544 ns | 0.2027 | 0.0024 | 3392 B | -| 'Update: MS HashDict' | 10000 | 50 | 114.57 ns | 17.939 ns | 0.983 ns | 0.0215 | - | 360 B | -| 'Update: MS SortedDict' | 10000 | 50 | 65.51 ns | 0.969 ns | 0.053 ns | 0.0129 | - | 216 B | -| 'Update: LangExt HashMap' | 10000 | 50 | 103.28 ns | 11.740 ns | 0.644 ns | 0.0535 | - | 896 B | -| 'Update: LangExt Map' | 10000 | 50 | 67.62 ns | 2.649 ns | 0.145 ns | 0.0119 | - | 200 B | +| Method | N | KeyLength | Mean | Gen0 | Gen1 | Allocated | +|--------------------------- |------ |---------- |----------------:|---------:|---------:|----------:| +| 'Build: NiceBTree' | 10000 | 10 | 2,037,851.45 ns | 35.1563 | 15.6250 | 644600 B | +| 'Build: MS HashDict' | 10000 | 10 | 1,647,876.61 ns | 37.1094 | 15.6250 | 640096 B | +| 'Build: MS SortedDict' | 10000 | 10 | 3,853,709.48 ns | 31.2500 | 11.7188 | 560112 B | +| 'Build: LangExt HashMap' | 10000 | 10 | 1,612,117.07 ns | 472.6563 | 154.2969 | 7919328 B | +| 'Build: LangExt Map' | 10000 | 10 | 5,363,298.26 ns | 507.8125 | 203.1250 | 8594784 B | +| 'Read: NiceBTree' | 10000 | 10 | 36.30 ns | - | - | - | +| 'Read: MS HashDict' | 10000 | 10 | 12.66 ns | - | - | - | +| 'Read: MS SortedDict' | 10000 | 10 | 233.59 ns | - | - | - | +| 'Read: LangExt HashMap' | 10000 | 10 | 28.61 ns | - | - | - | +| 'Read: LangExt Map' | 10000 | 10 | 268.13 ns | - | - | - | +| 'Iterate: NiceBTree' | 10000 | 10 | 12,630.95 ns | - | - | - | +| 'Iterate: MS HashDict' | 10000 | 10 | 151,314.44 ns | - | - | - | +| 'Iterate: MS SortedDict' | 10000 | 10 | 57,402.20 ns | - | - | - | +| 'Iterate: LangExt HashMap' | 10000 | 10 | 148,980.47 ns | 10.0098 | - | 170712 B | +| 'Iterate: LangExt Map' | 10000 | 10 | 34,428.07 ns | - | - | 32 B | +| 'Update: NiceBTree' | 10000 | 10 | 303.01 ns | 0.2027 | 0.0024 | 3392 B | +| 'Update: MS HashDict' | 10000 | 10 | 48.36 ns | 0.0100 | - | 168 B | +| 'Update: MS SortedDict' | 10000 | 10 | 137.47 ns | 0.0196 | - | 328 B | +| 'Update: LangExt HashMap' | 10000 | 10 | 102.57 ns | 0.0502 | 0.0001 | 840 B | +| 'Update: LangExt Map' | 10000 | 10 | 122.54 ns | 0.0186 | - | 312 B | +| 'Build: NiceBTree' | 10000 | 50 | 2,020,984.87 ns | 35.1563 | 11.7188 | 624248 B | +| 'Build: MS HashDict' | 10000 | 50 | 1,811,186.24 ns | 37.1094 | 15.6250 | 640096 B | +| 'Build: MS SortedDict' | 10000 | 50 | 3,883,214.25 ns | 31.2500 | 15.6250 | 560112 B | +| 'Build: LangExt HashMap' | 10000 | 50 | 1,784,616.64 ns | 472.6563 | 154.2969 | 7926712 B | +| 'Build: LangExt Map' | 10000 | 50 | 5,248,030.22 ns | 507.8125 | 203.1250 | 8544720 B | +| 'Read: NiceBTree' | 10000 | 50 | 40.64 ns | - | - | - | +| 'Read: MS HashDict' | 10000 | 50 | 29.91 ns | - | - | - | +| 'Read: MS SortedDict' | 10000 | 50 | 255.55 ns | - | - | - | +| 'Read: LangExt HashMap' | 10000 | 50 | 47.61 ns | - | - | - | +| 'Read: LangExt Map' | 10000 | 50 | 255.68 ns | - | - | - | +| 'Iterate: NiceBTree' | 10000 | 50 | 12,718.71 ns | - | - | - | +| 'Iterate: MS HashDict' | 10000 | 50 | 170,815.59 ns | - | - | - | +| 'Iterate: MS SortedDict' | 10000 | 50 | 68,982.58 ns | - | - | - | +| 'Iterate: LangExt HashMap' | 10000 | 50 | 144,442.27 ns | 9.7656 | - | 165600 B | +| 'Iterate: LangExt Map' | 10000 | 50 | 35,082.49 ns | - | - | 32 B | +| 'Update: NiceBTree' | 10000 | 50 | 393.56 ns | 0.2027 | 0.0024 | 3392 B | +| 'Update: MS HashDict' | 10000 | 50 | 114.57 ns | 0.0215 | - | 360 B | +| 'Update: MS SortedDict' | 10000 | 50 | 65.51 ns | 0.0129 | - | 216 B | +| 'Update: LangExt HashMap' | 10000 | 50 | 103.28 ns | 0.0535 | - | 896 B | +| 'Update: LangExt Map' | 10000 | 50 | 67.62 ns | 0.0119 | - | 200 B | #+end_src ** Architecture Notes: Key Strategies From ae3e8ae8c4837e362f32d1e381b5486bf2e20ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Sat, 18 Apr 2026 23:20:36 +0200 Subject: [PATCH 06/22] Changed stupid mistake in key strategies now strategies faster. go wroom. --- PersistentMap/KeyStrategies.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/PersistentMap/KeyStrategies.cs b/PersistentMap/KeyStrategies.cs index 1a437a8..2efb4ac 100644 --- a/PersistentMap/KeyStrategies.cs +++ b/PersistentMap/KeyStrategies.cs @@ -85,6 +85,8 @@ public struct UnicodeStrategy : IKeyStrategy // Essential for the < and > operators to work correctly. return packed ^ unchecked((long)0x8080808080808080); } + + public bool UsesPrefixes => true; } public struct IntStrategy : IKeyStrategy @@ -101,6 +103,10 @@ public struct IntStrategy : IKeyStrategy // though standard int shifting usually works fine for direct mapping. return (long)key << 32; } + + public bool UsesPrefixes => true; + + public bool IsLossless => true; } public struct DoubleStrategy : IKeyStrategy From 570a736606ce0bc703772f66e9f1100cee453dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Tue, 21 Apr 2026 08:41:59 +0200 Subject: [PATCH 07/22] Refactor key strategies --- PersistentMap/KeyStrategies/DoubleStrategy.cs | 32 +++++++ PersistentMap/KeyStrategies/IntStrategy.cs | 0 PersistentMap/KeyStrategies/PrefixScanner.cs | 95 +++++++++++++++++++ .../KeyStrategies/StandardStrategy.cs | 30 ++++++ 4 files changed, 157 insertions(+) create mode 100644 PersistentMap/KeyStrategies/DoubleStrategy.cs create mode 100644 PersistentMap/KeyStrategies/IntStrategy.cs create mode 100644 PersistentMap/KeyStrategies/PrefixScanner.cs create mode 100644 PersistentMap/KeyStrategies/StandardStrategy.cs diff --git a/PersistentMap/KeyStrategies/DoubleStrategy.cs b/PersistentMap/KeyStrategies/DoubleStrategy.cs new file mode 100644 index 0000000..e41425e --- /dev/null +++ b/PersistentMap/KeyStrategies/DoubleStrategy.cs @@ -0,0 +1,32 @@ + +public struct DoubleStrategy : IKeyStrategy +{ + public bool IsLossless => true; + // Use the standard comparison for the fallback/refine step + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Compare(double x, double y) => x.CompareTo(y); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long GetPrefix(double key) + { + // 1. Bit Cast to Long (0 cost) + long bits = Unsafe.As(ref key); + + // 2. The Magic Twist + // If the sign bit (MSB) is set (negative), we flip ALL bits. + // If the sign bit is clear (positive), we flip ONLY the sign bit. + // This maps: + // -Negative Max -> 0 + // -0 -> Midpoint + // +Negative Max -> Max + + long mask = (bits >> 63); // 0 for positive, -1 (All 1s) for negative + + // If negative: bits ^ -1 = ~bits (Flip All) + // If positive: bits ^ 0 = bits (Flip None) + // Then we toggle the sign bit (0x8000...) to shift the range to signed long. + + return (bits ^ (mask & 0x7FFFFFFFFFFFFFFF)) ^ unchecked((long)0x8000000000000000); + } +} + diff --git a/PersistentMap/KeyStrategies/IntStrategy.cs b/PersistentMap/KeyStrategies/IntStrategy.cs new file mode 100644 index 0000000..e69de29 diff --git a/PersistentMap/KeyStrategies/PrefixScanner.cs b/PersistentMap/KeyStrategies/PrefixScanner.cs new file mode 100644 index 0000000..7ad9de1 --- /dev/null +++ b/PersistentMap/KeyStrategies/PrefixScanner.cs @@ -0,0 +1,95 @@ +/// +/// Helper for SIMD accelerated prefix scanning. +/// +public static class PrefixScanner +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FindFirstGreaterOrEqual(ReadOnlySpan prefixes, long targetPrefix) + { + + // Fallback for short arrays or unsupported hardware + if (!Avx2.IsSupported || prefixes.Length < 4) + return LinearScan(prefixes, targetPrefix); + + return Avx512F.IsSupported + ? ScanAvx512(prefixes, targetPrefix) + : ScanAvx2(prefixes, targetPrefix); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LinearScan(ReadOnlySpan prefixes, long target) + { + for (var i = 0; i < prefixes.Length; i++) + if (prefixes[i] >= target) + return i; + return prefixes.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe int ScanAvx2(ReadOnlySpan prefixes, long target) + { + // Create a vector where every element is the target prefix + var vTarget = Vector256.Create(target); + var i = 0; + var len = prefixes.Length; + + // Process 4 longs at a time (256 bits) + for (; i <= len - 4; i += 4) + fixed (long* ptr = prefixes) + { + var vData = Avx2.LoadVector256(ptr + i); + + // Compare: result is -1 (all 1s) if true, 0 if false + // We want Data >= Target. + // AVX2 CompareGreaterThan is for signed. Longs should be treated carefully, + // but for text prefixes (positive), signed compare is usually sufficient. + // Effectively: !(Data < Target) could be safer if signs vary, + // but here we assume prefixes are derived from unsigned chars. + // Standard AVX2 hack for CompareGreaterOrEqual (Signed): + // No native _mm256_cmpge_epi64 in AVX2. + // Use CompareGreaterThan(Data, Target - 1) + var vResult = Avx2.CompareGreaterThan(vData, Vector256.Create(target - 1)); + + var mask = Avx2.MoveMask(vResult.AsByte()); + + if (mask != 0) + { + // Identify the first set bit corresponding to a 64-bit element + // MoveMask returns 32 bits (1 per byte). Each long is 8 bytes. + // We check bits 0, 8, 16, 24. + if ((mask & 0xFF) != 0) return i + 0; + if ((mask & 0xFF00) != 0) return i + 1; + if ((mask & 0xFF0000) != 0) return i + 2; + return i + 3; + } + } + + return LinearScan(prefixes.Slice(i), target) + i; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe int ScanAvx512(ReadOnlySpan prefixes, long target) + { + var vTarget = Vector512.Create(target); + var i = 0; + var len = prefixes.Length; + + for (; i <= len - 8; i += 8) + fixed (long* ptr = prefixes) + { + var vData = Avx512F.LoadVector512(ptr + i); + // AVX512 has dedicated Compare Greater Than or Equal Long + var mask = Avx512F.CompareGreaterThanOrEqual(vData, vTarget); + + if (mask != Vector512.Zero) + { + // Extract most significant bit mask + var m = mask.ExtractMostSignificantBits(); + // Count trailing zeros to find the index + return i + BitOperations.TrailingZeroCount(m); + } + } + + return LinearScan(prefixes.Slice(i), target) + i; + } +} diff --git a/PersistentMap/KeyStrategies/StandardStrategy.cs b/PersistentMap/KeyStrategies/StandardStrategy.cs new file mode 100644 index 0000000..835b661 --- /dev/null +++ b/PersistentMap/KeyStrategies/StandardStrategy.cs @@ -0,0 +1,30 @@ + +/// +/// A universal key strategy for any type that relies on standard comparisons +/// (IComparable, IComparer, or custom StringComparers) without SIMD prefixes. +/// +public readonly struct StandardStrategy : IKeyStrategy +{ + private readonly IComparer _comparer; + + // If no comparer is provided, it defaults to Comparer.Default + // which automatically uses IComparable if the type implements it. + public StandardStrategy(IComparer? comparer = null) + { + _comparer = comparer ?? Comparer.Default; + } + + // Tell the B-Tree to skip SIMD routing and just use LinearSearch + public bool UsesPrefixes => false; + + // This will never be called because UsesPrefixes is false, + // but we must satisfy the interface. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long GetPrefix(K key) => 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Compare(K x, K y) + { + return _comparer.Compare(x, y); + } +} From 9242c1c751e586f25508d83724335442f3850820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Wed, 22 Apr 2026 15:55:33 +0200 Subject: [PATCH 08/22] perf: Optimize non-prefix key strategies and memory usage - Conditionally allocate prefix buffers in `LeafNode` and introduce `PrefixInternalNode` to reduce memory overhead when prefixes are disabled. - Bypass prefix calculation and logic entirely when `UsesPrefixes` is false. - Add a binary search fallback for key scanning. - Implement a dedicated `int` scanning fast-path, removing SIMD prefix usage from `IntStrategy`. - Reorganize key strategies into separate files. - Add a new benchmark project specifically for string keys. --- NiceBtree.sln | 7 + PersistentMap/BTreeFunctions.cs | 519 ++++++--------- PersistentMap/BaseOrderedMap.cs | 4 +- PersistentMap/KeyStrategies.cs | 184 +----- PersistentMap/KeyStrategies/DoubleStrategy.cs | 2 + PersistentMap/KeyStrategies/IntStrategy.cs | 16 + PersistentMap/KeyStrategies/PrefixScanner.cs | 6 + .../KeyStrategies/StandardStrategy.cs | 11 +- PersistentMap/Nodes.cs | 118 ++-- PersistentMap/PersistentMap.cs | 2 +- PersistentMap/Readme.org | 622 ++++++++++++++---- TestProject1/StandardStrategy.cs | 38 ++ benchmarks/MyBenchMarks/StringBenchmarks.cs | 445 +++++++++++++ 13 files changed, 1297 insertions(+), 677 deletions(-) create mode 100644 TestProject1/StandardStrategy.cs create mode 100644 benchmarks/MyBenchMarks/StringBenchmarks.cs diff --git a/NiceBtree.sln b/NiceBtree.sln index 51a8511..4da8777 100644 --- a/NiceBtree.sln +++ b/NiceBtree.sln @@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgainstImmutableDict", "ben EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgainstLanguageExt", "benchmarks\AgainstLanguageExt\AgainstLanguageExt.csproj", "{6C16526B-5139-4EA3-BF74-E6320F467198}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyBenchMarks", "benchmarks\MyBenchMarks\MyBenchMarks.csproj", "{769E1CEA-7E01-405B-80A2-95CBF432A2BA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,7 @@ Global {CA49AA3C-0CE6-4735-887F-FB3631D63CEE} = {B0432C7A-80E2-4EA6-8FAB-B8F23A8C39DE} {13304F19-7ED3-4C40-9A08-46D539667D50} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53} {6C16526B-5139-4EA3-BF74-E6320F467198} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53} + {769E1CEA-7E01-405B-80A2-95CBF432A2BA} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {CA49AA3C-0CE6-4735-887F-FB3631D63CEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -39,5 +42,9 @@ Global {6C16526B-5139-4EA3-BF74-E6320F467198}.Debug|Any CPU.Build.0 = Debug|Any CPU {6C16526B-5139-4EA3-BF74-E6320F467198}.Release|Any CPU.ActiveCfg = Release|Any CPU {6C16526B-5139-4EA3-BF74-E6320F467198}.Release|Any CPU.Build.0 = Release|Any CPU + {769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/PersistentMap/BTreeFunctions.cs b/PersistentMap/BTreeFunctions.cs index 9c44c18..a039e75 100644 --- a/PersistentMap/BTreeFunctions.cs +++ b/PersistentMap/BTreeFunctions.cs @@ -13,7 +13,6 @@ namespace PersistentMap public static bool TryGetValue(Node root, K key, TStrategy strategy, out V value) where TStrategy : IKeyStrategy { - // 1. Calculate ONCE long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; Node current = root; @@ -22,7 +21,6 @@ namespace PersistentMap if (current.IsLeaf) { var leaf = current.AsLeaf(); - // Leaf uses standard FindIndex (Lower Bound) to find exact match int index = FindIndex(leaf, key, keyPrefix, strategy); if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0) { @@ -34,7 +32,6 @@ namespace PersistentMap } else { - // FIX: Internal uses FindRoutingIndex (Upper Bound) var internalNode = current.AsInternal(); int index = FindRoutingIndex(internalNode, key, keyPrefix, strategy); current = internalNode.Children[index]!; @@ -42,7 +39,6 @@ namespace PersistentMap } } - // Public API public static Node Set(Node root, K key, V value, IKeyStrategy strategy, OwnerId owner, out bool countChanged) { root = root.EnsureEditable(owner); @@ -51,13 +47,19 @@ namespace PersistentMap if (splitResult != null) { - var newRoot = new InternalNode(owner); - newRoot.Children[0] = root; + var newRoot = strategy.UsesPrefixes + ? new PrefixInternalNode(owner) + : new InternalNode(owner); + newRoot.Keys[0] = splitResult.Separator; + newRoot.Children[0] = root; newRoot.Children[1] = splitResult.NewNode; newRoot.SetCount(1); + if (strategy.UsesPrefixes) + { newRoot.AllPrefixes[0] = strategy.GetPrefix(splitResult.Separator); + } return newRoot; } @@ -65,10 +67,8 @@ namespace PersistentMap return root; } - // Recursive Helper private static SplitResult? InsertRecursive(Node node, K key, V value, IKeyStrategy strategy, OwnerId owner, out bool added) { - // 1. Calculate ONCE long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; if (node.IsLeaf) @@ -79,11 +79,11 @@ namespace PersistentMap if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0) { leaf.Values[index] = value; - added = false; // Key existed, value updated. Count does not change. + added = false; return null; } - added = true; // New key. Count +1. + added = true; if (leaf.Header.Count < LeafNode.Capacity) { InsertIntoLeaf(leaf, index, key, value, strategy); @@ -120,9 +120,8 @@ namespace PersistentMap } } - // Public API public static Node Remove(Node root, K key, TStrategy strategy, OwnerId owner, out bool countChanged) - where TStrategy : IKeyStrategy + where TStrategy : IKeyStrategy { root = root.EnsureEditable(owner); @@ -143,11 +142,9 @@ namespace PersistentMap return root; } - // Recursive Helper private static bool RemoveRecursive(Node node, K key, TStrategy strategy, OwnerId owner, out bool removed) - where TStrategy : IKeyStrategy + where TStrategy : IKeyStrategy { - // 1. Calculate ONCE long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; if (node.IsLeaf) @@ -158,11 +155,11 @@ namespace PersistentMap if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0) { RemoveFromLeaf(leaf, index, strategy); - removed = true; // Item removed. Count -1. - return leaf.Header.Count .MergeThreshold; + removed = true; + return leaf.Header.Count < LeafNode.MergeThreshold; } - removed = false; // Item not found. + removed = false; return false; } else @@ -187,101 +184,129 @@ namespace PersistentMap // Internal Helpers: Search // --------------------------------------------------------- - // Used by Leaf Nodes: Finds the first key >= searchKey (Lower Bound) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static int FindIndex(Node node, K key, long keyPrefix, TStrategy strategy) where TStrategy : IKeyStrategy { + if (typeof(K) == typeof(int)) + { + Span keys = node.GetKeys(); + ref K firstKeyRef = ref MemoryMarshal.GetReference(keys); + ref int firstIntRef = ref Unsafe.As(ref firstKeyRef); + ReadOnlySpan intKeys = MemoryMarshal.CreateReadOnlySpan(ref firstIntRef, keys.Length); + int intKey = Unsafe.As(ref key); + return IntScanner.FindFirstGreaterOrEqual(intKeys, intKey); + } + if (strategy.UsesPrefixes) { - // Use the pre-calculated prefix here! int index = PrefixScanner.FindFirstGreaterOrEqual(node.Prefixes, keyPrefix); return RefineSearch(index, node.GetKeys(), key, strategy); } - return LinearSearchKeys(node.GetKeys(), key, strategy); + return FallbackSearchKeys(node.GetKeys(), key, strategy); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LinearSearchKeys(Span keys, K key, TStrategy strategy) - where TStrategy : IKeyStrategy - { - int i = 0; - // Standard linear scan on the keys array - while (i < keys.Length && strategy.Compare(keys[i], key) < 0) - { - i++; - } - return i; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int RefineSearch(int startIndex, Span keys, K key, TStrategy strategy) - where TStrategy : IKeyStrategy - { - int i = startIndex; - // JIT can now inline 'strategy.Compare' here! - while (i < keys.Length && strategy.Compare(keys[i], key) < 0) - { - i++; - } - return i; - } - - // Used by Internal Nodes: Finds the child index to descend into. - // If Key == Separator, we must go RIGHT (index + 1), so we need (Upper Bound). - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static int FindRoutingIndex(InternalNode node, K key, long keyPrefix, TStrategy strategy) where TStrategy : IKeyStrategy { if (!strategy.UsesPrefixes) { - return LinearSearchRouting(node.GetKeys(), key, strategy); + return FallbackRoutingKeys(node.GetKeys(), key, strategy); } - // Use the pre-calculated prefix here! int index = PrefixScanner.FindFirstGreaterOrEqual(node.Prefixes, keyPrefix); - return RefineRouting(index, node.Keys, node.Header.Count, key, strategy); + return RefineRouting(index, node.GetKeys(), key, strategy); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LinearSearchRouting(Span keys, K key, TStrategy strategy) - where TStrategy : IKeyStrategy - { - int i = 0; - // Routing: Skip everything that is LessOrEqual. - // We stop at the first item that is Greater. - while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) - { - i++; - } - return i; - } - - // Overload for primitive types (avoids IComparer call overhead in fallback) - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LinearSearchRouting(Span keys, T key) where T : struct, IComparable - { - int i = 0; - while (i < keys.Length && keys[i].CompareTo(key) <= 0) i++; - return i; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int RefineRouting(int startIndex, K[] keys, int count, K key, TStrategy strategy) - where TStrategy : IKeyStrategy + private static int RefineSearch(int startIndex, ReadOnlySpan keys, K key, TStrategy strategy) + where TStrategy : IKeyStrategy { int i = startIndex; - // DIFFERENCE: We continue past valid matches. - // We want the first key STRICTLY GREATER than target. - // If keys[i] == key, we increment (go to right child). - while (i < count && strategy.Compare(keys[i], key) <= 0) - { - i++; - } + while (i < keys.Length && strategy.Compare(keys[i], key) < 0) i++; return i; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int RefineRouting(int startIndex, ReadOnlySpan keys, K key, TStrategy strategy) + where TStrategy : IKeyStrategy + { + int i = startIndex; + while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) i++; + return i; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int FallbackSearchKeys(ReadOnlySpan keys, K key, TStrategy strategy) + where TStrategy : IKeyStrategy + { + return strategy.UseBinarySearch + ? BinarySearchKeys(keys, key, strategy) + : LinearSearchKeys(keys, key, strategy); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int FallbackRoutingKeys(ReadOnlySpan keys, K key, TStrategy strategy) + where TStrategy : IKeyStrategy + { + return strategy.UseBinarySearch + ? BinaryRoutingKeys(keys, key, strategy) + : LinearRoutingKeys(keys, key, strategy); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LinearSearchKeys(ReadOnlySpan keys, K key, TStrategy strategy) + where TStrategy : IKeyStrategy + { + int i = 0; + while (i < keys.Length && strategy.Compare(keys[i], key) < 0) i++; + return i; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LinearRoutingKeys(ReadOnlySpan keys, K key, TStrategy strategy) + where TStrategy : IKeyStrategy + { + int i = 0; + while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) i++; + return i; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int BinarySearchKeys(ReadOnlySpan keys, K key, TStrategy strategy) + where TStrategy : IKeyStrategy + { + int low = 0; + int high = keys.Length - 1; + while (low <= high) + { + int mid = low + ((high - low) >> 1); + int cmp = strategy.Compare(keys[mid], key); + if (cmp == 0) return mid; + if (cmp < 0) low = mid + 1; + else high = mid - 1; + } + return low; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int BinaryRoutingKeys(ReadOnlySpan keys, K key, TStrategy strategy) + where TStrategy : IKeyStrategy + { + int low = 0; + int high = keys.Length - 1; + while (low <= high) + { + int mid = low + ((high - low) >> 1); + int cmp = strategy.Compare(keys[mid], key); + if (cmp <= 0) low = mid + 1; + else high = mid - 1; + } + return low; + } + // --------------------------------------------------------- // Insertion Logic // --------------------------------------------------------- @@ -290,226 +315,137 @@ namespace PersistentMap { public Node NewNode; public K Separator; - public SplitResult(Node newNode, K separator) { NewNode = newNode; Separator = separator; } + public SplitResult(Node newNode, K separator) + { + NewNode = newNode; + Separator = separator; + } } - private static SplitResult? InsertRecur2sive(Node node, K key, V value, TStrategy strategy, OwnerId owner) - where TStrategy : IKeyStrategy - { - - // 1. Calculate ONCE - long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; - - - // --- LEAF CASE --- - if (node.IsLeaf) - { - var leaf = node.AsLeaf(); - // Leaf uses FindIndex - int index = FindIndex(leaf, key, keyPrefix, strategy); - - if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0) - { - leaf.Values[index] = value; - return null; - } - - if (leaf.Header.Count < LeafNode.Capacity) - { - InsertIntoLeaf(leaf, index, key, value, strategy); - return null; - } - else - { - return SplitLeaf(leaf, index, key, value, strategy, owner); - } - } - - // --- INTERNAL CASE --- - var internalNode = node.AsInternal(); - - // FIX: Internal uses FindRoutingIndex - int childIndex = FindRoutingIndex(internalNode, key, keyPrefix, strategy); - - var child = internalNode.Children[childIndex]!.EnsureEditable(owner); - internalNode.Children[childIndex] = child; - - var split = InsertRecursive(child, key, value, strategy, owner, out _); - - if (split != null) - { - // ... checks ... - // Use childIndex here - if (internalNode.Header.Count < InternalNode.Capacity - 1) - { - InsertIntoInternal(internalNode, childIndex, split.Separator, split.NewNode, strategy); - return null; - } - else - { - return SplitInternal(internalNode, childIndex, split.Separator, split.NewNode, strategy, owner); - } - } - return null; - } private static void InsertIntoLeaf(LeafNode leaf, int index, K key, V value, TStrategy strategy) - where TStrategy : IKeyStrategy + where TStrategy : IKeyStrategy { int count = leaf.Header.Count; if (index < count) { int moveCount = count - index; - // Fast Span memory moves leaf.Keys.AsSpan(index, moveCount).CopyTo(leaf.Keys.AsSpan(index + 1)); leaf.Values.AsSpan(index, moveCount).CopyTo(leaf.Values.AsSpan(index + 1)); if (strategy.UsesPrefixes) { - leaf.AllPrefixes.Slice(index, count - index) - .CopyTo(leaf.AllPrefixes.Slice(index + 1)); + leaf.AllPrefixes.Slice(index, moveCount).CopyTo(leaf.AllPrefixes.Slice(index + 1)); } } leaf.Keys[index] = key; - - // This fails if leaf.Values is a Span of length 'count' leaf.Values[index] = value; + if (strategy.UsesPrefixes) - leaf.AllPrefixes![index] = strategy.GetPrefix(key); + { + leaf.AllPrefixes[index] = strategy.GetPrefix(key); + } leaf.SetCount(count + 1); } + private static SplitResult SplitLeaf(LeafNode left, int insertIndex, K key, V value, TStrategy strategy, OwnerId owner) - where TStrategy : IKeyStrategy + where TStrategy : IKeyStrategy { - var right = new LeafNode(owner); + var right = new LeafNode(owner, strategy.UsesPrefixes); int totalCount = left.Header.Count; - // Heuristics - int splitPoint; - if (insertIndex == totalCount) splitPoint = totalCount; // Append: Keep all in Left (90/10 logic effectively) - else if (insertIndex == 0) splitPoint = 0; // Prepend: Right gets all - else splitPoint = totalCount / 2; - - // Move items to Right + int splitPoint = (insertIndex == totalCount) ? totalCount : (insertIndex == 0 ? 0 : totalCount / 2); int moveCount = totalCount - splitPoint; + if (moveCount > 0) { - // Fast Span memory moves left.Keys.AsSpan(splitPoint, moveCount).CopyTo(right.Keys.AsSpan(0)); left.Values.AsSpan(splitPoint, moveCount).CopyTo(right.Values.AsSpan(0)); - // Manually copy prefixes if needed or re-calculate + if (strategy.UsesPrefixes) - for (int i = 0; i < moveCount; i++) right.AllPrefixes[i] = left.AllPrefixes[splitPoint + i]; + { + left.AllPrefixes.Slice(splitPoint, moveCount).CopyTo(right.AllPrefixes); + } } - // Update Counts left.SetCount(splitPoint); right.SetCount(moveCount); - // Insert the New Item into the correct node if (insertIndex < splitPoint || (splitPoint == 0 && insertIndex == 0)) - { - InsertIntoLeaf(left, insertIndex, key, value, strategy); - } - else - { - InsertIntoLeaf(right, insertIndex - splitPoint, key, value, strategy); - } +{ + InsertIntoLeaf(left, insertIndex, key, value, strategy); +} +else +{ + InsertIntoLeaf(right, insertIndex - splitPoint, key, value, strategy); +} - - // In B+ Tree, the separator is the first key of the right node return new SplitResult(right, right.Keys[0]); } private static void InsertIntoInternal(InternalNode node, int index, K separator, Node newChild, TStrategy strategy) - where TStrategy : IKeyStrategy + where TStrategy : IKeyStrategy { int count = node.Header.Count; - // Shift Keys and Prefixes if (index < count) { - int moveCount = count - index; + Span keysSpan = node.Keys; + keysSpan.Slice(index, moveCount).CopyTo(keysSpan.Slice(index + 1)); + + Span> childrenSpan = node.Children; + childrenSpan.Slice(index + 1, moveCount).CopyTo(childrenSpan.Slice(index + 2)); - // Fast Span memory moves - node.Keys.AsSpan(index, moveCount).CopyTo(node.Keys.AsSpan(index + 1)); - - // FIX: Shift raw prefix array if (strategy.UsesPrefixes) { - node.AllPrefixes.Slice(index, count - index) - .CopyTo(node.AllPrefixes.Slice(index + 1)); + node.AllPrefixes.Slice(index, moveCount).CopyTo(node.AllPrefixes.Slice(index + 1)); } } - // Shift Children - // Children buffer is indexable like an array but requires manual loop or Unsafe copy - // if we don't want to use unsafe pointers. - // Since it's a small struct buffer (size 33), a loop is fine/fast. - for (int i = count + 1; i > index + 1; i--) + node.Keys[index] = separator; + node.Children[index + 1] = newChild; + + if (strategy.UsesPrefixes) { - node.Children[i] = node.Children[i - 1]; + node.AllPrefixes[index] = strategy.GetPrefix(separator); } - node.Keys[index] = separator; - - // FIX: Write to raw array - if (strategy.UsesPrefixes) - node.AllPrefixes![index] = strategy.GetPrefix(separator); - - node.Children[index + 1] = newChild; node.SetCount(count + 1); } private static SplitResult SplitInternal(InternalNode left, int insertIndex, K separator, Node newChild, TStrategy strategy, OwnerId owner) - where TStrategy : IKeyStrategy + where TStrategy : IKeyStrategy { - var right = new InternalNode(owner); + var right = strategy.UsesPrefixes + ? new PrefixInternalNode(owner) + : new InternalNode(owner); + int count = left.Header.Count; - int splitPoint = count / 2; // Internal nodes usually split 50/50 to keep tree fat - - // The key at splitPoint moves UP to become the separator. - // Keys > splitPoint move to Right. - + int splitPoint = count / 2; K upKey = left.Keys[splitPoint]; + int moveCount = count - splitPoint - 1; - // Move Keys/Prefixes to Right - int moveCount = count - splitPoint - 1; // -1 because splitPoint key goes up - // Fast Span memory moves if (moveCount > 0) { - left.Keys.AsSpan(splitPoint + 1, moveCount).CopyTo(right.Keys.AsSpan(0)); + Span leftKeys = left.Keys; + Span rightKeys = right.Keys; + leftKeys.Slice(splitPoint + 1, moveCount).CopyTo(rightKeys); + + Span> leftChildren = left.Children; + Span> rightChildren = right.Children; + leftChildren.Slice(splitPoint + 1, moveCount + 1).CopyTo(rightChildren); if (strategy.UsesPrefixes) { - left.AllPrefixes.Slice(splitPoint + 1, moveCount).CopyTo(right.AllPrefixes.Slice(0)); + left.AllPrefixes.Slice(splitPoint + 1, moveCount).CopyTo(right.AllPrefixes); } } - // Left has children 0..splitPoint. Right has children splitPoint+1..End - for (int i = 0; i <= moveCount; i++) - { - right.Children[i] = left.Children[splitPoint + 1 + i]; - } left.SetCount(splitPoint); right.SetCount(moveCount); - // Determine where to insert the new Separator/Child - // Note: We extracted 'upKey' from the original array. - // We now have to compare the *incoming* separator with 'upKey' - // to see if it goes Left or Right. - - if (insertIndex == splitPoint) - { - // Special case: The new key is exactly the one pushing up? - // Usually easier to insert into temp buffer and split, - // but here we can branch: - // If insertIndex <= splitPoint, insert left. Else right. - } - - // Simplified insertion into split nodes: if (insertIndex <= splitPoint) { InsertIntoInternal(left, insertIndex, separator, newChild, strategy); @@ -526,26 +462,19 @@ namespace PersistentMap // Removal Logic // --------------------------------------------------------- - // --------------------------------------------------------- - // Removal Logic (Fixed Type Inference & Casting) - // --------------------------------------------------------- - - private static void RemoveFromLeaf(LeafNode leaf, int index, TStrategy strategy) - where TStrategy : IKeyStrategy + where TStrategy : IKeyStrategy { int count = leaf.Header.Count; int moveCount = count - index - 1; if (moveCount > 0) { - // Fast Span memory moves leaf.Keys.AsSpan(index + 1, moveCount).CopyTo(leaf.Keys.AsSpan(index)); leaf.Values.AsSpan(index + 1, moveCount).CopyTo(leaf.Values.AsSpan(index)); if (strategy.UsesPrefixes) { - // Replaced manual 'for' loop with native slice copy leaf.AllPrefixes.Slice(index + 1, moveCount).CopyTo(leaf.AllPrefixes.Slice(index)); } } @@ -553,11 +482,9 @@ namespace PersistentMap leaf.SetCount(count - 1); } - // FIX 3: Added to HandleUnderflow private static bool HandleUnderflow(InternalNode parent, int childIndex, TStrategy strategy, OwnerId owner) - where TStrategy : IKeyStrategy + where TStrategy : IKeyStrategy { - // Try to borrow from Right Sibling if (childIndex < parent.Header.Count) { var rightSibling = parent.Children[childIndex + 1]!.EnsureEditable(owner); @@ -575,7 +502,6 @@ namespace PersistentMap return parent.Header.Count < LeafNode.MergeThreshold; } } - // Try to borrow from Left Sibling else if (childIndex > 0) { var leftSibling = parent.Children[childIndex - 1]!.EnsureEditable(owner); @@ -589,7 +515,6 @@ namespace PersistentMap } else { - // Merge Left and Current. Note separator index is 'childIndex - 1' Merge(parent, childIndex - 1, leftSibling, rightChild, strategy); return parent.Header.Count < LeafNode.MergeThreshold; } @@ -600,15 +525,12 @@ namespace PersistentMap private static bool CanBorrow(Node node) { - // Note: LeafNode.MergeThreshold is constant 8, so we can access it statically or via 8 return node.Header.Count > 8 + 1; } - // FIX 4: Added to Merge/Rotate so we can cast to LeafNode successfully. private static void Merge(InternalNode parent, int separatorIndex, Node left, Node right, TStrategy strategy) - where TStrategy : IKeyStrategy + where TStrategy : IKeyStrategy { - // Case A: Merging Leaves if (left.IsLeaf) { var leftLeaf = left.AsLeaf(); @@ -616,117 +538,113 @@ namespace PersistentMap int lCount = leftLeaf.Header.Count; int rCount = rightLeaf.Header.Count; + rightLeaf.Keys.AsSpan(0, rCount).CopyTo(leftLeaf.Keys.AsSpan(lCount)); rightLeaf.Values.AsSpan(0, rCount).CopyTo(leftLeaf.Values.AsSpan(lCount)); + if (strategy.UsesPrefixes) { - rightLeaf.AllPrefixes.Slice(0, rCount) - .CopyTo(leftLeaf.AllPrefixes.Slice(lCount)); + rightLeaf.AllPrefixes.Slice(0, rCount).CopyTo(leftLeaf.AllPrefixes.Slice(lCount)); } leftLeaf.SetCount(lCount + rCount); } - // Case B: Merging Internal Nodes else { var leftInternal = left.AsInternal(); var rightInternal = right.AsInternal(); - // Pull separator from parent K separator = parent.Keys[separatorIndex]; int lCount = leftInternal.Header.Count; leftInternal.Keys[lCount] = separator; + if (strategy.UsesPrefixes) + { leftInternal.AllPrefixes[lCount] = strategy.GetPrefix(separator); + } int rCount = rightInternal.Header.Count; - rightInternal.Keys.AsSpan(0, rCount).CopyTo(leftInternal.Keys.AsSpan(lCount + 1)); + Span rightKeys = rightInternal.Keys; + Span leftKeys = leftInternal.Keys; + rightKeys.Slice(0, rCount).CopyTo(leftKeys.Slice(lCount + 1)); + if (strategy.UsesPrefixes) { - rightInternal.AllPrefixes.Slice(0, rCount) - .CopyTo(leftInternal.AllPrefixes.Slice(lCount + 1)); + rightInternal.AllPrefixes.Slice(0, rCount).CopyTo(leftInternal.AllPrefixes.Slice(lCount + 1)); } - for (int i = 0; i <= rCount; i++) - { - leftInternal.Children[lCount + 1 + i] = rightInternal.Children[i]; - } + Span> rightChildren = rightInternal.Children; + Span> leftChildren = leftInternal.Children; + rightChildren.Slice(0, rCount + 1).CopyTo(leftChildren.Slice(lCount + 1)); leftInternal.SetCount(lCount + 1 + rCount); } - // Remove Separator and Right Child from Parent int pCount = parent.Header.Count; int moveCount = pCount - separatorIndex - 1; if (moveCount > 0) { - parent.Keys.AsSpan(separatorIndex + 1, moveCount).CopyTo(parent.Keys.AsSpan(separatorIndex)); + Span parentKeys = parent.Keys; + parentKeys.Slice(separatorIndex + 1, moveCount).CopyTo(parentKeys.Slice(separatorIndex)); if (strategy.UsesPrefixes) { - // Replaced manual 'for' loop with native slice copy parent.AllPrefixes.Slice(separatorIndex + 1, moveCount).CopyTo(parent.AllPrefixes.Slice(separatorIndex)); } - } - for (int i = separatorIndex + 2; i <= pCount; i++) - { - parent.Children[i - 1] = parent.Children[i]; + Span> parentChildren = parent.Children; + parentChildren.Slice(separatorIndex + 2, moveCount).CopyTo(parentChildren.Slice(separatorIndex + 1)); } parent.SetCount(pCount - 1); } private static void RotateLeft(InternalNode parent, int separatorIndex, Node left, Node right, TStrategy strategy) - where TStrategy : IKeyStrategy + where TStrategy : IKeyStrategy { - // Move one item from Right to Left if (left.IsLeaf) { var leftLeaf = left.AsLeaf(); var rightLeaf = right.AsLeaf(); - // Move first of right to end of left InsertIntoLeaf(leftLeaf, leftLeaf.Header.Count, rightLeaf.Keys[0], rightLeaf.Values[0], strategy); RemoveFromLeaf(rightLeaf, 0, strategy); - // Update Parent Separator parent.Keys[separatorIndex] = rightLeaf.Keys[0]; if (strategy.UsesPrefixes) + { parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]); + } } else { var leftInternal = left.AsInternal(); var rightInternal = right.AsInternal(); - // 1. Move Parent Separator to Left End K sep = parent.Keys[separatorIndex]; InsertIntoInternal(leftInternal, leftInternal.Header.Count, sep, rightInternal.Children[0]!, strategy); - // 2. Move Right[0] Key to Parent parent.Keys[separatorIndex] = rightInternal.Keys[0]; if (strategy.UsesPrefixes) + { parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightInternal.Keys[0]); + } - // 3. Fix Right (Remove key 0 and shift child 0 out) - // We basically remove key at 0. Child 0 was moved to left. Child 1 becomes Child 0. - // Re-using Remove logic implies shifts. - // Manual shift for performance: int rCount = rightInternal.Header.Count; - // Shift children - for (int i = 0; i < rCount; i++) rightInternal.Children[i] = rightInternal.Children[i + 1]; + Span> rightChildren = rightInternal.Children; + rightChildren.Slice(1, rCount).CopyTo(rightChildren); + if (rCount > 1) { - // Fast Span memory moves (Replaces Array.Copy & manual loop) - rightInternal.Keys.AsSpan(1, rCount - 1).CopyTo(rightInternal.Keys.AsSpan(0)); + Span rightKeys = rightInternal.Keys; + rightKeys.Slice(1, rCount - 1).CopyTo(rightKeys); if (strategy.UsesPrefixes) { - rightInternal.AllPrefixes.Slice(1, rCount - 1).CopyTo(rightInternal.AllPrefixes.Slice(0)); + rightInternal.AllPrefixes.Slice(1, rCount - 1).CopyTo(rightInternal.AllPrefixes); } } @@ -735,9 +653,8 @@ namespace PersistentMap } private static void RotateRight(InternalNode parent, int separatorIndex, Node left, Node right, TStrategy strategy) - where TStrategy : IKeyStrategy + where TStrategy : IKeyStrategy { - // Move one item from Left to Right if (left.IsLeaf) { var leftLeaf = left.AsLeaf(); @@ -749,31 +666,29 @@ namespace PersistentMap parent.Keys[separatorIndex] = rightLeaf.Keys[0]; if (strategy.UsesPrefixes) - parent.AllPrefixes![separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]); + { + parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]); + } } else { - var leftInternal = (InternalNode)left; - var rightInternal = (InternalNode)right; + var leftInternal = left.AsInternal(); + var rightInternal = right.AsInternal(); int last = leftInternal.Header.Count - 1; - // 1. Move Parent Separator to Right Start K sep = parent.Keys[separatorIndex]; - // The child moving to right is the *last* child of left (index count) InsertIntoInternal(rightInternal, 0, sep, leftInternal.Children[last + 1]!, strategy); - // 2. Move Left[last] Key to Parent parent.Keys[separatorIndex] = leftInternal.Keys[last]; if (strategy.UsesPrefixes) - parent.AllPrefixes![separatorIndex] = strategy.GetPrefix(leftInternal.Keys[last]); + { + parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(leftInternal.Keys[last]); + } - // 3. Truncate Left leftInternal.SetCount(last); } } - - public static bool TryGetMin(Node root, out K key, out V value) { var current = root; @@ -790,7 +705,7 @@ namespace PersistentMap return false; } - key = leaf.Keys![0]; + key = leaf.Keys[0]; value = leaf.Values[0]; return true; } @@ -813,7 +728,7 @@ namespace PersistentMap } int last = leaf.Header.Count - 1; - key = leaf.Keys![last]; + key = leaf.Keys[last]; value = leaf.Values[last]; return true; } @@ -826,7 +741,6 @@ namespace PersistentMap int depth = 0; long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; - var current = root; while (!current.IsLeaf) { @@ -841,23 +755,19 @@ namespace PersistentMap var leaf = current.AsLeaf(); int index = FindIndex(leaf, key, keyPrefix, strategy); - if (index < leaf.Header.Count && strategy.Compare(leaf.Keys![index], key) == 0) index++; + if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0) index++; - // 1. Successor is in the same leaf if (index < leaf.Header.Count) { - nextKey = leaf.Keys![index]; + nextKey = leaf.Keys[index]; nextValue = leaf.Values[index]; return true; } - // 2. Successor is in the next leaf (We must backtrack up the tree!) for (int i = depth - 1; i >= 0; i--) { - // If we haven't reached the right-most child of this parent if (indices[i] < path[i].Header.Count) { - // Take one step right, then go absolute left all the way down current = path[i].Children[indices[i] + 1]!; while (!current.IsLeaf) { @@ -865,7 +775,7 @@ namespace PersistentMap } var targetLeaf = current.AsLeaf(); - nextKey = targetLeaf.Keys![0]; + nextKey = targetLeaf.Keys[0]; nextValue = targetLeaf.Values[0]; return true; } @@ -879,13 +789,11 @@ namespace PersistentMap public static bool TryGetPredecessor(Node root, K key, TStrategy strategy, out K prevKey, out V prevValue) where TStrategy : IKeyStrategy { - // Max depth of a B-Tree is small, preallocate a small array to track the descent path. InternalNode[] path = new InternalNode[32]; int[] indices = new int[32]; int depth = 0; long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; - var current = root; while (!current.IsLeaf) { @@ -900,20 +808,17 @@ namespace PersistentMap var leaf = current.AsLeaf(); int index = FindIndex(leaf, key, keyPrefix, strategy); - // Easy case: Predecessor is in the same leaf if (index > 0) { - prevKey = leaf.Keys![index - 1]; + prevKey = leaf.Keys[index - 1]; prevValue = leaf.Values[index - 1]; return true; } - // Hard case: We need to backtrack to find the first left branch we ignored for (int i = depth - 1; i >= 0; i--) { if (indices[i] > 0) { - // Jump to the left sibling branch, then take the absolute right-most path down current = path[i].Children[indices[i] - 1]!; while (!current.IsLeaf) { @@ -923,7 +828,7 @@ namespace PersistentMap var targetLeaf = current.AsLeaf(); int last = targetLeaf.Header.Count - 1; - prevKey = targetLeaf.Keys![last]; + prevKey = targetLeaf.Keys[last]; prevValue = targetLeaf.Values[last]; return true; } @@ -934,6 +839,4 @@ namespace PersistentMap return false; } } - - } diff --git a/PersistentMap/BaseOrderedMap.cs b/PersistentMap/BaseOrderedMap.cs index ee99960..613bca0 100644 --- a/PersistentMap/BaseOrderedMap.cs +++ b/PersistentMap/BaseOrderedMap.cs @@ -40,13 +40,13 @@ public abstract class BaseOrderedMap : IEnumerable Create(TStrategy strategy) { // Start with an empty leaf owned by None so the first write triggers CoW. - var emptyRoot = new LeafNode(OwnerId.None); + var emptyRoot = new LeafNode(OwnerId.None, strategy.UsesPrefixes); return new PersistentMap(emptyRoot, strategy, 0); } public static TransientMap CreateTransient(TStrategy strategy) { - var emptyRoot = new LeafNode(OwnerId.None); + var emptyRoot = new LeafNode(OwnerId.None, strategy.UsesPrefixes); return new TransientMap(emptyRoot, strategy,0); } diff --git a/PersistentMap/KeyStrategies.cs b/PersistentMap/KeyStrategies.cs index 2efb4ac..2bc14a6 100644 --- a/PersistentMap/KeyStrategies.cs +++ b/PersistentMap/KeyStrategies.cs @@ -18,38 +18,10 @@ public interface IKeyStrategy // bool IsLossless => false; + bool UseBinarySearch => false; } -/// -/// A universal key strategy for any type that relies on standard comparisons -/// (IComparable, IComparer, or custom StringComparers) without SIMD prefixes. -/// -public readonly struct StandardStrategy : IKeyStrategy -{ - private readonly IComparer _comparer; - - // If no comparer is provided, it defaults to Comparer.Default - // which automatically uses IComparable if the type implements it. - public StandardStrategy(IComparer? comparer = null) - { - _comparer = comparer ?? Comparer.Default; - } - - // Tell the B-Tree to skip SIMD routing and just use LinearSearch - public bool UsesPrefixes => false; - - // This will never be called because UsesPrefixes is false, - // but we must satisfy the interface. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long GetPrefix(K key) => 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Compare(K x, K y) - { - return _comparer.Compare(x, y); - } -} public struct UnicodeStrategy : IKeyStrategy @@ -89,159 +61,5 @@ public struct UnicodeStrategy : IKeyStrategy public bool UsesPrefixes => true; } -public struct IntStrategy : IKeyStrategy -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Compare(int x, int y) => x.CompareTo(y); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long GetPrefix(int key) - { - // Pack the 32-bit int into the high 32-bits of the long. - // This preserves sorting order when scanning the long array. - // Cast to uint first to prevent sign extension confusion during the shift, - // though standard int shifting usually works fine for direct mapping. - return (long)key << 32; - } - - public bool UsesPrefixes => true; - - public bool IsLossless => true; -} - -public struct DoubleStrategy : IKeyStrategy -{ - public bool IsLossless => true; - // Use the standard comparison for the fallback/refine step - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Compare(double x, double y) => x.CompareTo(y); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long GetPrefix(double key) - { - // 1. Bit Cast to Long (0 cost) - long bits = Unsafe.As(ref key); - - // 2. The Magic Twist - // If the sign bit (MSB) is set (negative), we flip ALL bits. - // If the sign bit is clear (positive), we flip ONLY the sign bit. - // This maps: - // -Negative Max -> 0 - // -0 -> Midpoint - // +Negative Max -> Max - - long mask = (bits >> 63); // 0 for positive, -1 (All 1s) for negative - - // If negative: bits ^ -1 = ~bits (Flip All) - // If positive: bits ^ 0 = bits (Flip None) - // Then we toggle the sign bit (0x8000...) to shift the range to signed long. - - return (bits ^ (mask & 0x7FFFFFFFFFFFFFFF)) ^ unchecked((long)0x8000000000000000); - } -} - -/// -/// Helper for SIMD accelerated prefix scanning. -/// -public static class PrefixScanner -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int FindFirstGreaterOrEqual(ReadOnlySpan prefixes, long targetPrefix) - { - // Handle MinValue specifically to avoid underflow in (target - 1) logic - // If target is MinValue, any value in prefixes is >= target. - // So the first element (index 0) is the match. - // TODO: evaluate if this is needed. - //if (targetPrefix == long.MinValue) - //{ - // return 0; - //} - - // Fallback for short arrays or unsupported hardware - if (!Avx2.IsSupported || prefixes.Length < 4) - return LinearScan(prefixes, targetPrefix); - - return Avx512F.IsSupported - ? ScanAvx512(prefixes, targetPrefix) - : ScanAvx2(prefixes, targetPrefix); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LinearScan(ReadOnlySpan prefixes, long target) - { - for (var i = 0; i < prefixes.Length; i++) - if (prefixes[i] >= target) - return i; - return prefixes.Length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int ScanAvx2(ReadOnlySpan prefixes, long target) - { - // Create a vector where every element is the target prefix - var vTarget = Vector256.Create(target); - var i = 0; - var len = prefixes.Length; - - // Process 4 longs at a time (256 bits) - for (; i <= len - 4; i += 4) - fixed (long* ptr = prefixes) - { - var vData = Avx2.LoadVector256(ptr + i); - - // Compare: result is -1 (all 1s) if true, 0 if false - // We want Data >= Target. - // AVX2 CompareGreaterThan is for signed. Longs should be treated carefully, - // but for text prefixes (positive), signed compare is usually sufficient. - // Effectively: !(Data < Target) could be safer if signs vary, - // but here we assume prefixes are derived from unsigned chars. - // Standard AVX2 hack for CompareGreaterOrEqual (Signed): - // No native _mm256_cmpge_epi64 in AVX2. - // Use CompareGreaterThan(Data, Target - 1) - var vResult = Avx2.CompareGreaterThan(vData, Vector256.Create(target - 1)); - - var mask = Avx2.MoveMask(vResult.AsByte()); - - if (mask != 0) - { - // Identify the first set bit corresponding to a 64-bit element - // MoveMask returns 32 bits (1 per byte). Each long is 8 bytes. - // We check bits 0, 8, 16, 24. - if ((mask & 0xFF) != 0) return i + 0; - if ((mask & 0xFF00) != 0) return i + 1; - if ((mask & 0xFF0000) != 0) return i + 2; - return i + 3; - } - } - - return LinearScan(prefixes.Slice(i), target) + i; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int ScanAvx512(ReadOnlySpan prefixes, long target) - { - var vTarget = Vector512.Create(target); - var i = 0; - var len = prefixes.Length; - - for (; i <= len - 8; i += 8) - fixed (long* ptr = prefixes) - { - var vData = Avx512F.LoadVector512(ptr + i); - // AVX512 has dedicated Compare Greater Than or Equal Long - var mask = Avx512F.CompareGreaterThanOrEqual(vData, vTarget); - - if (mask != Vector512.Zero) - { - // Extract most significant bit mask - var m = mask.ExtractMostSignificantBits(); - // Count trailing zeros to find the index - return i + BitOperations.TrailingZeroCount(m); - } - } - - return LinearScan(prefixes.Slice(i), target) + i; - } -} diff --git a/PersistentMap/KeyStrategies/DoubleStrategy.cs b/PersistentMap/KeyStrategies/DoubleStrategy.cs index e41425e..b62090e 100644 --- a/PersistentMap/KeyStrategies/DoubleStrategy.cs +++ b/PersistentMap/KeyStrategies/DoubleStrategy.cs @@ -1,3 +1,5 @@ +namespace PersistentMap; +using System.Runtime.CompilerServices; public struct DoubleStrategy : IKeyStrategy { diff --git a/PersistentMap/KeyStrategies/IntStrategy.cs b/PersistentMap/KeyStrategies/IntStrategy.cs index e69de29..f6133e2 100644 --- a/PersistentMap/KeyStrategies/IntStrategy.cs +++ b/PersistentMap/KeyStrategies/IntStrategy.cs @@ -0,0 +1,16 @@ +namespace PersistentMap; + +using System.Runtime.CompilerServices; + +public struct IntStrategy : IKeyStrategy +{ + public bool UsesPrefixes => false; + public bool IsLossless => true; + public bool UseBinarySearch => false; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Compare(int x, int y) => x.CompareTo(y); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long GetPrefix(int key) => 0; // Unused +} diff --git a/PersistentMap/KeyStrategies/PrefixScanner.cs b/PersistentMap/KeyStrategies/PrefixScanner.cs index 7ad9de1..9f6d907 100644 --- a/PersistentMap/KeyStrategies/PrefixScanner.cs +++ b/PersistentMap/KeyStrategies/PrefixScanner.cs @@ -1,3 +1,9 @@ +namespace PersistentMap; + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; // For AVX2 +using System.Numerics; /// /// Helper for SIMD accelerated prefix scanning. /// diff --git a/PersistentMap/KeyStrategies/StandardStrategy.cs b/PersistentMap/KeyStrategies/StandardStrategy.cs index 835b661..23ebfdd 100644 --- a/PersistentMap/KeyStrategies/StandardStrategy.cs +++ b/PersistentMap/KeyStrategies/StandardStrategy.cs @@ -1,4 +1,6 @@ +namespace PersistentMap; +using System.Runtime.CompilerServices; /// /// A universal key strategy for any type that relies on standard comparisons /// (IComparable, IComparer, or custom StringComparers) without SIMD prefixes. @@ -9,11 +11,16 @@ public readonly struct StandardStrategy : IKeyStrategy // If no comparer is provided, it defaults to Comparer.Default // which automatically uses IComparable if the type implements it. - public StandardStrategy(IComparer? comparer = null) + + public StandardStrategy() + { + _comparer = Comparer.Default; + } + + public StandardStrategy(IComparer? comparer) { _comparer = comparer ?? Comparer.Default; } - // Tell the B-Tree to skip SIMD routing and just use LinearSearch public bool UsesPrefixes => false; diff --git a/PersistentMap/Nodes.cs b/PersistentMap/Nodes.cs index 37e8533..1e1d6ba 100644 --- a/PersistentMap/Nodes.cs +++ b/PersistentMap/Nodes.cs @@ -32,6 +32,12 @@ public struct NodeHeader } } +[InlineArray(32)] +public struct KeyBuffer +{ + private K _element0; +} + // Constraint: Internal Nodes fixed at 32 children. // This removes the need for a separate array allocation for children references. [InlineArray(32)] @@ -82,6 +88,12 @@ public abstract class Node // Zero-overhead cast. Assumes you checked !IsLeaf or know logic flow. return Unsafe.As>(this); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] +public PrefixInternalNode AsPrefixInternal() +{ + return Unsafe.As>(this); +} } public sealed class LeafNode : Node @@ -96,12 +108,14 @@ public sealed class LeafNode : Node public override Span AllPrefixes => _prefixes != null ? _prefixes : Span.Empty; - public LeafNode(OwnerId owner) : base(owner, NodeFlags.IsLeaf | NodeFlags.HasPrefixes) + public LeafNode(OwnerId owner, bool usePrefixes) : base(owner, NodeFlags.IsLeaf | (usePrefixes ? NodeFlags.HasPrefixes : NodeFlags.None)) { Keys = new K[Capacity]; Values = new V[Capacity]; - _prefixes = new long[Capacity]; - + if (usePrefixes) + { + _prefixes = new long[Capacity]; + } } // Copy Constructor for CoW @@ -153,67 +167,75 @@ public sealed class LeafNode : Node } } -public sealed class InternalNode : Node +public class InternalNode : Node { public const int Capacity = 32; - // InlineArray storage - internal InternalPrefixBuffer _prefixBuffer; + public KeyBuffer Keys; public NodeBuffer Children; - - public K[]? Keys; - - public override Span AllPrefixes => MemoryMarshal.CreateSpan(ref _prefixBuffer[0], Capacity); - public InternalNode(OwnerId owner) : base(owner, NodeFlags.HasPrefixes) + public override Span AllPrefixes => Span.Empty; + + public InternalNode(OwnerId owner, NodeFlags flags = NodeFlags.None) + : base(owner, flags) { - Keys = new K[Capacity]; - // Children buffer is a struct, zero-initialized by default } - // Copy Constructor for CoW - private InternalNode(InternalNode original, OwnerId newOwner) - : base(newOwner, original.Header.Flags) + // Fixed CoW Constructor + protected InternalNode(InternalNode original, OwnerId newOwner, NodeFlags flags) + : base(newOwner, flags) { Header.Count = original.Header.Count; - Keys = new K[Capacity]; - Array.Copy(original.Keys, Keys, original.Header.Count); - // Fast struct blit for prefixes - this._prefixBuffer = original._prefixBuffer; + // Fast struct blit for both Keys and Children. + // No loop required for InlineArrays! + this.Keys = original.Keys; + this.Children = original.Children; + } - var srcChildren = original.GetChildren(); - for (var i = 0; i < srcChildren.Length; i++) Children[i] = srcChildren[i]; + // The missing method needed by BTreeFunctions for routing + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span> GetChildren() + { + // An internal node always has (Count + 1) children + return MemoryMarshal.CreateSpan(ref Children[0], Header.Count + 1); + } + + public override Span GetKeys() => MemoryMarshal.CreateSpan(ref Keys[0], Header.Count); + + public override Node EnsureEditable(OwnerId transactionId) + { + if (transactionId == OwnerId.None) return new InternalNode(this, OwnerId.None, Header.Flags); + if (Header.Owner == transactionId) return this; + return new InternalNode(this, transactionId, Header.Flags); + } +} + + +public sealed class PrefixInternalNode : InternalNode +{ + internal InternalPrefixBuffer _prefixBuffer; + + public override Span AllPrefixes => MemoryMarshal.CreateSpan(ref _prefixBuffer[0], Capacity); + + public PrefixInternalNode(OwnerId owner) + : base(owner, NodeFlags.HasPrefixes) + { + } + + // CoW Constructor + private PrefixInternalNode(PrefixInternalNode original, OwnerId newOwner) + : base(original, newOwner, original.Header.Flags) + { + // Copy the base Keys and Children, then blit the prefix buffer + this._prefixBuffer = original._prefixBuffer; } public override Node EnsureEditable(OwnerId transactionId) { - if (transactionId == OwnerId.None) - { - return new InternalNode(this, OwnerId.None); - } - - if (Header.Owner == transactionId) - { - return this; - } - - return new InternalNode(this, transactionId); - } - public override Span GetKeys() - { - return Keys.AsSpan(0, Header.Count); - } - - // Exposes the InlineArray as a Span - public Span?> GetChildren() - { - return MemoryMarshal.CreateSpan?>(ref Children[0]!, Header.Count + 1); - } - - public void SetChild(int index, Node node) - { - Children[index] = node; + if (transactionId == OwnerId.None) return new PrefixInternalNode(this, OwnerId.None); + if (Header.Owner == transactionId) return this; + return new PrefixInternalNode(this, transactionId); } } diff --git a/PersistentMap/PersistentMap.cs b/PersistentMap/PersistentMap.cs index 5f6ed43..c691918 100644 --- a/PersistentMap/PersistentMap.cs +++ b/PersistentMap/PersistentMap.cs @@ -25,7 +25,7 @@ public sealed class PersistentMap : BaseOrderedMap(default(OwnerId)); + var emptyRoot = new LeafNode(default(OwnerId), strategy.UsesPrefixes); return new PersistentMap(emptyRoot, strategy, 0); } diff --git a/PersistentMap/Readme.org b/PersistentMap/Readme.org index f608d5f..af2ae28 100644 --- a/PersistentMap/Readme.org +++ b/PersistentMap/Readme.org @@ -5,16 +5,16 @@ A high-performance, persistent (Copy-on-Write) B+ Tree implemented in C#. It is designed for zero-overhead reads, SIMD-accelerated key routing, and allocation-free range queries. It supports both fully immutable usage and "Transient" mode for high-throughput bulk mutations. ** Features -- *Copy-on-Write Semantics*: Thread-safe, immutable tree states. Modifying the tree yields a new version while sharing unmodified nodes. +- *Copy-on-Write Semantics*: Thread-safe, immutable tree states. Modifying the tre yields a new version while sharing unmodified nodes. - *Transient Mode*: Perform bulk mutations in-place with standard mutable performance, then freeze it into a =PersistentMap= in $O(1)$ time. - *SIMD Prefix Scanning*: Uses AVX2/AVX512 to vectorize B+ tree routing and binary searches via =long= key-prefixes. - *Linear Time Set Operations*: Sort-merge based =Intersect=, =Except=, and =SymmetricExcept= execute in $O(N+M)$ time using lazy evaluation. ** When should I use this? -Never, probably. This was just a fun little project. If you want a really fast immutable sorted map you should consider it. Despite this map being faster than LanguageExt.HashMap for some key types, you should definitely use that if you don't need a sorted collection. It is well tested and does not have any problems key collisions, which will slow this map down by a lot. +Never, probably. This was just a fun little project. If you want a really fast immutable sorted map you should consider it. Despite this map being faster than LanguageExt.HashMap for some key types, you should definitely use that if you don't need a sorted collection. It is well tested and does have a very nice API, and it also has a performance model that is easy to understand. If you look at the performance characteristics of it below, it scales much more linear with collection size. For example: I don't know why PersistentMap lookups suddenly becomes so much slower when we reach 100000 integer keys. There might be a gazillion things like that, that make PersistentMap much slower for real world usage. -The general version of this, using =StandardStrategy= does not benefit from the prefix optimization. +The general version of this, using =StandardStrategy= does not benefit from the prefix optimization, although might benefit from a usage of binary search in the future. ** Quick Start @@ -36,8 +36,8 @@ if (map2.TryGetValue(2, out var value)) } #+end_src -*** 2. Transient Mode (Bulk Mutations) -If you need to insert thousands of elements, creating a new persistent tree on every insert is too slow. Use a =TransientMap= to mutate the tree in-place, then lock it into a persistent snapshot. +*** 2. Transient Mode (Bulk Mutations +If you need to insert thousands of elements, creating a new persistent tree on every insert is too slow. Use a =TransientMap= to mutate the tree in-place, then lock it into a persistent snapshot. This does not edit an existing map, but will make bulk operations a lot faster on nodes "owned" by the current map. #+begin_src csharp var transientMap = BaseOrderedMap.CreateTransient(new IntStrategy()); @@ -53,7 +53,7 @@ var persistentSnapshot = transientMap.ToPersistent(); #+end_src *** 3. Range Queries and Iteration -Because it is a B+ tree, leaf nodes are linked. Range queries require zero allocations and simply walk the leaves. +Because it is a B+ tree range queries require zero allocations and simply walk the leaves. #+begin_src csharp var map = GetPopulatedMap(); @@ -68,7 +68,7 @@ foreach (var kvp in map.Range(min: 10, max: 50)) var greaterThan100 = map.From(100); var lessThan50 = map.Until(50); var allElements = map.AsEnumerable(); -#+end_src +#+end_sr *** 4. Tree Navigation Find bounds and adjacent elements instantly. Missing keys will correctly resolve to the mathematical lower/upper bound. @@ -91,7 +91,7 @@ if (map.TryGetPredecessor(42, out int prevKey, out string prevVal)) #+end_src *** 5. Set Operations -Set operations take advantage of the tree's underlying sorted linked-list structure to merge trees in linear $O(N+M)$ time. +Set operations take advantage of the tree's underlying sorted structure to merge trees in linear $O(N+M)$ time. #+begin_src csharp var mapA = CreateMap(1, 2, 3, 4); @@ -108,146 +108,502 @@ var symmetricDiff = mapA.SymmetricExcept(mapB); #+end_src ** Benchmarks -This is going to be all over the place, but here is a small comparison to other immutable sequences. Due to how the prefix optimization works, this persistent map will be the absolutely most performant when there is high entropy in the first 8 bytes of the key. The following is pretty much the best scenario we can have since we probably only look at the first 8 characters (this is for reading a value). +These benchmarks tries a variety of operations. The Int benchmarks (the first ones) use avx for lookups. On a computer without avx this is bound to be slower. In benchmarks where there are many writes to the tree, the transient version is used, since this is the workload this datastructure is optimized for. -#+begin_src -| Method | CollectionSize | KeySize | Mean | Gen0 | Allocated | -|-----------------|----------------|----------|--------------:|-----------:|----------:| -| PersistentMap | **1024** | **10** | **25.61 ns** | **0.0043** | **72 B** | -| Sys.Sorted | 1024 | 10 | 153.18 ns | - | - | -| LangExt.HashMap | 1024 | 10 | 24.80 ns | - | - | -| LangExtSorted | 1024 | 10 | 176.90 ns | - | - | -| PersistentMap | **1024** | **100** | **26.43 ns** | **0.0043** | **72 B** | -| SysSorted | 1024 | 100 | 154.77 ns | - | - | -| LangExt.HashMap | 1024 | 100 | 66.30 ns | - | - | -| LangExtSorted | 1024 | 100 | 177.28 ns | - | - | -| PersistentMap | **1024** | **1000** | **26.17 ns** | **0.0043** | **72 B** | -| SysSorted | 1024 | 1000 | 155.68 ns | - | - | -| LangExt.HashMap | 1024 | 1000 | 491.97 ns | - | - | -| LangExtSorted | 1024 | 1000 | 181.58 ns | - | - | -| PersistentMap | **131072** | **10** | **109.34 ns** | **0.0072** | **120 B** | -| SysSorted | 131072 | 10 | 460.22 ns | - | - | -| LangExt.HashMap | 131072 | 10 | 60.35 ns | - | - | -| LangExtSorted | 131072 | 10 | 555.17 ns | - | - | -| PersistentMap | **131072** | **100** | **147.30 ns** | **0.0072** | **120 B** | -| SysSorted | 131072 | 100 | 556.39 ns | - | - | -| LangExt.HashMap | 131072 | 100 | 162.81 ns | - | - | -| LangExtSorted | 131072 | 100 | 605.15 ns | - | - | -| PersistentMap | **131072** | **1000** | **170.16 ns** | **0.0072** | **120 B** | -| SysSorted | 131072 | 1000 | 625.78 ns | - | - | -| LangExt.HashMap | 131072 | 1000 | 763.75 ns | - | - | -| LangExtSorted | 131072 | 1000 | 692.92 ns | - | - | +Build: builds a map of size N. I did not benchmark the builders used by the built in collections, but they are almost certainly at least as fast as the transients used by this library. For integers, the map was sorted (triggering a small optimization in PersistentMap). For the string benchmark it was random. +The retrieval benchmarks reads a subset of the keys in random order. -#+end_src +The update benchmarks updates a subset of the keys in random order. -To look at pure overhead, here is a benchmark using integers as keys. This is also a good fit for this BTree, since it can utilize a key strategy that compares integers using AVX. The hash based alternatives are going to have a huge advantage here. As you can see, reading a single value isn't great, and setting a single value after building the btree is also pretty awful (setting many should probably be done using transients). Iterating a building (using transients. The only valid comparison here is probably to MS SortedDict) is however plenty fast. +The update and set benchmarks updates and sets keys in random order. Half of the keys are new. + +The iteration benchmarks iterate from start to finish. The ordered collections are of course in order. + +The removal benchmarks removes a subset of the keys in random order. + +*** Integer keys +This is pretty much the best case scenario for everyone. Key comparisons are fast, hashing is minimal. For b+trees this means we can do a lot of key comparisons at once using avx. The machine this is done on is an amd 5900x, which supports some kind of bastardized avx512. It is not really a gain over avx256 in this benchmark though on that processor. With regards to building, the microsoft collections have builders, and they are about as fast as the TransientMap, but a little little slower on my computer. LanguageExt lacks transients, and thus these comparisons are _not_ fair. + +ImmDict is System.Collections.Immutable dictionary. ImmSortedDict is it's sorted sibling. ExtMap is the sorted map from LanguageExt. ExtHashMap is the unsorted HashMap from LanguageExt. #+begin_src -| Method | N | Mean | Gen0 | Gen1 | Gen2 | Allocated | -|-------------------------------------|--------|-----------------:|----------:|----------:|--------:|------------:| -| 'Build: PersistentMap (Transient)' | 100 | 3,764.63 ns | 0.3929 | 0.0038 | - | 6632 B | -| 'Build: MS Sorted (Builder)' | 100 | 3,096.11 ns | 0.2899 | 0.0038 | - | 4864 B | -| 'Build: LanguageExt Map (AVL)' | 100 | 6,967.02 ns | 2.2736 | 0.0229 | - | 38144 B | -| 'Build: LanguageExt HashMap' | 100 | 4,594.07 ns | 1.9684 | 0.0076 | - | 33024 B | -| 'Read: PersistentMap' | 100 | 1,596.68 ns | 0.4292 | - | - | 7200 B | -| 'Read: MS Sorted' | 100 | 474.54 ns | - | - | - | - | -| 'Read: LanguageExt Map' | 100 | 1,311.31 ns | - | - | - | - | -| 'Read: LanguageExt HashMap' | 100 | 641.22 ns | - | - | - | - | -| 'Iterate: PersistentMap' | 100 | 135.41 ns | - | - | - | - | -| 'Iterate: MS Sorted' | 100 | 372.31 ns | - | - | - | - | -| 'Iterate: LanguageExt Map' | 100 | 287.33 ns | 0.0019 | - | - | 32 B | -| 'Iterate: LanguageExt HashMap' | 100 | 781.56 ns | 0.0648 | - | - | 1088 B | -| 'Set: PersistentMap' | 100 | 85.68 ns | 0.1142 | 0.0007 | - | 1912 B | -| 'Set: MS Sorted' | 100 | 66.44 ns | 0.0229 | - | - | 384 B | -| 'Set: LanguageExt Map' | 100 | 60.04 ns | 0.0219 | - | - | 368 B | -| 'Set: LanguageExt HashMap' | 100 | 36.62 ns | 0.0206 | - | - | 344 B | -| 'Build: PersistentMap (Transient)' | 1000 | 49,445.56 ns | 3.1738 | 0.2441 | - | 53096 B | -| 'Build: MS Sorted (Builder)' | 1000 | 50,163.19 ns | 2.8687 | 0.4272 | - | 48064 B | -| 'Build: LanguageExt Map (AVL)' | 1000 | 103,877.98 ns | 34.6680 | 3.1738 | - | 580688 B | -| 'Build: LanguageExt HashMap' | 1000 | 124,339.17 ns | 45.4102 | 3.2959 | - | 760096 B | -| 'Read: PersistentMap' | 1000 | 17,671.71 ns | 4.3030 | - | - | 72000 B | -| 'Read: MS Sorted' | 1000 | 7,911.72 ns | - | - | - | - | -| 'Read: LanguageExt Map' | 1000 | 20,187.52 ns | - | - | - | - | -| 'Read: LanguageExt HashMap' | 1000 | 9,740.28 ns | - | - | - | - | -| 'Iterate: PersistentMap' | 1000 | 1,217.47 ns | - | - | - | - | -| 'Iterate: MS Sorted' | 1000 | 3,875.47 ns | - | - | - | - | -| 'Iterate: LanguageExt Map' | 1000 | 2,862.82 ns | - | - | - | 32 B | -| 'Iterate: LanguageExt HashMap' | 1000 | 11,974.93 ns | 1.9226 | - | - | 32320 B | -| 'Set: PersistentMap' | 1000 | 121.01 ns | 0.1142 | 0.0007 | - | 1912 B | -| 'Set: MS Sorted' | 1000 | 91.62 ns | 0.0315 | - | - | 528 B | -| 'Set: LanguageExt Map' | 1000 | 82.26 ns | 0.0305 | - | - | 512 B | -| 'Set: LanguageExt HashMap' | 1000 | 57.02 ns | 0.0367 | - | - | 616 B | -| 'Build: PersistentMap (Transient)' | 100000 | 10,808,233.62 ns | 296.8750 | 218.7500 | - | 5185832 B | -| 'Build: MS Sorted (Builder)' | 100000 | 16,655,882.43 ns | 281.2500 | 250.0000 | - | 4800064 B | -| 'Build: LanguageExt Map (AVL)' | 100000 | 39,932,734.83 ns | 5333.3333 | 3333.3333 | - | 89959040 B | -| 'Build: LanguageExt HashMap' | 100000 | 21,220,179.10 ns | 5781.2500 | 2968.7500 | 31.2500 | 96555422 B | -| 'Read: PersistentMap' | 100000 | 7,359,807.97 ns | 710.9375 | - | - | 12000000 B | -| 'Read: MS Sorted' | 100000 | 8,428,009.48 ns | - | - | - | - | -| 'Read: LanguageExt Map' | 100000 | 10,268,884.43 ns | - | - | - | - | -| 'Read: LanguageExt HashMap' | 100000 | 1,936,555.07 ns | - | - | - | - | -| 'Iterate: PersistentMap' | 100000 | 151,028.79 ns | - | - | - | - | -| 'Iterate: MS Sorted' | 100000 | 1,068,072.16 ns | - | - | - | - | -| 'Iterate: LanguageExt Map' | 100000 | 837,677.39 ns | - | - | - | 32 B | -| 'Iterate: LanguageExt HashMap' | 100000 | 1,226,773.82 ns | 64.4531 | - | - | 1082432 B | -| 'Set: PersistentMap' | 100000 | 208.61 ns | 0.1984 | 0.0024 | - | 3320 B | -| 'Set: MS Sorted' | 100000 | 138.82 ns | 0.0458 | - | - | 768 B | -| 'Set: LanguageExt Map' | 100000 | 128.28 ns | 0.0448 | - | - | 752 B | -| 'Set: LanguageExt HashMap' | 100000 | 84.33 ns | 0.0583 | - | - | 976 B | - +| Method | N | Mean | Gen0 | Gen1 | Gen2 | Allocated | +|-------------------------|--------|-----------------:|-----------:|----------:|--------:|------------:| +| Build_ImmDict | 100 | 11,307.04 ns | 4.9744 | 0.0458 | - | 41688 B | +| Build_ImmSortedDict | 100 | 8,493.79 ns | 4.4250 | 0.0458 | - | 37104 B | +| Build_ExtMap | 100 | 8,519.63 ns | 5.3101 | 0.0458 | - | 44432 B | +| Build_ExtHashMap | 100 | 9,855.33 ns | 7.5378 | 0.0458 | - | 63104 B | +| Build_PersistentMap | 100 | 8,698.33 ns | 16.3879 | 0.1526 | - | 137072 B | +| Build_TransientMap | 100 | 1,665.90 ns | 0.6332 | 0.0038 | - | 5304 B | +| Retrieve_ImmDict | 100 | 39.19 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 100 | 64.32 ns | - | - | - | - | +| Retrieve_ExtMap | 100 | 117.61 ns | - | - | - | - | +| Retrieve_ExtHashMap | 100 | 84.19 ns | - | - | - | - | +| Retrieve_PersistentMap | 100 | 47.25 ns | - | - | - | - | +| Update_ImmDict | 100 | 1,145.75 ns | 0.4616 | 0.0019 | - | 3872 B | +| Update_PersistentMap | 100 | 1.107 μs | 1.9398 | 0.0248 | - | 15.86 KB | +| Update_TransientMap | 100 | 347.49 ns | 0.3576 | 0.0033 | - | 2992 B | +| Update_ImmSortedDict | 100 | 849.39 ns | 0.3958 | 0.0010 | - | 3312 B | +| Update_ExtMap | 100 | 642.50 ns | 0.3939 | 0.0010 | - | 3296 B | +| Update_ExtHashMap | 100 | 541.84 ns | 0.5283 | 0.0010 | - | 4424 B | +| UpdateSet_ImmDict | 100 | 1,236.82 ns | 0.5226 | 0.0019 | - | 4376 B | +| UpdateSet_PersistentMap | 100 | 1189 ns | 1.9398 | 0.0248 | - | 15.86 KB | +| UpdateSet_TransientMap | 100 | 380.70 ns | 0.3576 | 0.0033 | - | 2992 B | +| UpdateSet_ImmSortedDict | 100 | 887.07 ns | 0.4587 | 0.0010 | - | 3840 B | +| UpdateSet_ExtMap | 100 | 856.76 ns | 0.4797 | 0.0010 | - | 4016 B | +| UpdateSet_ExtHashMap | 100 | 582.31 ns | 0.5312 | 0.0019 | - | 4448 B | +| Iterate_ImmDict | 100 | 1,324.41 ns | - | - | - | - | +| Iterate_PersistentMap | 100 | 175.98 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100 | 488.69 ns | - | - | - | - | +| Iterate_ExtMap | 100 | 337.40 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 100 | 1,209.77 ns | 0.2518 | - | - | 2112 B | +| Remove_ImmDict | 100 | 899.57 ns | 0.4425 | 0.0010 | - | 3704 B | +| Remove_TransientMap | 100 | 433.52 ns | 0.3290 | 0.0029 | - | 2752 B | +| Remove_ImmSortedDict | 100 | 728.77 ns | 0.3786 | 0.0010 | - | 3168 B | +| Remove_ExtMap | 100 | 653.72 ns | 0.3767 | 0.0010 | - | 3152 B | +| Remove_ExtHashMap | 100 | 589.03 ns | 0.5178 | - | - | 4336 B | +| Build_ImmDict | 1000 | 168,692.47 ns | 71.5332 | 6.8359 | - | 598712 B | +| Build_ImmSortedDict | 1000 | 125,591.01 ns | 62.9883 | 5.1270 | - | 526896 B | +| Build_ExtMap | 1000 | 117,763.81 ns | 72.3877 | 6.1035 | - | 605936 B | +| Build_ExtHashMap | 1000 | 64,443.19 ns | 67.5049 | 1.5869 | - | 564864 B | +| Build_PersistentMap | 1000 | 133,156.05 ns | 192.1387 | 7.8125 | - | 1607744 B | +| Build_TransientMap | 1000 | 25,945.72 ns | 4.2725 | 0.1526 | - | 35976 B | +| Retrieve_ImmDict | 1000 | 686.48 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 1000 | 1,145.83 ns | - | - | - | - | +| Retrieve_ExtMap | 1000 | 2,276.07 ns | - | - | - | - | +| Retrieve_ExtHashMap | 1000 | 808.53 ns | - | - | - | - | +| Retrieve_PersistentMap | 1000 | 680.50 ns | - | - | - | - | +| Update_ImmDict | 1000 | 16,863.81 ns | 6.5613 | 0.2136 | - | 54960 B | +| Update_PersistentMap | 1000 | 13,617.12 ns | 19.4092 | 1.1597 | - | 158.59 KB | +| Update_TransientMap | 1000 | 3,611.03 ns | 2.5406 | 0.1564 | - | 21280 B | +| Update_ImmSortedDict | 1000 | 12,428.90 ns | 5.5542 | 0.1526 | - | 46464 B | +| Update_ExtMap | 1000 | 10,091.51 ns | 5.6000 | 0.1678 | - | 46880 B | +| Update_ExtHashMap | 1000 | 6,758.96 ns | 7.9575 | 0.2136 | - | 66616 B | +| UpdateSet_ImmDict | 1000 | 21,489.70 ns | 7.0496 | 0.1831 | - | 59160 B | +| UpdateSet_PersistentMap | 1000 | 14,890.21 ns | 19.4855 | 1.0529 | - | 159.23 KB | +| UpdateSet_TransientMap | 1000 | 5,063.11 ns | 2.4796 | 0.1450 | - | 20776 B | +| UpdateSet_ImmSortedDict | 1000 | 13,333.61 ns | 6.3782 | 0.1526 | - | 53472 B | +| UpdateSet_ExtMap | 1000 | 11,221.39 ns | 6.5918 | 0.1526 | - | 55184 B | +| UpdateSet_ExtHashMap | 1000 | 15,967.43 ns | 13.1836 | 0.4578 | - | 110440 B | +| Iterate_ImmDict | 1000 | 15,325.93 ns | - | - | - | - | +| Iterate_PersistentMap | 1000 | 1,574.20 ns | - | - | - | - | +| Iterate_ImmSortedDict | 1000 | 5,110.07 ns | - | - | - | - | +| Iterate_ExtMap | 1000 | 3,432.88 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 1000 | 8,207.75 ns | 0.2441 | - | - | 2112 B | +| Remove_ImmDict | 1000 | 15,205.95 ns | 6.4392 | 0.2136 | - | 54064 B | +| Remove_TransientMap | 1000 | 4,036.18 ns | 2.2507 | 0.1373 | - | 18880 B | +| Remove_ImmSortedDict | 1000 | 10,664.14 ns | 5.7068 | 0.1678 | - | 47760 B | +| Remove_ExtMap | 1000 | 9,993.90 ns | 5.5084 | 0.1526 | - | 46160 B | +| Remove_ExtHashMap | 1000 | 7,475.24 ns | 7.7209 | 0.1907 | - | 64608 B | +| Build_ImmDict | 10000 | 2,571,753.12 ns | 41.4063 | 390.6250 | - | 7882552 B | +| Build_ImmSortedDict | 10000 | 1,975,364.14 ns | 820.3125 | 296.8750 | - | 6893616 B | +| Build_ExtMap | 10000 | 1,866,221.83 ns | 917.9688 | 320.3125 | - | 7692272 B | +| Build_ExtHashMap | 10000 | 1,215,103.58 ns | 1009.7656 | 240.2344 | - | 8446080 B | +| Build_PersistentMap | 10000 | 1,930,457.96 ns | 2345.7031 | 494.1406 | - | 19626728 B | +| Build_TransientMap | 10000 | 640,413.08 ns | 41.0156 | 8.7891 | - | 347344 B | +| Retrieve_ImmDict | 10000 | 14,880.32 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 10000 | 15,595.68 ns | - | - | - | - | +| Retrieve_ExtMap | 10000 | 36,225.60 ns | - | - | - | - | +| Retrieve_ExtHashMap | 10000 | 11,987.70 ns | - | - | - | - | +| Retrieve_PersistentMap | 10000 | 10,227.73 ns | - | - | - | - | +| Update_ImmDict | 10000 | 318,905.00 ns | 86.9141 | 23.4375 | - | 730200 B | +| Update_PersistentMap | 10000 | 202,244.42 ns | 243.6523 | 73.2422 | - | 1992.19 KB | +| Update_TransientMap | 10000 | 73,203.50 ns | 24.9023 | 7.3242 | - | 209056 B | +| Update_ImmSortedDict | 10000 | 216,638.87 ns | 77.1484 | 16.1133 | - | 645360 B | +| Update_ExtMap | 10000 | 176,737.24 ns | 74.4629 | 17.5781 | - | 623600 B | +| Update_ExtHashMap | 10000 | 105,445.84 ns | 97.2900 | 17.3340 | - | 814376 B | +| UpdateSet_ImmDict | 10000 | 333,260.72 ns | 92.2852 | 19.0430 | - | 775784 B | +| UpdateSet_PersistentMap | 10000 | 221,958.91 ns | 244.6289 | 71.2891 | - | 1998.95 KB | +| UpdateSet_TransientMap | 10000 | 93,484.07 ns | 24.9023 | 7.3242 | - | 209072 B | +| UpdateSet_ImmSortedDict | 10000 | 224,214.31 ns | 83.2520 | 14.6484 | - | 697920 B | +| UpdateSet_ExtMap | 10000 | 186,761.55 ns | 83.7402 | 14.4043 | - | 700880 B | +| UpdateSet_ExtHashMap | 10000 | 112,371.27 ns | 97.7783 | 20.2637 | - | 818240 B | +| Iterate_ImmDict | 10000 | 152,686.50 ns | - | - | - | - | +| Iterate_PersistentMap | 10000 | 14,841.56 ns | - | - | - | - | +| Iterate_ImmSortedDict | 10000 | 53,372.05 ns | - | - | - | - | +| Iterate_ExtMap | 10000 | 38,673.93 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 10000 | 111,676.15 ns | 8.0566 | - | - | 67648 B | +| Remove_ImmDict | 10000 | 303,798.22 ns | 86.4258 | 19.5313 | - | 726560 B | +| Remove_TransientMap | 10000 | 58,890.93 ns | 22.0947 | 6.5308 | - | 185056 B | +| Remove_ImmSortedDict | 10000 | 219,974.63 ns | 77.8809 | 15.1367 | - | 653184 B | +| Remove_ExtMap | 10000 | 188,713.80 ns | 74.2188 | 14.4043 | - | 621248 B | +| Remove_ExtHashMap | 10000 | 120,113.97 ns | 95.9473 | 15.9912 | - | 802944 B | +| Build_ImmDict | 100000 | 38,394,437.95 ns | 11714.2857 | 1071.4286 | 71.4286 | 97460075 B | +| Build_ImmSortedDict | 100000 | 30,860,676.12 ns | 10187.5000 | 906.2500 | 62.5000 | 84908636 B | +| Build_ExtMap | 100000 | 28,415,796.22 ns | 11156.2500 | 937.5000 | 62.5000 | 92907004 B | +| Build_ExtHashMap | 100000 | 29,149,824.12 ns | 15375.0000 | 2750.0000 | 62.5000 | 128198060 B | +| Build_PersistentMap | 100000 | 24,745,757.59 ns | 27687.5000 | 375.0000 | - | 231722008 B | +| Build_TransientMap | 100000 | 9,137,195.74 ns | 406.2500 | 234.3750 | - | 3460512 B | +| Retrieve_ImmDict | 100000 | 1,259,618.31 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 100000 | 975,518.14 ns | - | - | - | - | +| Retrieve_ExtMap | 100000 | 1,535,487.85 ns | - | - | - | - | +| Retrieve_ExtHashMap | 100000 | 284,590.55 ns | - | - | - | - | +| Retrieve_PersistentMap | 100000 | 429,001.27 ns | - | - | - | - | +| Update_ImmDict | 100000 | 5,705,786.31 ns | 1093.7500 | 906.2500 | - | 9183488 B | +| Update_PersistentMap | 100000 | 4,056,612.12 ns | 2945.3125 | 2781.2500 | 15.6250 | 23984.39 KB | +| Update_TransientMap | 100000 | 1,145,551.13 ns | 248.0469 | 199.2188 | - | 2081568 B | +| Update_ImmSortedDict | 100000 | 4,433,611.11 ns | 953.1250 | 796.8750 | - | 8021136 B | +| Update_ExtMap | 100000 | 3,901,065.86 ns | 937.5000 | 789.0625 | - | 7848704 B | +| Update_ExtHashMap | 100000 | 2,696,228.39 ns | 1289.0625 | 960.9375 | - | 10805952 B | +| UpdateSet_ImmDict | 100000 | 5,340,382.88 ns | 1109.3750 | 867.1875 | - | 9318896 B | +| UpdateSet_PersistentMap | 100000 | 4,629,564.21 ns | 2984.3750 | 1906.2500 | 39.0625 | 24060.44 KB | +| UpdateSet_TransientMap | 100000 | 1,332,859.76 ns | 250.0000 | 208.9844 | - | 2099520 B | +| UpdateSet_ImmSortedDict | 100000 | 4,418,076.49 ns | 1000.0000 | 992.1875 | - | 8396544 B | +| UpdateSet_ExtMap | 100000 | 3,107,339.72 ns | 996.0938 | 507.8125 | - | 8349248 B | +| UpdateSet_ExtHashMap | 100000 | 2,630,473.81 ns | 1292.9688 | 976.5625 | - | 10845480 B | +| Iterate_ImmDict | 100000 | 1,550,040.28 ns | - | - | - | - | +| Iterate_PersistentMap | 100000 | 149,743.16 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100000 | 723,978.27 ns | - | - | - | - | +| Iterate_ExtMap | 100000 | 504,204.91 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 100000 | 1,936,574.10 ns | 257.8125 | - | - | 2164800 B | +| Remove_ImmDict | 100000 | 5,419,879.00 ns | 1093.7500 | 914.0625 | - | 9149160 B | +| Remove_TransientMap | 100000 | 951,332.63 ns | 219.7266 | 155.2734 | - | 1839264 B | +| Remove_ImmSortedDict | 100000 | 4,203,794.51 ns | 953.1250 | 781.2500 | - | 8028144 B | +| Remove_ExtMap | 100000 | 3,896,109.04 ns | 929.6875 | 789.0625 | - | 7824560 B | +| Remove_ExtHashMap | 100000 | 2,816,957.99 ns | 1277.3438 | 914.0625 | - | 10709360 B | #+end_src -Lastly, here is a comparison of how things look compared to itself for when the prefixes are turned off for strings. This relies on regular linear string searches. This is however STILL a pretty good benchmark for all ordered dicts, since the strings are random, meaning the string comparison can stop almost immediately. For real world keys, all hash based dicts will be better, with everything regarding getting or setting a single key. +* String keys + +These benchmarks act like above, but do not insert keys in a specific order. Sorting them before will yield a speed boost. One uses the standardkeystrategy (does linear string comparisons) and one uses the unicodstrategy which encodes the first 8 bytes as a long and uses avx to search for keys. #+begin_src -| Method | N | KeyLength | Mean | Gen0 | Gen1 | Allocated | -|--------------------------- |------ |---------- |----------------:|---------:|---------:|----------:| -| 'Build: NiceBTree' | 10000 | 10 | 2,037,851.45 ns | 35.1563 | 15.6250 | 644600 B | -| 'Build: MS HashDict' | 10000 | 10 | 1,647,876.61 ns | 37.1094 | 15.6250 | 640096 B | -| 'Build: MS SortedDict' | 10000 | 10 | 3,853,709.48 ns | 31.2500 | 11.7188 | 560112 B | -| 'Build: LangExt HashMap' | 10000 | 10 | 1,612,117.07 ns | 472.6563 | 154.2969 | 7919328 B | -| 'Build: LangExt Map' | 10000 | 10 | 5,363,298.26 ns | 507.8125 | 203.1250 | 8594784 B | -| 'Read: NiceBTree' | 10000 | 10 | 36.30 ns | - | - | - | -| 'Read: MS HashDict' | 10000 | 10 | 12.66 ns | - | - | - | -| 'Read: MS SortedDict' | 10000 | 10 | 233.59 ns | - | - | - | -| 'Read: LangExt HashMap' | 10000 | 10 | 28.61 ns | - | - | - | -| 'Read: LangExt Map' | 10000 | 10 | 268.13 ns | - | - | - | -| 'Iterate: NiceBTree' | 10000 | 10 | 12,630.95 ns | - | - | - | -| 'Iterate: MS HashDict' | 10000 | 10 | 151,314.44 ns | - | - | - | -| 'Iterate: MS SortedDict' | 10000 | 10 | 57,402.20 ns | - | - | - | -| 'Iterate: LangExt HashMap' | 10000 | 10 | 148,980.47 ns | 10.0098 | - | 170712 B | -| 'Iterate: LangExt Map' | 10000 | 10 | 34,428.07 ns | - | - | 32 B | -| 'Update: NiceBTree' | 10000 | 10 | 303.01 ns | 0.2027 | 0.0024 | 3392 B | -| 'Update: MS HashDict' | 10000 | 10 | 48.36 ns | 0.0100 | - | 168 B | -| 'Update: MS SortedDict' | 10000 | 10 | 137.47 ns | 0.0196 | - | 328 B | -| 'Update: LangExt HashMap' | 10000 | 10 | 102.57 ns | 0.0502 | 0.0001 | 840 B | -| 'Update: LangExt Map' | 10000 | 10 | 122.54 ns | 0.0186 | - | 312 B | -| 'Build: NiceBTree' | 10000 | 50 | 2,020,984.87 ns | 35.1563 | 11.7188 | 624248 B | -| 'Build: MS HashDict' | 10000 | 50 | 1,811,186.24 ns | 37.1094 | 15.6250 | 640096 B | -| 'Build: MS SortedDict' | 10000 | 50 | 3,883,214.25 ns | 31.2500 | 15.6250 | 560112 B | -| 'Build: LangExt HashMap' | 10000 | 50 | 1,784,616.64 ns | 472.6563 | 154.2969 | 7926712 B | -| 'Build: LangExt Map' | 10000 | 50 | 5,248,030.22 ns | 507.8125 | 203.1250 | 8544720 B | -| 'Read: NiceBTree' | 10000 | 50 | 40.64 ns | - | - | - | -| 'Read: MS HashDict' | 10000 | 50 | 29.91 ns | - | - | - | -| 'Read: MS SortedDict' | 10000 | 50 | 255.55 ns | - | - | - | -| 'Read: LangExt HashMap' | 10000 | 50 | 47.61 ns | - | - | - | -| 'Read: LangExt Map' | 10000 | 50 | 255.68 ns | - | - | - | -| 'Iterate: NiceBTree' | 10000 | 50 | 12,718.71 ns | - | - | - | -| 'Iterate: MS HashDict' | 10000 | 50 | 170,815.59 ns | - | - | - | -| 'Iterate: MS SortedDict' | 10000 | 50 | 68,982.58 ns | - | - | - | -| 'Iterate: LangExt HashMap' | 10000 | 50 | 144,442.27 ns | 9.7656 | - | 165600 B | -| 'Iterate: LangExt Map' | 10000 | 50 | 35,082.49 ns | - | - | 32 B | -| 'Update: NiceBTree' | 10000 | 50 | 393.56 ns | 0.2027 | 0.0024 | 3392 B | -| 'Update: MS HashDict' | 10000 | 50 | 114.57 ns | 0.0215 | - | 360 B | -| 'Update: MS SortedDict' | 10000 | 50 | 65.51 ns | 0.0129 | - | 216 B | -| 'Update: LangExt HashMap' | 10000 | 50 | 103.28 ns | 0.0535 | - | 896 B | -| 'Update: LangExt Map' | 10000 | 50 | 67.62 ns | 0.0119 | - | 200 B | +``` + +BenchmarkDotNet v0.15.8, Linux Fedora Linux 43 (Container Image) +AMD Ryzen 9 5900X 3.69GHz, 1 CPU, 24 logical and 12 physical cores +.NET SDK 10.0.104 + [Host] : .NET 10.0.4 (10.0.4, 10.0.426.12010), X64 RyuJIT x86-64-v3 + ShortRun : .NET 10.0.4 (10.0.4, 10.0.426.12010), X64 RyuJIT x86-64-v3 + +Job=ShortRun IterationCount=3 LaunchCount=1 +WarmupCount=3 + +``` +| Method | N | StringLength | Mean | Gen0 | Gen1 | Gen2 | Allocated | +|--------------------------------- |------- |------------- |------------------:|-----------:|----------:|---------:|------------:| +| Build_TransientMap_Standard | 100 | 8 | 42,942.85 ns | 0.7324 | - | - | 6216 B | +| Build_TransientMap_Unicode | 100 | 8 | 12,289.81 ns | 0.8850 | 0.0153 | - | 7528 B | +| Build_ImmDict | 100 | 8 | 14,166.14 ns | 5.4016 | 0.0610 | - | 45280 B | +| Build_ImmSortedDict | 100 | 8 | 22,037.60 ns | 4.2114 | 0.0305 | - | 35472 B | +| Build_ExtMap | 100 | 8 | 22,837.07 ns | 5.6152 | 0.0610 | - | 47104 B | +| Build_ExtHashMap | 100 | 8 | 8,907.91 ns | 3.9673 | 0.0305 | - | 33240 B | +| Retrieve_ImmDict | 100 | 8 | 80.81 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100 | 8 | 4,956.86 ns | 0.0534 | - | - | 480 B | +| Retrieve_PersistentMap_Unicode | 100 | 8 | 148.61 ns | - | - | - | - | +| Update_ImmDict | 100 | 8 | 1,089.03 ns | 0.4215 | - | - | 3536 B | +| Update_PersistentMap_Standard | 100 | 8 | 8,182.15 ns | 2.3956 | 0.0305 | - | 20160 B | +| Update_PersistentMap_Unicode | 100 | 8 | 1,954.07 ns | 2.7046 | 0.0496 | - | 22640 B | +| Update_TransientMap_Standard | 100 | 8 | 7,007.11 ns | 0.4349 | - | - | 3640 B | +| Update_TransientMap_Unicode | 100 | 8 | 667.55 ns | 0.4644 | 0.0057 | - | 3888 B | +| Update_ImmSortedDict | 100 | 8 | 1,741.49 ns | 0.3662 | - | - | 3072 B | +| Update_ExtMap | 100 | 8 | 1,812.71 ns | 0.4120 | - | - | 3456 B | +| Update_ExtHashMap | 100 | 8 | 767.69 ns | 0.5360 | 0.0010 | - | 4488 B | +| UpdateSet_ImmDict | 100 | 8 | 1,508.45 ns | 0.5589 | 0.0019 | - | 4688 B | +| UpdateSet_PersistentMap_Standard | 100 | 8 | 6,877.98 ns | 2.4033 | 0.0381 | - | 20160 B | +| UpdateSet_PersistentMap_Unicode | 100 | 8 | 2,660.42 ns | 2.7046 | 0.0534 | - | 22640 B | +| UpdateSet_TransientMap_Standard | 100 | 8 | 5,415.27 ns | 0.4349 | - | - | 3640 B | +| UpdateSet_TransientMap_Unicode | 100 | 8 | 1,212.47 ns | 0.4635 | 0.0057 | - | 3888 B | +| UpdateSet_ImmSortedDict | 100 | 8 | 2,424.14 ns | 0.4578 | - | - | 3840 B | +| UpdateSet_ExtMap | 100 | 8 | 2,111.13 ns | 0.4730 | - | - | 3960 B | +| UpdateSet_ExtHashMap | 100 | 8 | 876.06 ns | 0.5646 | 0.0019 | - | 4728 B | +| Iterate_ImmDict | 100 | 8 | 1,272.01 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 100 | 8 | 191.98 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100 | 8 | 475.35 ns | - | - | - | - | +| Iterate_ExtMap | 100 | 8 | 322.36 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 100 | 8 | 1,179.25 ns | 0.2747 | - | - | 2304 B | +| Iterate_PersistentMap_Unicode | 100 | 8 | 191.41 ns | - | - | - | - | +| Remove_ImmDict | 100 | 8 | 1,420.61 ns | 0.5360 | 0.0019 | - | 4496 B | +| Remove_PersistentMap_Standard | 100 | 8 | 8,294.72 ns | 2.4261 | 0.0305 | - | 20400 B | +| Remove_PersistentMap_Unicode | 100 | 8 | 2,597.23 ns | 2.6779 | 0.0496 | - | 22400 B | +| Remove_TransientMap_Standard | 100 | 8 | 7,178.94 ns | 0.4654 | - | - | 3928 B | +| Remove_TransientMap_Unicode | 100 | 8 | 1,446.12 ns | 0.4406 | 0.0057 | - | 3688 B | +| Remove_ImmSortedDict | 100 | 8 | 2,031.56 ns | 0.3777 | - | - | 3168 B | +| Remove_ExtMap | 100 | 8 | 2,274.33 ns | 0.5798 | - | - | 4856 B | +| Remove_ExtHashMap | 100 | 8 | 817.02 ns | 0.5102 | 0.0019 | - | 4272 B | +| Build_TransientMap_Standard | 100 | 50 | 43,518.39 ns | 0.7324 | - | - | 6216 B | +| Build_TransientMap_Unicode | 100 | 50 | 12,061.37 ns | 0.8850 | 0.0153 | - | 7528 B | +| Build_ImmDict | 100 | 50 | 16,497.93 ns | 5.4016 | 0.0610 | - | 45216 B | +| Build_ImmSortedDict | 100 | 50 | 22,820.20 ns | 4.2114 | 0.0305 | - | 35232 B | +| Build_ExtMap | 100 | 50 | 22,925.57 ns | 5.6458 | 0.0610 | - | 47328 B | +| Build_ExtHashMap | 100 | 50 | 13,019.93 ns | 4.3945 | 0.0305 | - | 36776 B | +| Retrieve_ImmDict | 100 | 50 | 286.77 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100 | 50 | 4,620.16 ns | 0.0534 | - | - | 480 B | +| Retrieve_PersistentMap_Unicode | 100 | 50 | 158.67 ns | - | - | - | - | +| Update_ImmDict | 100 | 50 | 1,490.11 ns | 0.4902 | 0.0019 | - | 4112 B | +| Update_PersistentMap_Standard | 100 | 50 | 7,007.05 ns | 2.4033 | 0.0381 | - | 20160 B | +| Update_PersistentMap_Unicode | 100 | 50 | 1,976.44 ns | 2.7046 | 0.0534 | - | 22640 B | +| Update_TransientMap_Standard | 100 | 50 | 6,208.54 ns | 0.4349 | - | - | 3640 B | +| Update_TransientMap_Unicode | 100 | 50 | 665.76 ns | 0.4644 | 0.0057 | - | 3888 B | +| Update_ImmSortedDict | 100 | 50 | 2,133.72 ns | 0.4120 | - | - | 3456 B | +| Update_ExtMap | 100 | 50 | 1,842.17 ns | 0.4253 | - | - | 3568 B | +| Update_ExtHashMap | 100 | 50 | 971.20 ns | 0.5474 | 0.0019 | - | 4592 B | +| UpdateSet_ImmDict | 100 | 50 | 1,816.66 ns | 0.6046 | 0.0019 | - | 5072 B | +| UpdateSet_PersistentMap_Standard | 100 | 50 | 6,616.38 ns | 2.4033 | 0.0381 | - | 20160 B | +| UpdateSet_PersistentMap_Unicode | 100 | 50 | 2,517.24 ns | 2.7046 | 0.0496 | - | 22640 B | +| UpdateSet_TransientMap_Standard | 100 | 50 | 5,282.92 ns | 0.4349 | - | - | 3640 B | +| UpdateSet_TransientMap_Unicode | 100 | 50 | 1,214.38 ns | 0.4635 | 0.0057 | - | 3888 B | +| UpdateSet_ImmSortedDict | 100 | 50 | 2,531.36 ns | 0.4730 | - | - | 3984 B | +| UpdateSet_ExtMap | 100 | 50 | 2,399.90 ns | 0.5913 | - | - | 4968 B | +| UpdateSet_ExtHashMap | 100 | 50 | 1,041.57 ns | 0.5512 | 0.0019 | - | 4624 B | +| Iterate_ImmDict | 100 | 50 | 1,265.60 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 100 | 50 | 183.51 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100 | 50 | 487.68 ns | - | - | - | - | +| Iterate_ExtMap | 100 | 50 | 313.58 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 100 | 50 | 1,174.73 ns | 0.2747 | - | - | 2304 B | +| Iterate_PersistentMap_Unicode | 100 | 50 | 190.36 ns | - | - | - | - | +| Remove_ImmDict | 100 | 50 | 1,566.27 ns | 0.5283 | 0.0019 | - | 4432 B | +| Remove_PersistentMap_Standard | 100 | 50 | 7,539.09 ns | 2.4338 | 0.0381 | - | 20400 B | +| Remove_PersistentMap_Unicode | 100 | 50 | 2,572.60 ns | 2.6779 | 0.0458 | - | 22400 B | +| Remove_TransientMap_Standard | 100 | 50 | 6,450.13 ns | 0.4654 | - | - | 3928 B | +| Remove_TransientMap_Unicode | 100 | 50 | 1,408.78 ns | 0.4406 | 0.0057 | - | 3688 B | +| Remove_ImmSortedDict | 100 | 50 | 2,241.88 ns | 0.3891 | - | - | 3264 B | +| Remove_ExtMap | 100 | 50 | 2,156.69 ns | 0.4387 | - | - | 3680 B | +| Remove_ExtHashMap | 100 | 50 | 927.47 ns | 0.4807 | 0.0010 | - | 4024 B | +| Build_TransientMap_Standard | 1000 | 8 | 741,046.07 ns | 5.8594 | - | - | 49512 B | +| Build_TransientMap_Unicode | 1000 | 8 | 176,282.35 ns | 7.3242 | 0.7324 | - | 62248 B | +| Build_ImmDict | 1000 | 8 | 251,462.15 ns | 79.1016 | 8.3008 | - | 663488 B | +| Build_ImmSortedDict | 1000 | 8 | 420,903.44 ns | 61.0352 | 4.8828 | - | 513312 B | +| Build_ExtMap | 1000 | 8 | 426,942.00 ns | 79.1016 | 7.3242 | - | 662448 B | +| Build_ExtHashMap | 1000 | 8 | 154,509.50 ns | 70.3125 | 4.8828 | - | 589264 B | +| Retrieve_ImmDict | 1000 | 8 | 1,057.94 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 1000 | 8 | 71,081.84 ns | 0.4883 | - | - | 4800 B | +| Retrieve_PersistentMap_Unicode | 1000 | 8 | 2,056.30 ns | - | - | - | - | +| Update_ImmDict | 1000 | 8 | 20,613.44 ns | 7.4768 | 0.2747 | - | 62688 B | +| Update_PersistentMap_Standard | 1000 | 8 | 91,445.80 ns | 24.0479 | 2.3193 | - | 201600 B | +| Update_PersistentMap_Unicode | 1000 | 8 | 29,005.34 ns | 27.0386 | 2.5330 | - | 226400 B | +| Update_TransientMap_Standard | 1000 | 8 | 85,352.16 ns | 4.5166 | 0.4883 | - | 38184 B | +| Update_TransientMap_Unicode | 1000 | 8 | 7,968.40 ns | 4.4250 | 0.4425 | - | 37024 B | +| Update_ImmSortedDict | 1000 | 8 | 32,334.14 ns | 5.7373 | 0.1221 | - | 48288 B | +| Update_ExtMap | 1000 | 8 | 34,280.75 ns | 6.4087 | 0.1831 | - | 53824 B | +| Update_ExtHashMap | 1000 | 8 | 11,163.54 ns | 7.1716 | 0.2289 | - | 60016 B | +| UpdateSet_ImmDict | 1000 | 8 | 23,570.66 ns | 8.3618 | 0.3967 | - | 69984 B | +| UpdateSet_PersistentMap_Standard | 1000 | 8 | 103,655.77 ns | 24.0479 | 2.1973 | - | 201600 B | +| UpdateSet_PersistentMap_Unicode | 1000 | 8 | 33,858.71 ns | 27.0386 | 2.6245 | - | 226400 B | +| UpdateSet_TransientMap_Standard | 1000 | 8 | 86,914.93 ns | 4.1504 | 0.3662 | - | 35368 B | +| UpdateSet_TransientMap_Unicode | 1000 | 8 | 12,939.69 ns | 4.5929 | 0.4883 | - | 38432 B | +| UpdateSet_ImmSortedDict | 1000 | 8 | 37,789.08 ns | 6.3477 | 0.2441 | - | 53232 B | +| UpdateSet_ExtMap | 1000 | 8 | 38,304.46 ns | 7.6904 | 0.3052 | - | 64576 B | +| UpdateSet_ExtHashMap | 1000 | 8 | 14,695.01 ns | 7.7057 | 0.2747 | - | 64464 B | +| Iterate_ImmDict | 1000 | 8 | 13,240.07 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 1000 | 8 | 1,628.88 ns | - | - | - | - | +| Iterate_ImmSortedDict | 1000 | 8 | 4,933.29 ns | - | - | - | - | +| Iterate_ExtMap | 1000 | 8 | 3,191.58 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 1000 | 8 | 14,380.64 ns | 2.6550 | - | - | 22320 B | +| Iterate_PersistentMap_Unicode | 1000 | 8 | 1,647.93 ns | - | - | - | - | +| Remove_ImmDict | 1000 | 8 | 20,527.32 ns | 7.6599 | 0.3052 | - | 64160 B | +| Remove_PersistentMap_Standard | 1000 | 8 | 106,140.73 ns | 24.2920 | 1.5869 | - | 204000 B | +| Remove_PersistentMap_Unicode | 1000 | 8 | 30,242.16 ns | 26.7639 | 2.4414 | - | 224000 B | +| Remove_TransientMap_Standard | 1000 | 8 | 91,057.33 ns | 4.6387 | 0.4883 | - | 39224 B | +| Remove_TransientMap_Unicode | 1000 | 8 | 14,218.28 ns | 3.7994 | 0.3662 | - | 31848 B | +| Remove_ImmSortedDict | 1000 | 8 | 33,413.24 ns | 5.6763 | 0.1221 | - | 47616 B | +| Remove_ExtMap | 1000 | 8 | 37,472.01 ns | 6.8970 | 0.1831 | - | 57744 B | +| Remove_ExtHashMap | 1000 | 8 | 12,185.93 ns | 7.9803 | 0.2441 | - | 66864 B | +| Build_TransientMap_Standard | 1000 | 50 | 727,350.80 ns | 4.8828 | - | - | 44992 B | +| Build_TransientMap_Unicode | 1000 | 50 | 176,948.61 ns | 7.0801 | 0.4883 | - | 59368 B | +| Build_ImmDict | 1000 | 50 | 285,938.59 ns | 78.6133 | 8.3008 | - | 660096 B | +| Build_ImmSortedDict | 1000 | 50 | 433,882.40 ns | 61.0352 | 5.3711 | - | 511344 B | +| Build_ExtMap | 1000 | 50 | 412,922.58 ns | 78.1250 | 7.3242 | - | 656288 B | +| Build_ExtHashMap | 1000 | 50 | 185,309.94 ns | 70.3125 | 4.8828 | - | 589128 B | +| Retrieve_ImmDict | 1000 | 50 | 3,151.77 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 1000 | 50 | 76,705.78 ns | 0.4883 | - | - | 4800 B | +| Retrieve_PersistentMap_Unicode | 1000 | 50 | 2,077.37 ns | - | - | - | - | +| Update_ImmDict | 1000 | 50 | 23,312.29 ns | 7.5073 | 0.3052 | - | 62880 B | +| Update_PersistentMap_Standard | 1000 | 50 | 93,945.90 ns | 24.0479 | 2.0752 | - | 201600 B | +| Update_PersistentMap_Unicode | 1000 | 50 | 29,787.05 ns | 27.0386 | 2.2888 | - | 226400 B | +| Update_TransientMap_Standard | 1000 | 50 | 85,349.42 ns | 3.7842 | 0.2441 | - | 32552 B | +| Update_TransientMap_Unicode | 1000 | 50 | 7,349.19 ns | 4.0894 | 0.4044 | - | 34208 B | +| Update_ImmSortedDict | 1000 | 50 | 32,705.50 ns | 5.6763 | 0.1221 | - | 47952 B | +| Update_ExtMap | 1000 | 50 | 33,742.93 ns | 6.5308 | 0.1831 | - | 54720 B | +| Update_ExtHashMap | 1000 | 50 | 13,720.77 ns | 7.2479 | 0.2289 | - | 60688 B | +| UpdateSet_ImmDict | 1000 | 50 | 24,725.01 ns | 8.0566 | 0.3967 | - | 67424 B | +| UpdateSet_PersistentMap_Standard | 1000 | 50 | 106,965.60 ns | 24.1699 | 1.9531 | - | 202504 B | +| UpdateSet_PersistentMap_Unicode | 1000 | 50 | 34,398.72 ns | 27.2217 | 2.6245 | - | 227840 B | +| UpdateSet_TransientMap_Standard | 1000 | 50 | 89,942.92 ns | 3.9063 | 0.3662 | - | 33456 B | +| UpdateSet_TransientMap_Unicode | 1000 | 50 | 13,555.52 ns | 4.4250 | 0.4578 | - | 37056 B | +| UpdateSet_ImmSortedDict | 1000 | 50 | 37,035.94 ns | 6.2866 | 0.1831 | - | 53088 B | +| UpdateSet_ExtMap | 1000 | 50 | 39,025.94 ns | 7.8735 | 0.3052 | - | 66200 B | +| UpdateSet_ExtHashMap | 1000 | 50 | 15,654.51 ns | 7.5989 | 0.2441 | - | 63608 B | +| Iterate_ImmDict | 1000 | 50 | 12,858.73 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 1000 | 50 | 1,674.98 ns | - | - | - | - | +| Iterate_ImmSortedDict | 1000 | 50 | 4,882.38 ns | - | - | - | - | +| Iterate_ExtMap | 1000 | 50 | 3,498.40 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 1000 | 50 | 14,585.65 ns | 2.7008 | - | - | 22608 B | +| Iterate_PersistentMap_Unicode | 1000 | 50 | 1,654.17 ns | - | - | - | - | +| Remove_ImmDict | 1000 | 50 | 24,001.50 ns | 7.6904 | 0.3052 | - | 64480 B | +| Remove_PersistentMap_Standard | 1000 | 50 | 115,583.75 ns | 24.2920 | 1.7090 | - | 204000 B | +| Remove_PersistentMap_Unicode | 1000 | 50 | 30,177.90 ns | 26.7639 | 2.2583 | - | 224000 B | +| Remove_TransientMap_Standard | 1000 | 50 | 104,629.87 ns | 3.9063 | 0.2441 | - | 33592 B | +| Remove_TransientMap_Unicode | 1000 | 50 | 13,859.15 ns | 3.2959 | 0.2899 | - | 27624 B | +| Remove_ImmSortedDict | 1000 | 50 | 34,305.81 ns | 5.6763 | 0.1221 | - | 47664 B | +| Remove_ExtMap | 1000 | 50 | 36,962.41 ns | 6.9580 | 0.2441 | - | 58640 B | +| Remove_ExtHashMap | 1000 | 50 | 14,045.01 ns | 7.8583 | 0.1984 | - | 65800 B | +| Build_TransientMap_Standard | 10000 | 8 | 10,481,091.44 ns | 46.8750 | 15.6250 | - | 453272 B | +| Build_TransientMap_Unicode | 10000 | 8 | 2,288,777.17 ns | 66.4063 | 19.5313 | - | 576584 B | +| Build_ImmDict | 10000 | 8 | 4,451,716.58 ns | 1046.8750 | 500.0000 | - | 8790528 B | +| Build_ImmSortedDict | 10000 | 8 | 6,589,276.41 ns | 804.6875 | 281.2500 | - | 6767232 B | +| Build_ExtMap | 10000 | 8 | 6,466,222.77 ns | 1015.6250 | 437.5000 | - | 8542480 B | +| Build_ExtHashMap | 10000 | 8 | 1,987,067.22 ns | 945.3125 | 312.5000 | - | 7921664 B | +| Retrieve_ImmDict | 10000 | 8 | 23,653.66 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 10000 | 8 | 1,067,006.38 ns | 7.8125 | - | - | 72000 B | +| Retrieve_PersistentMap_Unicode | 10000 | 8 | 33,794.37 ns | - | - | - | - | +| Update_ImmDict | 10000 | 8 | 393,904.16 ns | 100.5859 | 25.3906 | - | 844992 B | +| Update_PersistentMap_Standard | 10000 | 8 | 1,373,414.67 ns | 304.6875 | 132.8125 | - | 2560000 B | +| Update_PersistentMap_Unicode | 10000 | 8 | 416,266.85 ns | 366.2109 | 160.1563 | - | 3064000 B | +| Update_TransientMap_Standard | 10000 | 8 | 1,160,717.93 ns | 41.0156 | 13.6719 | - | 346280 B | +| Update_TransientMap_Unicode | 10000 | 8 | 154,306.58 ns | 40.7715 | 16.6016 | - | 341792 B | +| Update_ImmSortedDict | 10000 | 8 | 583,839.85 ns | 76.1719 | 15.6250 | - | 640368 B | +| Update_ExtMap | 10000 | 8 | 571,924.12 ns | 87.8906 | 22.4609 | - | 735640 B | +| Update_ExtHashMap | 10000 | 8 | 164,331.80 ns | 102.7832 | 22.9492 | - | 860152 B | +| UpdateSet_ImmDict | 10000 | 8 | 433,669.48 ns | 108.3984 | 29.2969 | - | 907072 B | +| UpdateSet_PersistentMap_Standard | 10000 | 8 | 1,426,063.43 ns | 306.6406 | 130.8594 | - | 2572328 B | +| UpdateSet_PersistentMap_Unicode | 10000 | 8 | 502,233.19 ns | 368.1641 | 154.2969 | - | 3086432 B | +| UpdateSet_TransientMap_Standard | 10000 | 8 | 1,183,758.86 ns | 41.0156 | 15.6250 | - | 357200 B | +| UpdateSet_TransientMap_Unicode | 10000 | 8 | 224,362.69 ns | 43.7012 | 16.3574 | - | 365632 B | +| UpdateSet_ImmSortedDict | 10000 | 8 | 648,222.74 ns | 82.0313 | 19.5313 | - | 687648 B | +| UpdateSet_ExtMap | 10000 | 8 | 638,998.68 ns | 99.6094 | 25.3906 | - | 833752 B | +| UpdateSet_ExtHashMap | 10000 | 8 | 188,297.97 ns | 104.9805 | 23.9258 | - | 878640 B | +| Iterate_ImmDict | 10000 | 8 | 180,912.56 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 10000 | 8 | 17,134.49 ns | - | - | - | - | +| Iterate_ImmSortedDict | 10000 | 8 | 55,355.92 ns | - | - | - | - | +| Iterate_ExtMap | 10000 | 8 | 66,171.51 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 10000 | 8 | 173,270.08 ns | 20.2637 | - | - | 171360 B | +| Iterate_PersistentMap_Unicode | 10000 | 8 | 17,291.12 ns | - | - | - | - | +| Remove_ImmDict | 10000 | 8 | 398,833.85 ns | 102.0508 | 24.9023 | - | 857216 B | +| Remove_PersistentMap_Standard | 10000 | 8 | 1,488,081.64 ns | 310.5469 | 123.0469 | - | 2608000 B | +| Remove_PersistentMap_Unicode | 10000 | 8 | 492,391.59 ns | 363.2813 | 180.6641 | - | 3041408 B | +| Remove_TransientMap_Standard | 10000 | 8 | 1,334,741.70 ns | 44.9219 | 15.6250 | - | 388696 B | +| Remove_TransientMap_Unicode | 10000 | 8 | 224,694.52 ns | 38.0859 | 15.1367 | - | 319240 B | +| Remove_ImmSortedDict | 10000 | 8 | 624,219.05 ns | 77.1484 | 15.6250 | - | 652944 B | +| Remove_ExtMap | 10000 | 8 | 648,812.07 ns | 91.7969 | 20.5078 | - | 774000 B | +| Remove_ExtHashMap | 10000 | 8 | 187,697.63 ns | 104.2480 | 21.4844 | - | 873600 B | +| Build_TransientMap_Standard | 10000 | 50 | 11,121,008.03 ns | 46.8750 | 15.6250 | - | 453272 B | +| Build_TransientMap_Unicode | 10000 | 50 | 2,345,447.27 ns | 66.4063 | 23.4375 | - | 567112 B | +| Build_ImmDict | 10000 | 50 | 4,711,900.43 ns | 1039.0625 | 507.8125 | - | 8746752 B | +| Build_ImmSortedDict | 10000 | 50 | 6,774,329.88 ns | 804.6875 | 265.6250 | - | 6751584 B | +| Build_ExtMap | 10000 | 50 | 6,748,429.60 ns | 1015.6250 | 429.6875 | - | 8544720 B | +| Build_ExtHashMap | 10000 | 50 | 2,211,770.12 ns | 941.4063 | 332.0313 | - | 7894224 B | +| Retrieve_ImmDict | 10000 | 50 | 53,674.39 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 10000 | 50 | 1,099,621.23 ns | 7.8125 | - | - | 72000 B | +| Retrieve_PersistentMap_Unicode | 10000 | 50 | 34,282.21 ns | - | - | - | - | +| Update_ImmDict | 10000 | 50 | 416,110.39 ns | 101.0742 | 24.9023 | - | 845696 B | +| Update_PersistentMap_Standard | 10000 | 50 | 1,432,050.69 ns | 304.6875 | 125.0000 | - | 2560000 B | +| Update_PersistentMap_Unicode | 10000 | 50 | 482,376.79 ns | 366.2109 | 158.2031 | - | 3064000 B | +| Update_TransientMap_Standard | 10000 | 50 | 1,185,346.37 ns | 39.0625 | 11.7188 | - | 339240 B | +| Update_TransientMap_Unicode | 10000 | 50 | 153,479.49 ns | 39.5508 | 13.9160 | - | 331136 B | +| Update_ImmSortedDict | 10000 | 50 | 578,927.03 ns | 76.1719 | 14.6484 | - | 641616 B | +| Update_ExtMap | 10000 | 50 | 582,689.89 ns | 86.9141 | 20.5078 | - | 732896 B | +| Update_ExtHashMap | 10000 | 50 | 195,769.80 ns | 102.5391 | 20.5078 | - | 859328 B | +| UpdateSet_ImmDict | 10000 | 50 | 460,644.35 ns | 109.3750 | 30.2734 | - | 915264 B | +| UpdateSet_PersistentMap_Standard | 10000 | 50 | 1,488,123.25 ns | 306.6406 | 132.8125 | - | 2575040 B | +| UpdateSet_PersistentMap_Unicode | 10000 | 50 | 516,710.59 ns | 368.1641 | 152.3438 | - | 3085600 B | +| UpdateSet_TransientMap_Standard | 10000 | 50 | 1,257,184.91 ns | 41.0156 | 13.6719 | - | 355688 B | +| UpdateSet_TransientMap_Unicode | 10000 | 50 | 222,602.17 ns | 42.2363 | 15.8691 | - | 354144 B | +| UpdateSet_ImmSortedDict | 10000 | 50 | 659,407.65 ns | 82.0313 | 19.5313 | - | 691728 B | +| UpdateSet_ExtMap | 10000 | 50 | 652,772.00 ns | 98.6328 | 23.4375 | - | 829552 B | +| UpdateSet_ExtHashMap | 10000 | 50 | 216,637.22 ns | 104.9805 | 24.1699 | - | 878840 B | +| Iterate_ImmDict | 10000 | 50 | 172,211.95 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 10000 | 50 | 17,227.48 ns | - | - | - | - | +| Iterate_ImmSortedDict | 10000 | 50 | 56,105.18 ns | - | - | - | - | +| Iterate_ExtMap | 10000 | 50 | 70,322.76 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 10000 | 50 | 176,803.08 ns | 20.0195 | - | - | 168264 B | +| Iterate_PersistentMap_Unicode | 10000 | 50 | 17,473.52 ns | - | - | - | - | +| Remove_ImmDict | 10000 | 50 | 429,520.72 ns | 102.5391 | 28.3203 | - | 860032 B | +| Remove_PersistentMap_Standard | 10000 | 50 | 1,558,056.72 ns | 310.5469 | 125.0000 | - | 2610816 B | +| Remove_PersistentMap_Unicode | 10000 | 50 | 522,677.95 ns | 363.2813 | 158.2031 | - | 3042816 B | +| Remove_TransientMap_Standard | 10000 | 50 | 1,333,105.09 ns | 44.9219 | 15.6250 | - | 391512 B | +| Remove_TransientMap_Unicode | 10000 | 50 | 223,026.23 ns | 37.3535 | 13.1836 | - | 312808 B | +| Remove_ImmSortedDict | 10000 | 50 | 611,137.62 ns | 77.1484 | 15.6250 | - | 651840 B | +| Remove_ExtMap | 10000 | 50 | 630,117.55 ns | 91.7969 | 21.4844 | - | 771256 B | +| Remove_ExtHashMap | 10000 | 50 | 208,051.51 ns | 104.7363 | 20.7520 | - | 877216 B | +| Build_TransientMap_Standard | 100000 | 8 | 158,074,169.00 ns | 500.0000 | 250.0000 | - | 4524208 B | +| Build_TransientMap_Unicode | 100000 | 8 | 40,354,289.36 ns | 692.3077 | 538.4615 | - | 5798728 B | +| Build_ImmDict | 100000 | 8 | 95,840,918.80 ns | 13200.0000 | 4600.0000 | 200.0000 | 109352965 B | +| Build_ImmSortedDict | 100000 | 8 | 145,451,097.25 ns | 10000.0000 | 3750.0000 | - | 83946432 B | +| Build_ExtMap | 100000 | 8 | 121,026,729.25 ns | 12500.0000 | 5750.0000 | - | 104660688 B | +| Build_ExtHashMap | 100000 | 8 | 45,582,232.06 ns | 12000.0000 | 2583.3333 | 83.3333 | 99799303 B | +| Retrieve_ImmDict | 100000 | 8 | 1,518,946.53 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100000 | 8 | 15,954,066.23 ns | 93.7500 | - | - | 960000 B | +| Retrieve_PersistentMap_Unicode | 100000 | 8 | 1,344,373.24 ns | - | - | - | - | +| Update_ImmDict | 100000 | 8 | 9,168,537.50 ns | 1265.6250 | 1062.5000 | - | 10588288 B | +| Update_PersistentMap_Standard | 100000 | 8 | 24,537,720.52 ns | 3718.7500 | 2906.2500 | 31.2500 | 31040034 B | +| Update_PersistentMap_Unicode | 100000 | 8 | 11,821,730.59 ns | 4671.8750 | 2500.0000 | 62.5000 | 38640073 B | +| Update_TransientMap_Standard | 100000 | 8 | 18,410,364.31 ns | 406.2500 | 312.5000 | - | 3415688 B | +| Update_TransientMap_Unicode | 100000 | 8 | 3,724,912.85 ns | 414.0625 | 351.5625 | - | 3487208 B | +| Update_ImmSortedDict | 100000 | 8 | 11,002,667.72 ns | 953.1250 | 781.2500 | - | 8020080 B | +| Update_ExtMap | 100000 | 8 | 11,289,391.77 ns | 1093.7500 | 937.5000 | - | 9246400 B | +| Update_ExtHashMap | 100000 | 8 | 3,795,553.44 ns | 1265.6250 | 968.7500 | - | 10624032 B | +| UpdateSet_ImmDict | 100000 | 8 | 9,324,688.19 ns | 1343.7500 | 1046.8750 | - | 11264256 B | +| UpdateSet_PersistentMap_Standard | 100000 | 8 | 28,227,498.72 ns | 3750.0000 | 2593.7500 | 31.2500 | 31143252 B | +| UpdateSet_PersistentMap_Unicode | 100000 | 8 | 12,980,484.49 ns | 4703.1250 | 2250.0000 | 62.5000 | 38809434 B | +| UpdateSet_TransientMap_Standard | 100000 | 8 | 18,455,887.50 ns | 406.2500 | 312.5000 | - | 3514688 B | +| UpdateSet_TransientMap_Unicode | 100000 | 8 | 4,599,394.95 ns | 429.6875 | 382.8125 | - | 3636872 B | +| UpdateSet_ImmSortedDict | 100000 | 8 | 11,611,425.71 ns | 1015.6250 | 875.0000 | - | 8495856 B | +| UpdateSet_ExtMap | 100000 | 8 | 13,256,339.44 ns | 1218.7500 | 1015.6250 | - | 10287328 B | +| UpdateSet_ExtHashMap | 100000 | 8 | 4,056,833.05 ns | 1281.2500 | 992.1875 | - | 10752664 B | +| Iterate_ImmDict | 100000 | 8 | 2,245,167.14 ns | - | - | - | 96 B | +| Iterate_PersistentMap_Standard | 100000 | 8 | 226,078.34 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100000 | 8 | 752,117.01 ns | - | - | - | - | +| Iterate_ExtMap | 100000 | 8 | 1,153,701.70 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 100000 | 8 | 2,747,144.18 ns | 273.4375 | - | - | 2318432 B | +| Iterate_PersistentMap_Unicode | 100000 | 8 | 241,814.65 ns | - | - | - | - | +| Remove_ImmDict | 100000 | 8 | 8,922,500.09 ns | 1281.2500 | 1031.2500 | - | 10744896 B | +| Remove_PersistentMap_Standard | 100000 | 8 | 28,366,797.02 ns | 3812.5000 | 2750.0000 | 31.2500 | 31781143 B | +| Remove_PersistentMap_Unicode | 100000 | 8 | 12,499,542.97 ns | 4640.6250 | 2875.0000 | 62.5000 | 38412730 B | +| Remove_TransientMap_Standard | 100000 | 8 | 21,085,953.04 ns | 468.7500 | 343.7500 | - | 4134328 B | +| Remove_TransientMap_Unicode | 100000 | 8 | 3,227,138.07 ns | 382.8125 | 324.2188 | - | 3226088 B | +| Remove_ImmSortedDict | 100000 | 8 | 10,475,303.83 ns | 953.1250 | 781.2500 | - | 8031888 B | +| Remove_ExtMap | 100000 | 8 | 11,473,612.68 ns | 1140.6250 | 937.5000 | - | 9630448 B | +| Remove_ExtHashMap | 100000 | 8 | 3,920,078.08 ns | 1265.6250 | 914.0625 | - | 10624344 B | +| Build_TransientMap_Standard | 100000 | 50 | 186,991,900.50 ns | 500.0000 | - | - | 4511632 B | +| Build_TransientMap_Unicode | 100000 | 50 | 42,267,081.83 ns | 666.6667 | 500.0000 | - | 5763784 B | +| Build_ImmDict | 100000 | 50 | 91,363,990.94 ns | 13166.6667 | 4833.3333 | 166.6667 | 109457840 B | +| Build_ImmSortedDict | 100000 | 50 | 134,058,261.08 ns | 10000.0000 | 4000.0000 | - | 83731632 B | +| Build_ExtMap | 100000 | 50 | 130,083,384.42 ns | 12250.0000 | 5250.0000 | - | 104371560 B | +| Build_ExtHashMap | 100000 | 50 | 50,176,040.06 ns | 11909.0909 | 2545.4545 | - | 99721040 B | +| Retrieve_ImmDict | 100000 | 50 | 1,878,376.76 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100000 | 50 | 22,662,880.57 ns | 93.7500 | - | - | 960000 B | +| Retrieve_PersistentMap_Unicode | 100000 | 50 | 1,421,803.13 ns | - | - | - | - | +| Update_ImmDict | 100000 | 50 | 8,957,341.32 ns | 1265.6250 | 1062.5000 | - | 10646144 B | +| Update_PersistentMap_Standard | 100000 | 50 | 37,471,664.44 ns | 3692.3077 | 2769.2308 | - | 31040000 B | +| Update_PersistentMap_Unicode | 100000 | 50 | 12,856,950.49 ns | 4656.2500 | 2781.2500 | 46.8750 | 38640047 B | +| Update_TransientMap_Standard | 100000 | 50 | 26,563,266.79 ns | 406.2500 | 312.5000 | - | 3414056 B | +| Update_TransientMap_Unicode | 100000 | 50 | 4,173,539.70 ns | 406.2500 | 335.9375 | - | 3440328 B | +| Update_ImmSortedDict | 100000 | 50 | 11,812,577.59 ns | 953.1250 | 781.2500 | - | 8012928 B | +| Update_ExtMap | 100000 | 50 | 11,842,757.42 ns | 1093.7500 | 906.2500 | - | 9233632 B | +| Update_ExtHashMap | 100000 | 50 | 4,189,884.01 ns | 1265.6250 | 984.3750 | - | 10632736 B | +| UpdateSet_ImmDict | 100000 | 50 | 10,459,268.37 ns | 1343.7500 | 1031.2500 | - | 11256960 B | +| UpdateSet_PersistentMap_Standard | 100000 | 50 | 41,029,434.25 ns | 3666.6667 | 2500.0000 | - | 31141992 B | +| UpdateSet_PersistentMap_Unicode | 100000 | 50 | 14,028,512.79 ns | 4687.5000 | 2421.8750 | 46.8750 | 38835953 B | +| UpdateSet_TransientMap_Standard | 100000 | 50 | 28,314,403.39 ns | 406.2500 | 312.5000 | - | 3509008 B | +| UpdateSet_TransientMap_Unicode | 100000 | 50 | 5,267,848.18 ns | 429.6875 | 359.3750 | - | 3630600 B | +| UpdateSet_ImmSortedDict | 100000 | 50 | 14,089,168.54 ns | 1015.6250 | 890.6250 | - | 8501136 B | +| UpdateSet_ExtMap | 100000 | 50 | 12,785,268.31 ns | 1218.7500 | 1015.6250 | - | 10265544 B | +| UpdateSet_ExtHashMap | 100000 | 50 | 4,418,089.19 ns | 1281.2500 | 992.1875 | - | 10750000 B | +| Iterate_ImmDict | 100000 | 50 | 2,338,660.26 ns | - | - | - | 384 B | +| Iterate_PersistentMap_Standard | 100000 | 50 | 211,359.08 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100000 | 50 | 715,600.57 ns | - | - | - | - | +| Iterate_ExtMap | 100000 | 50 | 1,117,575.94 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 100000 | 50 | 2,975,826.72 ns | 277.3438 | - | - | 2319904 B | +| Iterate_PersistentMap_Unicode | 100000 | 50 | 217,594.04 ns | - | - | - | - | +| Remove_ImmDict | 100000 | 50 | 9,190,906.43 ns | 1281.2500 | 1046.8750 | - | 10741824 B | +| Remove_PersistentMap_Standard | 100000 | 50 | 42,259,057.45 ns | 3727.2727 | 2636.3636 | - | 31774080 B | +| Remove_PersistentMap_Unicode | 100000 | 50 | 11,946,176.86 ns | 4625.0000 | 2859.3750 | 46.8750 | 38402868 B | +| Remove_TransientMap_Standard | 100000 | 50 | 26,120,804.07 ns | 468.7500 | 343.7500 | - | 4127064 B | +| Remove_TransientMap_Unicode | 100000 | 50 | 3,583,417.50 ns | 382.8125 | 312.5000 | - | 3205960 B | +| Remove_ImmSortedDict | 100000 | 50 | 14,164,094.39 ns | 953.1250 | 781.2500 | - | 8032656 B | +| Remove_ExtMap | 100000 | 50 | 13,861,593.95 ns | 1140.6250 | 921.8750 | - | 9617232 B | +| Remove_ExtHashMap | 100000 | 50 | 4,797,368.31 ns | 1265.6250 | 937.5000 | - | 10628320 B | + #+end_src -** Architecture Notes: Key Strategies + Architecture Notes: Key Strategies + + NiceBtree uses =IKeyStrategy= to map generic keys (like =string= or =double=) into sortable =long= prefixes. This achieves two things: 1. Enables AVX512/AVX2 vector instructions to search internal nodes simultaneously. 2. Avoids expensive =IComparable= interface calls or =string.Compare= during the initial descent of the tree, only falling back to exact comparisons when refining the search within a leaf. diff --git a/TestProject1/StandardStrategy.cs b/TestProject1/StandardStrategy.cs new file mode 100644 index 0000000..912c7a7 --- /dev/null +++ b/TestProject1/StandardStrategy.cs @@ -0,0 +1,38 @@ +namespace PersistentMap.Tests; +using PersistentMap; +using System.Linq; +using Xunit; +public class StandardStrategy +{ + private static string GenerateRandomString(int length, Random rnd) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + return new string(Enumerable.Repeat(chars, length).Select(s => s[rnd.Next(s.Length)]).ToArray()); + } + + [Fact] + public void Setup() + { + var N = 1000; + var _stdStrategy = new StandardStrategy(); + var _uniStrategy = new UnicodeStrategy(); + var rnd = new Random(42); + var StringLength = 10; + // Build random strings + var _allKeys = Enumerable.Range(0, N).Select(_ => GenerateRandomString(StringLength, rnd)).Distinct().ToArray(); + + // Regenerate if Distinct() reduced array size (highly unlikely with length 8/50, but safe) + while (_allKeys.Length < N) + { + _allKeys = _allKeys.Concat(new[] { GenerateRandomString(StringLength, rnd) }).Distinct().ToArray(); + } + + var transStd = BaseOrderedMap>.CreateTransient(_stdStrategy); + var transUni = BaseOrderedMap.CreateTransient(_uniStrategy); + for (int i = 0; i < _allKeys.Length; i++) + { + transStd.Set(_allKeys[i], i); + transUni.Set(_allKeys[i], i); + } + } +} \ No newline at end of file diff --git a/benchmarks/MyBenchMarks/StringBenchmarks.cs b/benchmarks/MyBenchMarks/StringBenchmarks.cs new file mode 100644 index 0000000..302e5d1 --- /dev/null +++ b/benchmarks/MyBenchMarks/StringBenchmarks.cs @@ -0,0 +1,445 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using BenchmarkDotNet.Attributes; +using LanguageExt; +using PersistentMap; + +namespace MapBenchmarks; + +[MemoryDiagnoser] +public class StringMapBenchmarks +{ + [Params(100, 1000, 10000, 100000)] + public int N { get; set; } + + [Params(8, 50)] + public int StringLength { get; set; } + + private string[] _allKeys; + private string[] _retrieveKeys; + private string[] _updateKeys; + private string[] _removeKeys; + private string[] _mixedKeys; + + private ImmutableDictionary _immDict; + private ImmutableSortedDictionary _immSortedDict; + private LanguageExt.Map _extMap; + private LanguageExt.HashMap _extHashMap; + + private PersistentMap> _persistentMapStandard; + private PersistentMap _persistentMapUnicode; + + private readonly StandardStrategy _stdStrategy = new StandardStrategy(); + private readonly UnicodeStrategy _uniStrategy = new UnicodeStrategy(); + + [GlobalSetup] + public void Setup() + { + var rnd = new Random(42); + + // Build random strings + _allKeys = Enumerable.Range(0, N).Select(_ => GenerateRandomString(StringLength, rnd)).Distinct().ToArray(); + + // Regenerate if Distinct() reduced array size (highly unlikely with length 8/50, but safe) + while (_allKeys.Length < N) + { + _allKeys = _allKeys.Concat(new[] { GenerateRandomString(StringLength, rnd) }).Distinct().ToArray(); + } + + int subsetSize = Math.Max(1, N / 10); + + var shuffled = _allKeys.OrderBy(x => rnd.Next()).ToArray(); + _retrieveKeys = shuffled.Take(subsetSize).ToArray(); + _updateKeys = shuffled.Skip(subsetSize).Take(subsetSize).ToArray(); + _removeKeys = shuffled.Skip(subsetSize * 2).Take(subsetSize).ToArray(); + + var existingHalf = shuffled.Skip(subsetSize * 3).Take(subsetSize / 2).ToArray(); + var newHalf = Enumerable.Range(0, subsetSize - (subsetSize / 2)).Select(_ => GenerateRandomString(StringLength, rnd)).ToArray(); + _mixedKeys = existingHalf.Concat(newHalf).OrderBy(x => rnd.Next()).ToArray(); + + // Pre-build collections + _immDict = ImmutableDictionary.CreateRange(_allKeys.Select((k, i) => new KeyValuePair(k, i))); + _immSortedDict = ImmutableSortedDictionary.CreateRange(_allKeys.Select((k, i) => new KeyValuePair(k, i))); + + _extMap = LanguageExt.Map.empty(); + _extHashMap = LanguageExt.HashMap.empty(); + for (int i = 0; i < _allKeys.Length; i++) + { + _extMap = _extMap.AddOrUpdate(_allKeys[i], i); + _extHashMap = _extHashMap.AddOrUpdate(_allKeys[i], i); + } + + var transStd = BaseOrderedMap>.CreateTransient(_stdStrategy); + var transUni = BaseOrderedMap.CreateTransient(_uniStrategy); + for (int i = 0; i < _allKeys.Length; i++) + { + transStd.Set(_allKeys[i], i); + transUni.Set(_allKeys[i], i); + } + _persistentMapStandard = transStd.ToPersistent(); + _persistentMapUnicode = transUni.ToPersistent(); + } + + private static string GenerateRandomString(int length, Random rnd) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + return new string(Enumerable.Repeat(chars, length).Select(s => s[rnd.Next(s.Length)]).ToArray()); + } + + // --- 1. BUILD --- + + [Benchmark] + public PersistentMap> Build_TransientMap_Standard() + { + var map = BaseOrderedMap>.CreateTransient(_stdStrategy); + for (int i = 0; i < _allKeys.Length; i++) map.Set(_allKeys[i], i); + return map.ToPersistent(); + } + + [Benchmark] + public PersistentMap Build_TransientMap_Unicode() + { + var map = BaseOrderedMap.CreateTransient(_uniStrategy); + for (int i = 0; i < _allKeys.Length; i++) map.Set(_allKeys[i], i); + return map.ToPersistent(); + } + + [Benchmark] + public ImmutableDictionary Build_ImmDict() + { + var map = ImmutableDictionary.Empty; + for (int i = 0; i < _allKeys.Length; i++) map = map.Add(_allKeys[i], i); + return map; + } + + // --- 1. BUILD (Missing) --- + + [Benchmark] + public ImmutableSortedDictionary Build_ImmSortedDict() + { + var map = ImmutableSortedDictionary.Empty; + for (int i = 0; i < _allKeys.Length; i++) map = map.Add(_allKeys[i], i); + return map; + } + + [Benchmark] + public LanguageExt.Map Build_ExtMap() + { + var map = LanguageExt.Map.empty(); + for (int i = 0; i < _allKeys.Length; i++) map = map.AddOrUpdate(_allKeys[i], i); + return map; + } + + [Benchmark] + public LanguageExt.HashMap Build_ExtHashMap() + { + var map = LanguageExt.HashMap.empty(); + for (int i = 0; i < _allKeys.Length; i++) map = map.AddOrUpdate(_allKeys[i], i); + return map; + } + + // --- 2. RETRIEVAL --- + + [Benchmark] + public int Retrieve_ImmDict() + { + int count = 0; + foreach (var k in _retrieveKeys) + if (_immDict.TryGetValue(k, out _)) count++; + return count; + } + + [Benchmark] + public int Retrieve_PersistentMap_Standard() + { + int count = 0; + foreach (var k in _retrieveKeys) + if (_persistentMapStandard.TryGetValue(k, out _)) count++; + return count; + } + + [Benchmark] + public int Retrieve_PersistentMap_Unicode() + { + int count = 0; + foreach (var k in _retrieveKeys) + if (_persistentMapUnicode.TryGetValue(k, out _)) count++; + return count; + } +[Benchmark] + public int Retrieve_ImmSortedDict() + { + int count = 0; + foreach (var k in _retrieveKeys) + if (_immSortedDict.TryGetValue(k, out _)) count++; + return count; + } + + [Benchmark] + public int Retrieve_ExtMap() + { + int count = 0; + foreach (var k in _retrieveKeys) + if (_extMap.Find(k).IsSome) count++; + return count; + } + + [Benchmark] + public int Retrieve_ExtHashMap() + { + int count = 0; + foreach (var k in _retrieveKeys) + if (_extHashMap.Find(k).IsSome) count++; + return count; + } + + + // --- 3. UPDATING --- + + [Benchmark] + public ImmutableDictionary Update_ImmDict() + { + var map = _immDict; + foreach (var k in _updateKeys) map = map.SetItem(k, 999); + return map; + } + + [Benchmark] + public PersistentMap> Update_PersistentMap_Standard() + { + var map = _persistentMapStandard; + foreach (var k in _updateKeys) map = map.Set(k, 999); + return map; + } + [Benchmark] + public PersistentMap Update_PersistentMap_Unicode() + { + var map = _persistentMapUnicode; + foreach (var k in _updateKeys) map = map.Set(k, 999); + return map; + } + + [Benchmark] + public PersistentMap> Update_TransientMap_Standard() + { + var transient = _persistentMapStandard.ToTransient(); + foreach (var k in _updateKeys) transient.Set(k, 999); + return transient.ToPersistent(); + } + +[Benchmark] + public PersistentMap Update_TransientMap_Unicode() + { + var transient = _persistentMapUnicode.ToTransient(); + foreach (var k in _updateKeys) transient.Set(k, 999); + return transient.ToPersistent(); + } + + [Benchmark] + public ImmutableSortedDictionary Update_ImmSortedDict() + { + var map = _immSortedDict; + foreach (var k in _updateKeys) map = map.SetItem(k, 999); + return map; + } + + [Benchmark] + public LanguageExt.Map Update_ExtMap() + { + var map = _extMap; + foreach (var k in _updateKeys) map = map.SetItem(k, 999); + return map; + } + + [Benchmark] + public LanguageExt.HashMap Update_ExtHashMap() + { + var map = _extHashMap; + foreach (var k in _updateKeys) map = map.SetItem(k, 999); + return map; + } + + // --- 4. UPDATE & SET (MIXED) --- + + [Benchmark] + public ImmutableDictionary UpdateSet_ImmDict() + { + var map = _immDict; + foreach (var k in _mixedKeys) map = map.SetItem(k, 999); + return map; + } + + [Benchmark] + public PersistentMap> UpdateSet_PersistentMap_Standard() + { + var map = _persistentMapStandard; + foreach (var k in _mixedKeys) map = map.Set(k, 999); + return map; + } + +[Benchmark] + public PersistentMap UpdateSet_PersistentMap_Unicode() + { + var map = _persistentMapUnicode; + foreach (var k in _mixedKeys) map = map.Set(k, 999); + return map; + } + + [Benchmark] + public PersistentMap> UpdateSet_TransientMap_Standard() + { + var transient = _persistentMapStandard.ToTransient(); + foreach (var k in _mixedKeys) transient.Set(k, 999); + return transient.ToPersistent(); + } + +[Benchmark] + public PersistentMap UpdateSet_TransientMap_Unicode() + { + var transient = _persistentMapUnicode.ToTransient(); + foreach (var k in _mixedKeys) transient.Set(k, 999); + return transient.ToPersistent(); + } + + [Benchmark] + public ImmutableSortedDictionary UpdateSet_ImmSortedDict() + { + var map = _immSortedDict; + foreach (var k in _mixedKeys) map = map.SetItem(k, 999); + return map; + } + + [Benchmark] + public LanguageExt.Map UpdateSet_ExtMap() + { + var map = _extMap; + foreach (var k in _mixedKeys) map = map.AddOrUpdate(k, 999); + return map; + } + + [Benchmark] + public LanguageExt.HashMap UpdateSet_ExtHashMap() + { + var map = _extHashMap; + foreach (var k in _mixedKeys) map = map.AddOrUpdate(k, 999); + return map; + } + + // --- 5. ITERATION --- + + [Benchmark] + public int Iterate_ImmDict() + { + int sum = 0; + foreach (var kvp in _immDict) sum += kvp.Value; + return sum; + } + + [Benchmark] + public int Iterate_PersistentMap_Standard() + { + int sum = 0; + foreach (var kvp in _persistentMapStandard) sum += kvp.Value; + return sum; + } + + [Benchmark] + public int Iterate_ImmSortedDict() + { + int sum = 0; + foreach (var kvp in _immSortedDict) sum += kvp.Value; + return sum; + } + + [Benchmark] + public int Iterate_ExtMap() + { + int sum = 0; + foreach (var kvp in _extMap) sum += kvp.Value; + return sum; + } + + [Benchmark] + public int Iterate_ExtHashMap() + { + int sum = 0; + foreach (var kvp in _extHashMap) sum += kvp.Value; + return sum; + } + + [Benchmark] + public int Iterate_PersistentMap_Unicode() + { + int sum = 0; + foreach (var kvp in _persistentMapUnicode) sum += kvp.Value; + return sum; + } + + // --- 6. REMOVAL --- + + [Benchmark] + public ImmutableDictionary Remove_ImmDict() + { + var map = _immDict; + foreach (var k in _removeKeys) map = map.Remove(k); + return map; + } + + [Benchmark] + public PersistentMap> Remove_PersistentMap_Standard() + { + var map = _persistentMapStandard; + foreach (var k in _removeKeys) map = map.Remove(k); + return map; + } + + [Benchmark] + public PersistentMap Remove_PersistentMap_Unicode() + { + var map = _persistentMapUnicode; + foreach (var k in _removeKeys) map = map.Remove(k); + return map; + } + + [Benchmark] + public PersistentMap> Remove_TransientMap_Standard() + { + var transient = _persistentMapStandard.ToTransient(); + foreach (var k in _removeKeys) transient.Remove(k); + return transient.ToPersistent(); + } + +[Benchmark] + public PersistentMap Remove_TransientMap_Unicode() + { + var transient = _persistentMapUnicode.ToTransient(); + foreach (var k in _removeKeys) transient.Remove(k); + return transient.ToPersistent(); + } + + [Benchmark] + public ImmutableSortedDictionary Remove_ImmSortedDict() + { + var map = _immSortedDict; + foreach (var k in _removeKeys) map = map.Remove(k); + return map; + } + + [Benchmark] + public LanguageExt.Map Remove_ExtMap() + { + var map = _extMap; + foreach (var k in _removeKeys) map = map.Remove(k); + return map; + } + + [Benchmark] + public LanguageExt.HashMap Remove_ExtHashMap() + { + var map = _extHashMap; + foreach (var k in _removeKeys) map = map.Remove(k); + return map; + } +} From c7c5c7b81be3423ca61ea7b3a74946203f7ec16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Wed, 22 Apr 2026 19:30:46 +0200 Subject: [PATCH 09/22] added proper benchmarking against others changed StandardStrategy tonuse binary search. and ao on --- PersistentMap/KeyStrategies/IntScanner.cs | 90 +++++ .../KeyStrategies/StandardStrategy.cs | 2 +- TestProject1/FuzzTestStandardStrategy.cs | 170 ++++++++ .../AgainstImmutableDict/AgainstImmutable.cs | 96 ----- .../AgainstLanguageExt/AgainstLanguageExt.cs | 198 ---------- benchmarks/AgainstLanguageExt/Cycicmap.cs | 112 ------ .../AgainstLanguageExt/integerBenchmarks.cs | 220 ----------- benchmarks/MyBenchMarks/MyBenchMarks.csproj | 19 + benchmarks/MyBenchMarks/Program.cs | 368 ++++++++++++++++++ 9 files changed, 648 insertions(+), 627 deletions(-) create mode 100644 PersistentMap/KeyStrategies/IntScanner.cs create mode 100644 TestProject1/FuzzTestStandardStrategy.cs delete mode 100644 benchmarks/AgainstImmutableDict/AgainstImmutable.cs delete mode 100644 benchmarks/AgainstLanguageExt/AgainstLanguageExt.cs delete mode 100644 benchmarks/AgainstLanguageExt/Cycicmap.cs delete mode 100644 benchmarks/AgainstLanguageExt/integerBenchmarks.cs create mode 100644 benchmarks/MyBenchMarks/MyBenchMarks.csproj create mode 100644 benchmarks/MyBenchMarks/Program.cs diff --git a/PersistentMap/KeyStrategies/IntScanner.cs b/PersistentMap/KeyStrategies/IntScanner.cs new file mode 100644 index 0000000..de7b691 --- /dev/null +++ b/PersistentMap/KeyStrategies/IntScanner.cs @@ -0,0 +1,90 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace PersistentMap; + +public static class IntScanner +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FindFirstGreaterOrEqual(ReadOnlySpan keys, int target) + { + // Fallback for short arrays or unsupported hardware. + // AVX2 processes 8 integers at a time. + if (!Avx2.IsSupported || keys.Length < 8) + return LinearScan(keys, target); + + return Avx512F.IsSupported + ? ScanAvx512(keys, target) + : ScanAvx2(keys, target); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LinearScan(ReadOnlySpan keys, int target) + { + for (var i = 0; i < keys.Length; i++) + if (keys[i] >= target) + return i; + return keys.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe int ScanAvx2(ReadOnlySpan keys, int target) + { + // AVX2 lacks a native GreaterOrEqual for 32-bit integers. + // We use GreaterThan(Data, target - 1). + var vTarget = Vector256.Create(target - 1); + var i = 0; + var len = keys.Length; + + for (; i <= len - 8; i += 8) + { + fixed (int* ptr = keys) + { + var vData = Avx2.LoadVector256(ptr + i); + var vResult = Avx2.CompareGreaterThan(vData, vTarget); + + // MoveMask creates a 32-bit integer from the most significant bit of each byte. + var mask = (uint)Avx2.MoveMask(vResult.AsByte()); + + if (mask != 0) + { + // Since an int is 4 bytes, MoveMask sets 4 bits per matching element. + // Dividing the trailing zero count by 4 maps the byte offset back to the integer index. + return i + (BitOperations.TrailingZeroCount(mask) / 4); + } + } + } + + return LinearScan(keys.Slice(i), target) + i; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe int ScanAvx512(ReadOnlySpan keys, int target) + { + // AVX-512 processes 16 integers (512 bits) per instruction. + var vTarget = Vector512.Create(target); + var i = 0; + var len = keys.Length; + + for (; i <= len - 16; i += 16) + { + fixed (int* ptr = keys) + { + var vData = Avx512F.LoadVector512(ptr + i); + + // Vector512 API is used directly here to cleanly get the mask + var mask = Vector512.GreaterThanOrEqual(vData, vTarget); + + if (mask != Vector512.Zero) + { + uint m = (uint)mask.ExtractMostSignificantBits(); + return i + BitOperations.TrailingZeroCount(m); + } + } + } + + return LinearScan(keys.Slice(i), target) + i; + } +} diff --git a/PersistentMap/KeyStrategies/StandardStrategy.cs b/PersistentMap/KeyStrategies/StandardStrategy.cs index 23ebfdd..77f6701 100644 --- a/PersistentMap/KeyStrategies/StandardStrategy.cs +++ b/PersistentMap/KeyStrategies/StandardStrategy.cs @@ -23,7 +23,7 @@ public readonly struct StandardStrategy : IKeyStrategy } // Tell the B-Tree to skip SIMD routing and just use LinearSearch public bool UsesPrefixes => false; - + public bool UseBinarySearch => true; // This will never be called because UsesPrefixes is false, // but we must satisfy the interface. [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/TestProject1/FuzzTestStandardStrategy.cs b/TestProject1/FuzzTestStandardStrategy.cs new file mode 100644 index 0000000..69e598f --- /dev/null +++ b/TestProject1/FuzzTestStandardStrategy.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; +using Xunit.Abstractions; +using PersistentMap; + +public class BTreeFuzzTestStandardStrategy +{ + private readonly ITestOutputHelper _output; + private readonly StandardStrategy _strategy = new(); + + public BTreeFuzzTestStandardStrategy(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void Fuzz_Insert_And_Remove_consistency() + { + // CONFIGURATION + const int Iterations = 100_000; // High enough to trigger all splits/merges + const int KeyRange = 5000; // Small enough to cause frequent collisions + const bool showOps = false; + int Seed = 2135974; // Environment.TickCount; + + // ORACLES + var reference = new SortedDictionary(); + var subject = BaseOrderedMap>.CreateTransient(_strategy); + + var random = new Random(Seed); + _output.WriteLine($"Starting Fuzz Test with Seed: {Seed}"); + + try + { + for (int i = 0; i < Iterations; i++) + { + // 1. Pick an Action: 70% Insert/Update, 30% Remove + bool isInsert = random.NextDouble() < 0.7; + int key = random.Next(KeyRange); + int val = key * 100; + + if (isInsert) + { + // ACTION: INSERT + if (showOps)Console.WriteLine($"insert: {key} : {val}"); + if (key == 4436) + { + Console.WriteLine("BP"); + } + + reference[key] = val; + subject.Set(key, val); + } + else + { + // ACTION: REMOVE + if (reference.ContainsKey(key)) + { + if (showOps)Console.WriteLine($"remove ${key}"); + reference.Remove(key); + subject.Remove(key); + } + else + { + // Try removing non-existent key (should be safe) + subject.Remove(key); + } + } + + // 2. VERIFY CONSISTENCY (Expensive but necessary) + // We check consistency every 1000 ops or if the tree is small, + // to keep the test fast enough. + //if (i % 1000 == 0 || reference.Count < 100) + //{ + AssertConsistency(reference, subject); + //} + } + + // Final check + AssertConsistency(reference, subject); + } + catch (Exception) + { + _output.WriteLine($"FAILED at iteration with SEED: {Seed}"); + throw; // Re-throw to fail the test + } + } + + [Fact] + public void Fuzz_Range_Queries() + { + // Validates that your Range Enumerator matches LINQ on the reference + const int Iterations = 1000; + const int KeyRange = 2000; + int Seed = Environment.TickCount; + + var reference = new SortedDictionary(); + var subject = BaseOrderedMap>.CreateTransient(_strategy); + var random = new Random(Seed); + + // Fill Data + for(int i=0; i= min + + // 1. Reference Result (LINQ) + // Note: SortedDictionary doesn't have a direct Range query, so we filter memory. + var expected = reference + .Where(kv => kv.Key >= min && kv.Key <= max) + .Select(kv => kv.Key) + .ToList(); + + // 2. Subject Result + var actual = persistent.Range(min, max) + .Select(kv => kv.Key) + .ToList(); + + // 3. Compare + if (!expected.SequenceEqual(actual)) + { + _output.WriteLine($"Range Mismatch! Range: [{min}, {max}]"); + _output.WriteLine($"Expected: {string.Join(",", expected)}"); + _output.WriteLine($"Actual: {string.Join(",", actual)}"); + Assert.Fail("Range query results differ."); + } + } + } + + private void AssertConsistency(SortedDictionary expected, TransientMap> actual) + { + // 1. Count + if (expected.Count != actual.Count) + { + Console.WriteLine("BP"); + throw new Exception($"Count Mismatch! Expected {expected.Count}, Got {actual.Count}"); + } + + // 2. Full Scan Verification + using var enumerator = actual.GetEnumerator(); + foreach (var kvp in expected) + { + if (!enumerator.MoveNext()) + { + throw new Exception("Enumerator ended too early!"); + } + + if (enumerator.Current.Key != kvp.Key || enumerator.Current.Value != kvp.Value) + { + Console.WriteLine("BP"); + throw new Exception($"Content Mismatch! Expected [{kvp.Key}:{kvp.Value}], Got [{enumerator.Current.Key}:{enumerator.Current.Value}]"); + } + } + + if (enumerator.MoveNext()) + { + throw new Exception("Enumerator has extra items!"); + } + } +} diff --git a/benchmarks/AgainstImmutableDict/AgainstImmutable.cs b/benchmarks/AgainstImmutableDict/AgainstImmutable.cs deleted file mode 100644 index 269355f..0000000 --- a/benchmarks/AgainstImmutableDict/AgainstImmutable.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using PersistentMap; - -// Ensure your PersistentMap namespace is included here -// using MyProject.Collections; - - -[MemoryDiagnoser] -public class ImmutableBenchmark -{ - [Params(10, 100)] - public int N { get; set; } - - [Params(10000)] - public int CollectionSize { get; set; } - - private ImmutableDictionary _immutableDict; - private ImmutableSortedDictionary _immutableSortedDict; - - // 1. Add field for your map - private PersistentMap _persistentMap; - - private string[] _searchKeys; - - [GlobalSetup] - public void Setup() - { - var random = new Random(42); - var data = new Dictionary(); - - while (data.Count < CollectionSize) - { - string key = GenerateRandomString(random, N); - if (!data.ContainsKey(key)) - { - data[key] = "value"; - } - } - - _immutableDict = data.ToImmutableDictionary(); - _immutableSortedDict = data.ToImmutableSortedDictionary(); - - // 2. Initialize your map. - // ASSUMPTION: Standard immutable pattern (Add returns new instance). - // Adjust if you have a bulk loader like .ToPersistentMap() or a constructor. - _persistentMap = PersistentMap.Empty(new UnicodeStrategy()); - foreach (var kvp in data) - { - _persistentMap = _persistentMap.Set(kvp.Key, kvp.Value); - } - - _searchKeys = data.Keys.ToArray(); - } - - [Benchmark(Baseline = true)] - public string ImmutableDict_Lookup() - { - var key = _searchKeys[CollectionSize / 2]; - _immutableDict.TryGetValue(key, out var value); - return value; - } - - [Benchmark] - public string ImmutableSortedDict_Lookup() - { - var key = _searchKeys[CollectionSize / 2]; - _immutableSortedDict.TryGetValue(key, out var value); - return value; - } - - // 3. Add the benchmark case - [Benchmark] - public string PersistentMap_Lookup() - { - var key = _searchKeys[CollectionSize / 2]; - // Adjust API call if your map uses a different method (e.g. Find, Get, indexer) - _persistentMap.TryGetValue(key, out var value); - return value; - } - - private string GenerateRandomString(Random rng, int length) - { - const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - var buffer = new char[length]; - for (int i = 0; i < length; i++) - { - buffer[i] = chars[rng.Next(chars.Length)]; - } - return new string(buffer); - } -} \ No newline at end of file diff --git a/benchmarks/AgainstLanguageExt/AgainstLanguageExt.cs b/benchmarks/AgainstLanguageExt/AgainstLanguageExt.cs deleted file mode 100644 index 3a221ca..0000000 --- a/benchmarks/AgainstLanguageExt/AgainstLanguageExt.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System.Collections.Immutable; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using LanguageExt; -using PersistentMap; - -namespace AgainstLanguageExt; -// Mocking your library types for the sake of the example to ensure compilation. -// Replace these with your actual namespace imports. - - -[MemoryDiagnoser] -[HideColumns("Ratio", "RatioSD", "Alloc Ratio")] -public class MapBenchmarks -{ - [Params(10, 100, 1000)] - public int KeyLength { get; set; } // Key length - - [Params(1000, 100000, 1000000)] - public int CollectionSize { get; set; } - - // Data Source - private KeyValuePair[] _data; - private string[] _searchKeys; - - private int _index = 0; - private int _mask; - - // Comparison Targets (for Lookup) - private ImmutableDictionary _sysDict; - private ImmutableSortedDictionary _sysSorted; - private LanguageExt.HashMap _langExtHash; - private LanguageExt.Map _langExtSorted; // Map is Sorted in LangExt - private PersistentMap _persistentMap; - - [GlobalSetup] - public void Setup() - { - var random = new Random(42); - var dict = new Dictionary(); - - // 1. Generate Data - while (dict.Count < CollectionSize) - { - string key = GenerateRandomString(random, KeyLength); - if (!dict.ContainsKey(key)) - { - dict[key] = random.Next(); - } - } - _data = dict.ToArray(); - _searchKeys = dict.Keys.ToArray(); - - // 2. Pre-build maps for the Lookup benchmarks - _sysDict = dict.ToImmutableDictionary(); - _sysSorted = dict.ToImmutableSortedDictionary(); - - _langExtHash = Prelude.toHashMap(dict); - _langExtSorted = Prelude.toMap(dict); - - // Build PersistentMap for lookup test - var trans = PersistentMap.Empty(new UnicodeStrategy()).ToTransient(); - foreach (var kvp in _data) - { - trans.Set(kvp.Key, kvp.Value); - } - _persistentMap = trans.ToPersistent(); - } - - // ========================================== - // BUILD BENCHMARKS - // ========================================== - - [Benchmark(Description = "Lookup: PersistentMap (Cyclic)")] - public int Lookup_Persistent_Cyclic() - { - // Fast wrap-around - var key = _searchKeys[_index++ & _mask]; - _persistentMap.TryGetValue(key, out var value); - return value; - } - - [Benchmark(Description = "Lookup: Sys.Sorted (Cyclic)")] - public int Lookup_SysSorted_Cyclic() - { - var key = _searchKeys[_index++ & _mask]; - _sysSorted.TryGetValue(key, out var value); - return value; - } - - [Benchmark(Description = "Build: Sys.ImmutableDict")] - public ImmutableDictionary Build_SysImmutable() - { - // Using CreateRange/ToImmutable is usually the standard 'bulk' build - return _data.ToImmutableDictionary(); - } - - [Benchmark(Description = "Build: LangExt.HashMap")] - public LanguageExt.HashMap Build_LangExtHash() - { - return Prelude.toHashMap(_data); - } - - [Benchmark(Description = "Build: LangExt.SortedMap")] - public LanguageExt.Map Build_LangExtSorted() - { - return Prelude.toMap(_data); - } - - [Benchmark(Description = "Build: PersistentMap (Iterative)")] - public PersistentMap Build_Persistent_Iterative() - { - // Simulating naive immutable building (O(n log n) or worse due to copying) - var map = PersistentMap.Empty(new UnicodeStrategy()); - foreach (var item in _data) - { - map = map.Set(item.Key, item.Value); - } - return map; - } - - [Benchmark(Description = "Build: PersistentMap (Transient)")] - public PersistentMap Build_Persistent_Transient() - { - // Simulating efficient mutable build -> freeze - var trans = PersistentMap.Empty(new UnicodeStrategy()).ToTransient(); - foreach (var item in _data) - { - trans.Set(item.Key, item.Value); - } - return trans.ToPersistent(); - } - - // ========================================== - // LOOKUP BENCHMARKS - // ========================================== - - [Benchmark(Baseline = true, Description = "Lookup: Sys.ImmutableDict")] - public int Lookup_SysImmutable() - { - var key = _searchKeys[CollectionSize / 2]; - _sysDict.TryGetValue(key, out var value); - return value; - } - - [Benchmark(Description = "Lookup: Sys.SortedDict")] - public int Lookup_SysSorted() - { - var key = _searchKeys[CollectionSize / 2]; - _sysSorted.TryGetValue(key, out var value); - return value; - } - - [Benchmark(Description = "Lookup: LangExt.HashMap")] - public int Lookup_LangExtHash() - { - var key = _searchKeys[CollectionSize / 2]; - // LanguageExt often uses Find which returns Option, or [] operator - // Assuming TryGetValue-like behavior or using match for fairness - return _langExtHash.Find(key).IfNone(0); - } - - [Benchmark(Description = "Lookup: LangExt.SortedMap")] - public int Lookup_LangExtSorted() - { - var key = _searchKeys[CollectionSize / 2]; - return _langExtSorted.Find(key).IfNone(0); - } - - [Benchmark(Description = "Lookup: PersistentMap")] - public int Lookup_PersistentMap() - { - var key = _searchKeys[CollectionSize / 2]; - _persistentMap.TryGetValue(key, out var value); - return value; - } - - // Helper - private string GenerateRandomString(Random rng, int length) - { - const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - var buffer = new char[length]; - for (int i = 0; i < length; i++) - { - buffer[i] = chars[rng.Next(chars.Length)]; - } - return new string(buffer); - } -} - -public class Program -{ - public static void Main(string[] args) - { - // This scans the assembly and lets the command line (args) decide what to run - BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); - } -} \ No newline at end of file diff --git a/benchmarks/AgainstLanguageExt/Cycicmap.cs b/benchmarks/AgainstLanguageExt/Cycicmap.cs deleted file mode 100644 index 0cf32e4..0000000 --- a/benchmarks/AgainstLanguageExt/Cycicmap.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using LanguageExt; -using static LanguageExt.Prelude; -using PersistentMap; - - -[MemoryDiagnoser] -[HideColumns("Ratio", "RatioSD", "Alloc Ratio")] -public class CyclicMapBenchmarks -{ - // Powers of 2 for fast bitwise masking - [Params(1024, 131072)] - public int CollectionSize { get; set; } - - [Params(10, 100, 1000)] - public int N { get; set; } - - // Collections - private PersistentMap _persistentMap; - private ImmutableSortedDictionary _sysSorted; - private LanguageExt.HashMap _langExtHash; - private LanguageExt.Map _langExtSorted; - - // Lookup scaffolding - private string[] _searchKeys; - private int _index = 0; - private int _mask; - - [GlobalSetup] - public void Setup() - { - _mask = CollectionSize - 1; - var random = new Random(42); - var data = new Dictionary(); - - // 1. Generate Data - while (data.Count < CollectionSize) - { - string key = GenerateRandomString(random, N); - if (!data.ContainsKey(key)) - { - data[key] = random.Next(); - } - } - - // 2. Build Collections - // PersistentMap - var builder = PersistentMap.Empty(new UnicodeStrategy()).ToTransient(); - foreach (var kvp in data) builder.Set(kvp.Key, kvp.Value); - _persistentMap = builder.ToPersistent(); - - // System - _sysSorted = data.ToImmutableSortedDictionary(); - - // LanguageExt - _langExtHash = toHashMap(data); - _langExtSorted = toMap(data); - - // 3. Setup Cyclic Keys - _searchKeys = data.Keys.ToArray(); - // Shuffle to defeat branch prediction / cache pre-fetching - Random.Shared.Shuffle(_searchKeys); - } - - [Benchmark(Description = "Cyclic: PersistentMap")] - public int Lookup_Persistent() - { - var key = _searchKeys[_index++ & _mask]; - _persistentMap.TryGetValue(key, out var value); - return value; - } - - [Benchmark(Description = "Cyclic: Sys.Sorted")] - public int Lookup_SysSorted() - { - var key = _searchKeys[_index++ & _mask]; - _sysSorted.TryGetValue(key, out var value); - return value; - } - - [Benchmark(Description = "Cyclic: LangExt.HashMap")] - public int Lookup_LangExtHash() - { - var key = _searchKeys[_index++ & _mask]; - // Option struct return, overhead is minimal but present - return _langExtHash.Find(key).IfNone(0); - } - - [Benchmark(Description = "Cyclic: LangExt.Sorted")] - public int Lookup_LangExtSorted() - { - var key = _searchKeys[_index++ & _mask]; - // AVL Tree traversal - return _langExtSorted.Find(key).IfNone(0); - } - - private string GenerateRandomString(Random rng, int length) - { - const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - var buffer = new char[length]; - for (int i = 0; i < length; i++) - { - buffer[i] = chars[rng.Next(chars.Length)]; - } - return new string(buffer); - } -} \ No newline at end of file diff --git a/benchmarks/AgainstLanguageExt/integerBenchmarks.cs b/benchmarks/AgainstLanguageExt/integerBenchmarks.cs deleted file mode 100644 index 4b91b17..0000000 --- a/benchmarks/AgainstLanguageExt/integerBenchmarks.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Linq; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using LanguageExt; // Your Namespace -using LanguageExt; -using LanguageExt; -using PersistentMap; // NuGet: LanguageExt.Core - -[MemoryDiagnoser] -public class ImmutableCollectionBenchmarks -{ - [Params(100, 1000, 100_000)] - public int N; - - private int[] _keys; - private int[] _values; - - // --- 1. Your Collections --- - private PersistentMap _niceMap; - private IntStrategy _strategy; - - // --- 2. Microsoft Collections --- - private System.Collections.Immutable.ImmutableSortedDictionary _msSortedMap; - private System.Collections.Immutable.ImmutableDictionary _msHashMap; - - // --- 3. LanguageExt Collections --- - private LanguageExt.Map _leMap; // AVL Tree (Sorted) - private LanguageExt.HashMap _leHashMap; // Hash Array Mapped Trie (Unsorted) - - [GlobalSetup] - public void Setup() - { - // Generate Data - var rand = new Random(42); - _keys = Enumerable.Range(0, N).Select(x => x * 2).ToArray(); - rand.Shuffle(_keys); - _values = _keys.Select(k => k * 100).ToArray(); - _strategy = new IntStrategy(); - - // 1. Setup NiceBTree - var transient = BaseOrderedMap.CreateTransient(_strategy); - for (int i = 0; i < N; i++) transient.Set(_keys[i], _values[i]); - _niceMap = transient.ToPersistent(); - - // 2. Setup MS Immutable - var msBuilder = System.Collections.Immutable.ImmutableSortedDictionary.CreateBuilder(); - for (int i = 0; i < N; i++) msBuilder.Add(_keys[i], _values[i]); - _msSortedMap = msBuilder.ToImmutable(); - - _msHashMap = System.Collections.Immutable.ImmutableDictionary.CreateRange( - _keys.Zip(_values, (k, v) => new System.Collections.Generic.KeyValuePair(k, v))); - - // 3. Setup LanguageExt - // Note: LanguageExt performs best when bulk-loaded from tuples - var tuples = _keys.Zip(_values, (k, v) => (k, v)); - _leMap = new LanguageExt.Map(tuples); - _leHashMap = new HashMap(tuples); - } - - // ========================================================= - // 1. BUILD (Item by Item) - // Note: LanguageExt has no "Mutable Builder", so this tests - // the cost of pure immutable inserts vs your Transient/Builder. - // ========================================================= - - [Benchmark(Description = "Build: PersistentMap (Transient)")] - public int Build_NiceBTree() - { - var t = BaseOrderedMap.CreateTransient(_strategy); - for (int i = 0; i < N; i++) t.Set(_keys[i], _values[i]); - return t.Count; - } - -[Benchmark(Description = "Build: PersistentMap (Persistent)")] - public int Build_PersistentBTree() - { - var t = PersistentMap.Empty(_strategy); - for (int i = 0; i < N; i++) t.Set(_keys[i], _values[i]); - return t.Count; - } - - [Benchmark(Description = "Build: MS Sorted (Builder)")] - public int Build_MsSorted() - { - var b = System.Collections.Immutable.ImmutableSortedDictionary.CreateBuilder(); - for (int i = 0; i < N; i++) b.Add(_keys[i], _values[i]); - return b.Count; - } - - [Benchmark(Description = "Build: LanguageExt Map (AVL)")] - public int Build_LanguageExt_Map() - { - var map = LanguageExt.Map.Empty; - for (int i = 0; i < N; i++) - { - // Pure immutable add - map = map.Add(_keys[i], _values[i]); - } - return map.Count; - } - - [Benchmark(Description = "Build: LanguageExt HashMap")] - public int Build_LanguageExt_HashMap() - { - var map = LanguageExt.HashMap.Empty; - for (int i = 0; i < N; i++) - { - map = map.Add(_keys[i], _values[i]); - } - return map.Count; - } - - // ========================================================= - // 2. READ (Lookup) - // ========================================================= - - [Benchmark(Description = "Read: NiceBTree")] - public int Read_NiceBTree() - { - var found = 1; - if (_niceMap.TryGetValue(_keys[N/2], out _)) found++; - - return found; - } - - [Benchmark(Description = "Read: MS Sorted")] - public int Read_MsSorted() - { - int found = 0; - if (_msSortedMap.ContainsKey(_keys[N/2])) found++; - - return found; - } - - [Benchmark(Description = "Read: LanguageExt Map")] - public int Read_LanguageExt_Map() - { - int found = 0; - // Find returns Option, IsSome checks if it exists - if (_leMap.Find(_keys[N/2]).IsSome) found++; - - return found; - } - - [Benchmark(Description = "Read: LanguageExt HashMap")] - public int Read_LanguageExt_HashMap() - { - int found = 0; - - if (_leHashMap.Find(_keys[N/2]).IsSome) found++; - - return found; - } - - // ========================================================= - // 3. ITERATE (Foreach) - // ========================================================= - - [Benchmark(Description = "Iterate: NiceBTree")] - public int Iterate_NiceBTree() - { - int sum = 0; - foreach (var kvp in _niceMap) sum += kvp.Key; - return sum; - } - - [Benchmark(Description = "Iterate: MS Sorted")] - public int Iterate_MsSorted() - { - int sum = 0; - foreach (var kvp in _msSortedMap) sum += kvp.Key; - return sum; - } - - [Benchmark(Description = "Iterate: LanguageExt Map")] - public int Iterate_LanguageExt_Map() - { - int sum = 0; - // LanguageExt Map is IEnumerable<(Key, Value)> - foreach (var item in _leMap) sum += item.Key; - return sum; - } - - [Benchmark(Description = "Iterate: LanguageExt HashMap")] - public int Iterate_LanguageExt_HashMap() - { - int sum = 0; - foreach (var item in _leHashMap) sum += item.Key; - return sum; - } - - // ========================================================= - // 4. SET (Persistent / Immutable Update) - // ========================================================= - - [Benchmark(Description = "Set: NiceBTree")] - public PersistentMap Set_NiceBTree() - { - return _niceMap.Set(_keys[N / 2], -1); - } - - [Benchmark(Description = "Set: MS Sorted")] - public System.Collections.Immutable.ImmutableSortedDictionary Set_MsSorted() - { - return _msSortedMap.SetItem(_keys[N / 2], -1); - } - - [Benchmark(Description = "Set: LanguageExt Map")] - public LanguageExt.Map Set_LanguageExt_Map() - { - return _leMap.SetItem(_keys[N / 2], -1); - } - - [Benchmark(Description = "Set: LanguageExt HashMap")] - public LanguageExt.HashMap Set_LanguageExt_HashMap() - { - return _leHashMap.SetItem(_keys[N / 2], -1); - } -} diff --git a/benchmarks/MyBenchMarks/MyBenchMarks.csproj b/benchmarks/MyBenchMarks/MyBenchMarks.csproj new file mode 100644 index 0000000..a55ce4e --- /dev/null +++ b/benchmarks/MyBenchMarks/MyBenchMarks.csproj @@ -0,0 +1,19 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + + + + diff --git a/benchmarks/MyBenchMarks/Program.cs b/benchmarks/MyBenchMarks/Program.cs new file mode 100644 index 0000000..dcad462 --- /dev/null +++ b/benchmarks/MyBenchMarks/Program.cs @@ -0,0 +1,368 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using LanguageExt; +using PersistentMap; + +namespace MapBenchmarks; + +[MemoryDiagnoser] +public class IntMapBenchmarks +{ + [Params(100, 1000, 10000, 100000)] + public int N { get; set; } + + private int[] _allKeys; + private int[] _retrieveKeys; + private int[] _updateKeys; + private int[] _removeKeys; + private int[] _mixedKeys; // Half existing, half new + + // Pre-built collections for read/update/remove tests + private ImmutableDictionary _immDict; + private ImmutableSortedDictionary _immSortedDict; + private LanguageExt.Map _extMap; + private LanguageExt.HashMap _extHashMap; + private PersistentMap _persistentMap; + + private readonly IntStrategy _intStrategy = new IntStrategy(); + + [GlobalSetup] + public void Setup() + { + var rnd = new Random(42); + + // Build integer keys (inserted sorted) + _allKeys = Enumerable.Range(0, N).ToArray(); + + int subsetSize = Math.Max(1, N / 10); + + // Subsets for different operations + var shuffled = _allKeys.OrderBy(x => rnd.Next()).ToArray(); + _retrieveKeys = shuffled.Take(subsetSize).ToArray(); + _updateKeys = shuffled.Skip(subsetSize).Take(subsetSize).ToArray(); + _removeKeys = shuffled.Skip(subsetSize * 2).Take(subsetSize).ToArray(); + + // Mixed keys: half existing, half completely new (N + 1 to N + subsetSize/2) + var existingHalf = shuffled.Skip(subsetSize * 3).Take(subsetSize / 2).ToArray(); + var newHalf = Enumerable.Range(N + 1, subsetSize - (subsetSize / 2)).ToArray(); + _mixedKeys = existingHalf.Concat(newHalf).OrderBy(x => rnd.Next()).ToArray(); + + // Pre-build collections + _immDict = ImmutableDictionary.CreateRange(_allKeys.Select(k => new KeyValuePair(k, k))); + _immSortedDict = ImmutableSortedDictionary.CreateRange(_allKeys.Select(k => new KeyValuePair(k, k))); + + _extMap = LanguageExt.Map.empty(); + _extHashMap = LanguageExt.HashMap.empty(); + foreach (var k in _allKeys) + { + _extMap = _extMap.AddOrUpdate(k, k); + _extHashMap = _extHashMap.AddOrUpdate(k, k); + } + + var transient = BaseOrderedMap.CreateTransient(_intStrategy); + foreach (var k in _allKeys) transient.Set(k, k); + _persistentMap = transient.ToPersistent(); + } + + // --- 1. BUILD --- + + [Benchmark] + public ImmutableDictionary Build_ImmDict() + { + var map = ImmutableDictionary.Empty; + foreach (var k in _allKeys) map = map.Add(k, k); + return map; + } + + [Benchmark] + public ImmutableSortedDictionary Build_ImmSortedDict() + { + var map = ImmutableSortedDictionary.Empty; + foreach (var k in _allKeys) map = map.Add(k, k); + return map; + } + + [Benchmark] + public LanguageExt.Map Build_ExtMap() + { + var map = LanguageExt.Map.empty(); + foreach (var k in _allKeys) map = map.AddOrUpdate(k, k); + return map; + } + + [Benchmark] + public LanguageExt.HashMap Build_ExtHashMap() + { + var map = LanguageExt.HashMap.empty(); + foreach (var k in _allKeys) map = map.AddOrUpdate(k, k); + return map; + } + + [Benchmark] + public PersistentMap Build_PersistentMap() + { + var map = PersistentMap.Empty(_intStrategy); + foreach (var k in _allKeys) map = map.Set(k, k); + return map; + } + + [Benchmark] + public PersistentMap Build_TransientMap() + { + var map = BaseOrderedMap.CreateTransient(_intStrategy); + foreach (var k in _allKeys) map.Set(k, k); + return map.ToPersistent(); + } + + // --- 2. RETRIEVAL --- + + [Benchmark] + public int Retrieve_ImmDict() + { + int count = 0; + foreach (var k in _retrieveKeys) + if (_immDict.TryGetValue(k, out _)) count++; + return count; + } + + [Benchmark] + public int Retrieve_ImmSortedDict() + { + int count = 0; + foreach (var k in _retrieveKeys) + if (_immSortedDict.TryGetValue(k, out _)) count++; + return count; + } + + [Benchmark] + public int Retrieve_ExtMap() + { + int count = 0; + foreach (var k in _retrieveKeys) + if (_extMap.Find(k).IsSome) count++; + return count; + } + + [Benchmark] + public int Retrieve_ExtHashMap() + { + int count = 0; + foreach (var k in _retrieveKeys) + if (_extHashMap.Find(k).IsSome) count++; + return count; + } + + [Benchmark] + public int Retrieve_PersistentMap() + { + int count = 0; + foreach (var k in _retrieveKeys) + if (_persistentMap.TryGetValue(k, out _)) count++; + return count; + } + + // --- 3. UPDATING --- + + [Benchmark] + public ImmutableDictionary Update_ImmDict() + { + var map = _immDict; + foreach (var k in _updateKeys) map = map.SetItem(k, 999); + return map; + } + +[Benchmark] + public PersistentMap Update_PersistentMap() + { + var map = _persistentMap; + foreach (var k in _updateKeys) map = map.Set(k, 999); + return map; + } + + [Benchmark] + public PersistentMap Update_TransientMap() + { + var transient = _persistentMap.ToTransient(); + foreach (var k in _updateKeys) transient.Set(k, 999); + return transient.ToPersistent(); + } + + [Benchmark] + public ImmutableSortedDictionary Update_ImmSortedDict() + { + var map = _immSortedDict; + foreach (var k in _updateKeys) map = map.SetItem(k, 999); + return map; + } + + [Benchmark] + public LanguageExt.Map Update_ExtMap() + { + var map = _extMap; + foreach (var k in _updateKeys) map = map.SetItem(k, 999); + return map; + } + + [Benchmark] + public LanguageExt.HashMap Update_ExtHashMap() + { + var map = _extHashMap; + foreach (var k in _updateKeys) map = map.SetItem(k, 999); + return map; + } + + // --- 4. UPDATE & SET (MIXED) --- + + [Benchmark] + public ImmutableDictionary UpdateSet_ImmDict() + { + var map = _immDict; + foreach (var k in _mixedKeys) map = map.SetItem(k, 999); + return map; + } + +[Benchmark] + public PersistentMap UpdateSet_PersistentMap() + { + var map = _persistentMap; + foreach (var k in _mixedKeys) map = map.Set(k, 999); + return map; + } + + [Benchmark] + public PersistentMap UpdateSet_TransientMap() + { + var transient = _persistentMap.ToTransient(); + foreach (var k in _mixedKeys) transient.Set(k, 999); + return transient.ToPersistent(); + } + + [Benchmark] + public ImmutableSortedDictionary UpdateSet_ImmSortedDict() + { + var map = _immSortedDict; + foreach (var k in _mixedKeys) map = map.SetItem(k, 999); + return map; + } + + [Benchmark] + public LanguageExt.Map UpdateSet_ExtMap() + { + var map = _extMap; + foreach (var k in _mixedKeys) map = map.AddOrUpdate(k, 999); + return map; + } + + [Benchmark] + public LanguageExt.HashMap UpdateSet_ExtHashMap() + { + var map = _extHashMap; + foreach (var k in _mixedKeys) map = map.AddOrUpdate(k, 999); + return map; + } + + // --- 5. ITERATION --- + + [Benchmark] + public int Iterate_ImmDict() + { + int sum = 0; + foreach (var kvp in _immDict) sum += kvp.Value; + return sum; + } + + [Benchmark] + public int Iterate_PersistentMap() + { + int sum = 0; + foreach (var kvp in _persistentMap) sum += kvp.Value; + return sum; + } + + [Benchmark] + public int Iterate_ImmSortedDict() + { + int sum = 0; + foreach (var kvp in _immSortedDict) sum += kvp.Value; + return sum; + } + + [Benchmark] + public int Iterate_ExtMap() + { + int sum = 0; + foreach (var kvp in _extMap) sum += kvp.Value; + return sum; + } + + [Benchmark] + public int Iterate_ExtHashMap() + { + int sum = 0; + foreach (var kvp in _extHashMap) sum += kvp.Value; + return sum; + } + + + // --- 6. REMOVAL --- + + [Benchmark] + public ImmutableDictionary Remove_ImmDict() + { + var map = _immDict; + foreach (var k in _removeKeys) map = map.Remove(k); + return map; + } + +[Benchmark] + public PersistentMap Remove_PersistentMap() + { + var map = _persistentMap; + foreach (var k in _removeKeys) map = map.Remove(k); + return map; + } + [Benchmark] + public PersistentMap Remove_TransientMap() + { + var transient = _persistentMap.ToTransient(); + foreach (var k in _removeKeys) transient.Remove(k); + return transient.ToPersistent(); + } + + [Benchmark] + public ImmutableSortedDictionary Remove_ImmSortedDict() + { + var map = _immSortedDict; + foreach (var k in _removeKeys) map = map.Remove(k); + return map; + } + + [Benchmark] + public LanguageExt.Map Remove_ExtMap() + { + var map = _extMap; + foreach (var k in _removeKeys) map = map.Remove(k); + return map; + } + + [Benchmark] + public LanguageExt.HashMap Remove_ExtHashMap() + { + var map = _extHashMap; + foreach (var k in _removeKeys) map = map.Remove(k); + return map; + } + + + + public static void Main(string[] args) + { + BenchmarkSwitcher + .FromAssembly(typeof(IntMapBenchmarks).Assembly) + .Run(args); + } +} From e0e7ca07727071795754557a6424c3f80372a451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Thu, 23 Apr 2026 16:21:10 +0200 Subject: [PATCH 10/22] Removed old benchmark code --- ...LanguageExt.MapBenchmarks-report-github.md | 111 ------------------ PersistentMap/Benchmarks/gh.benchmarks.md | 78 ------------ PersistentMap/Benchmarks/intkeys.md | 65 ---------- 3 files changed, 254 deletions(-) delete mode 100644 PersistentMap/Benchmarks/AgainstLanguageExt.MapBenchmarks-report-github.md delete mode 100644 PersistentMap/Benchmarks/gh.benchmarks.md delete mode 100644 PersistentMap/Benchmarks/intkeys.md diff --git a/PersistentMap/Benchmarks/AgainstLanguageExt.MapBenchmarks-report-github.md b/PersistentMap/Benchmarks/AgainstLanguageExt.MapBenchmarks-report-github.md deleted file mode 100644 index a34f42e..0000000 --- a/PersistentMap/Benchmarks/AgainstLanguageExt.MapBenchmarks-report-github.md +++ /dev/null @@ -1,111 +0,0 @@ -``` - -BenchmarkDotNet v0.15.8, Linux openSUSE Tumbleweed-Slowroll -AMD Ryzen 9 7900 3.02GHz, 1 CPU, 24 logical and 12 physical cores -.NET SDK 10.0.100 - [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4 - DefaultJob : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4 - -Alloc Ratio=NA - -``` -| Method | KeyLength | CollectionSize | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|----------------------------------- |---------- |--------------- |--------------------:|------------------:|------------------:|------------:|------------:|----------:|-------------:| -| **'Build: Sys.ImmutableDict'** | **10** | **1000** | **80,197.68 ns** | **628.656 ns** | **557.288 ns** | **3.7842** | **0.6104** | **-** | **64072 B** | -| 'Build: LangExt.HashMap' | 10 | 1000 | 101,031.57 ns | 842.539 ns | 703.558 ns | 16.8457 | 2.3193 | - | 282816 B | -| 'Build: LangExt.SortedMap' | 10 | 1000 | 231,169.60 ns | 4,303.018 ns | 3,814.513 ns | 3.1738 | 0.4883 | - | 56088 B | -| 'Build: PersistentMap (Iterative)' | 10 | 1000 | 247,805.45 ns | 4,848.561 ns | 7,106.959 ns | 138.1836 | 15.1367 | - | 2314008 B | -| 'Build: PersistentMap (Transient)' | 10 | 1000 | 85,655.04 ns | 826.536 ns | 690.195 ns | 3.5400 | 0.3662 | - | 59624 B | -| 'Lookup: Sys.ImmutableDict' | 10 | 1000 | 11.26 ns | 0.040 ns | 0.035 ns | - | - | - | - | -| 'Lookup: Sys.SortedDict' | 10 | 1000 | 132.93 ns | 1.110 ns | 0.984 ns | - | - | - | - | -| 'Lookup: LangExt.HashMap' | 10 | 1000 | 24.35 ns | 0.220 ns | 0.206 ns | - | - | - | - | -| 'Lookup: LangExt.SortedMap' | 10 | 1000 | 191.15 ns | 0.908 ns | 0.758 ns | - | - | - | - | -| 'Lookup: PersistentMap' | 10 | 1000 | 25.57 ns | 0.185 ns | 0.173 ns | - | - | - | - | -| | | | | | | | | | | -| **'Build: Sys.ImmutableDict'** | **10** | **100000** | **26,453,597.57 ns** | **369,042.136 ns** | **345,202.243 ns** | **375.0000** | **281.2500** | **-** | **6400128 B** | -| 'Build: LangExt.HashMap' | 10 | 100000 | 30,466,067.88 ns | 607,708.598 ns | 568,451.000 ns | 2125.0000 | 1125.0000 | 125.0000 | 33872183 B | -| 'Build: LangExt.SortedMap' | 10 | 100000 | 57,320,977.00 ns | 294,859.779 ns | 246,221.270 ns | 333.3333 | 222.2222 | - | 5600088 B | -| 'Build: PersistentMap (Iterative)' | 10 | 100000 | 61,269,640.88 ns | 944,908.467 ns | 883,867.966 ns | 22375.0000 | 15375.0000 | 125.0000 | 373286500 B | -| 'Build: PersistentMap (Transient)' | 10 | 100000 | 17,053,104.34 ns | 75,075.340 ns | 66,552.333 ns | 343.7500 | 218.7500 | - | 5764192 B | -| 'Lookup: Sys.ImmutableDict' | 10 | 100000 | 14.23 ns | 0.048 ns | 0.043 ns | - | - | - | - | -| 'Lookup: Sys.SortedDict' | 10 | 100000 | 317.13 ns | 1.974 ns | 1.750 ns | - | - | - | - | -| 'Lookup: LangExt.HashMap' | 10 | 100000 | 30.39 ns | 0.115 ns | 0.102 ns | - | - | - | - | -| 'Lookup: LangExt.SortedMap' | 10 | 100000 | 367.82 ns | 1.219 ns | 1.080 ns | - | - | - | - | -| 'Lookup: PersistentMap' | 10 | 100000 | 44.58 ns | 0.191 ns | 0.178 ns | - | - | - | - | -| | | | | | | | | | | -| **'Build: Sys.ImmutableDict'** | **10** | **1000000** | **565,648,044.07 ns** | **7,804,426.380 ns** | **7,300,265.281 ns** | **3000.0000** | **2000.0000** | **-** | **64006400 B** | -| 'Build: LangExt.HashMap' | 10 | 1000000 | 705,337,758.36 ns | 8,815,356.221 ns | 7,814,583.679 ns | 22000.0000 | 20000.0000 | - | 371442296 B | -| 'Build: LangExt.SortedMap' | 10 | 1000000 | 1,090,945,766.47 ns | 15,976,157.606 ns | 14,944,107.744 ns | 3000.0000 | 2000.0000 | - | 56000088 B | -| 'Build: PersistentMap (Iterative)' | 10 | 1000000 | 1,869,359,967.60 ns | 13,028,258.852 ns | 12,186,641.419 ns | 248000.0000 | 134000.0000 | 2000.0000 | 4116022328 B | -| 'Build: PersistentMap (Transient)' | 10 | 1000000 | 327,151,058.39 ns | 6,192,298.711 ns | 6,625,690.250 ns | 3000.0000 | 2500.0000 | - | 57911880 B | -| 'Lookup: Sys.ImmutableDict' | 10 | 1000000 | 16.76 ns | 0.329 ns | 0.323 ns | - | - | - | - | -| 'Lookup: Sys.SortedDict' | 10 | 1000000 | 229.91 ns | 0.557 ns | 0.521 ns | - | - | - | - | -| 'Lookup: LangExt.HashMap' | 10 | 1000000 | 30.30 ns | 0.095 ns | 0.089 ns | - | - | - | - | -| 'Lookup: LangExt.SortedMap' | 10 | 1000000 | 439.20 ns | 3.921 ns | 3.668 ns | - | - | - | - | -| 'Lookup: PersistentMap' | 10 | 1000000 | 57.93 ns | 0.262 ns | 0.219 ns | - | - | - | - | -| | | | | | | | | | | -| **'Build: Sys.ImmutableDict'** | **100** | **1000** | **152,297.84 ns** | **1,453.902 ns** | **1,359.981 ns** | **3.6621** | **0.4883** | **-** | **64072 B** | -| 'Build: LangExt.HashMap' | 100 | 1000 | 162,130.87 ns | 1,572.067 ns | 1,470.513 ns | 17.0898 | 2.1973 | - | 286248 B | -| 'Build: LangExt.SortedMap' | 100 | 1000 | 227,901.82 ns | 3,599.541 ns | 3,190.900 ns | 3.1738 | 0.4883 | - | 56088 B | -| 'Build: PersistentMap (Iterative)' | 100 | 1000 | 246,870.77 ns | 4,663.995 ns | 4,362.704 ns | 138.4277 | 15.1367 | - | 2316904 B | -| 'Build: PersistentMap (Transient)' | 100 | 1000 | 87,058.89 ns | 995.531 ns | 831.314 ns | 3.6621 | 0.3662 | - | 62520 B | -| 'Lookup: Sys.ImmutableDict' | 100 | 1000 | 51.85 ns | 0.344 ns | 0.287 ns | - | - | - | - | -| 'Lookup: Sys.SortedDict' | 100 | 1000 | 126.84 ns | 0.807 ns | 0.755 ns | - | - | - | - | -| 'Lookup: LangExt.HashMap' | 100 | 1000 | 59.05 ns | 0.193 ns | 0.161 ns | - | - | - | - | -| 'Lookup: LangExt.SortedMap' | 100 | 1000 | 172.41 ns | 1.498 ns | 1.401 ns | - | - | - | - | -| 'Lookup: PersistentMap' | 100 | 1000 | 26.71 ns | 0.094 ns | 0.088 ns | - | - | - | - | -| | | | | | | | | | | -| **'Build: Sys.ImmutableDict'** | **100** | **100000** | **33,894,492.92 ns** | **510,741.665 ns** | **477,748.070 ns** | **333.3333** | **266.6667** | **-** | **6400184 B** | -| 'Build: LangExt.HashMap' | 100 | 100000 | 42,503,012.72 ns | 844,933.727 ns | 1,854,650.153 ns | 2066.6667 | 1066.6667 | 66.6667 | 33877456 B | -| 'Build: LangExt.SortedMap' | 100 | 100000 | 58,627,288.83 ns | 659,710.519 ns | 550,888.162 ns | 333.3333 | 222.2222 | - | 5600088 B | -| 'Build: PersistentMap (Iterative)' | 100 | 100000 | 69,999,451.30 ns | 1,366,779.941 ns | 1,824,612.118 ns | 22625.0000 | 15625.0000 | 125.0000 | 377408145 B | -| 'Build: PersistentMap (Transient)' | 100 | 100000 | 19,221,436.72 ns | 376,157.061 ns | 628,473.997 ns | 343.7500 | 281.2500 | - | 5791360 B | -| 'Lookup: Sys.ImmutableDict' | 100 | 100000 | 55.38 ns | 0.349 ns | 0.309 ns | - | - | - | - | -| 'Lookup: Sys.SortedDict' | 100 | 100000 | 298.94 ns | 3.088 ns | 2.888 ns | - | - | - | - | -| 'Lookup: LangExt.HashMap' | 100 | 100000 | 81.90 ns | 0.693 ns | 0.649 ns | - | - | - | - | -| 'Lookup: LangExt.SortedMap' | 100 | 100000 | 401.93 ns | 2.570 ns | 2.278 ns | - | - | - | - | -| 'Lookup: PersistentMap' | 100 | 100000 | 49.00 ns | 0.523 ns | 0.490 ns | - | - | - | - | -| | | | | | | | | | | -| **'Build: Sys.ImmutableDict'** | **100** | **1000000** | **679,242,633.13 ns** | **13,320,688.340 ns** | **12,460,180.143 ns** | **3000.0000** | **2000.0000** | **-** | **64006792 B** | -| 'Build: LangExt.HashMap' | 100 | 1000000 | 905,145,222.68 ns | 16,013,156.606 ns | 19,665,596.105 ns | 22000.0000 | 20000.0000 | - | 371420160 B | -| 'Build: LangExt.SortedMap' | 100 | 1000000 | 1,311,013,675.64 ns | 21,946,824.587 ns | 19,455,288.355 ns | 3000.0000 | 2000.0000 | - | 56000088 B | -| 'Build: PersistentMap (Iterative)' | 100 | 1000000 | 1,781,838,551.67 ns | 21,016,398.450 ns | 19,658,752.159 ns | 249000.0000 | 134000.0000 | 1000.0000 | 4157329544 B | -| 'Build: PersistentMap (Transient)' | 100 | 1000000 | 378,070,334.14 ns | 7,546,360.889 ns | 19,747,512.605 ns | 3000.0000 | 2000.0000 | - | 57879232 B | -| 'Lookup: Sys.ImmutableDict' | 100 | 1000000 | 56.94 ns | 0.391 ns | 0.366 ns | - | - | - | - | -| 'Lookup: Sys.SortedDict' | 100 | 1000000 | 299.78 ns | 4.283 ns | 4.006 ns | - | - | - | - | -| 'Lookup: LangExt.HashMap' | 100 | 1000000 | 81.32 ns | 0.714 ns | 0.633 ns | - | - | - | - | -| 'Lookup: LangExt.SortedMap' | 100 | 1000000 | 371.55 ns | 1.288 ns | 1.142 ns | - | - | - | - | -| 'Lookup: PersistentMap' | 100 | 1000000 | 57.35 ns | 0.203 ns | 0.180 ns | - | - | - | - | -| | | | | | | | | | | -| **'Build: Sys.ImmutableDict'** | **1000** | **1000** | **862,166.32 ns** | **6,173.897 ns** | **5,775.067 ns** | **2.9297** | **-** | **-** | **64072 B** | -| 'Build: LangExt.HashMap' | 1000 | 1000 | 715,350.87 ns | 5,832.760 ns | 5,455.967 ns | 16.6016 | 1.9531 | - | 288824 B | -| 'Build: LangExt.SortedMap' | 1000 | 1000 | 231,953.07 ns | 4,633.161 ns | 4,957.431 ns | 3.1738 | 0.4883 | - | 56088 B | -| 'Build: PersistentMap (Iterative)' | 1000 | 1000 | 244,027.59 ns | 4,381.202 ns | 3,883.821 ns | 138.1836 | 14.6484 | - | 2312560 B | -| 'Build: PersistentMap (Transient)' | 1000 | 1000 | 85,141.37 ns | 997.399 ns | 832.873 ns | 3.4180 | 0.2441 | - | 58176 B | -| 'Lookup: Sys.ImmutableDict' | 1000 | 1000 | 463.86 ns | 2.281 ns | 1.905 ns | - | - | - | - | -| 'Lookup: Sys.SortedDict' | 1000 | 1000 | 141.16 ns | 1.373 ns | 1.285 ns | - | - | - | - | -| 'Lookup: LangExt.HashMap' | 1000 | 1000 | 482.30 ns | 2.993 ns | 2.499 ns | - | - | - | - | -| 'Lookup: LangExt.SortedMap' | 1000 | 1000 | 151.26 ns | 0.522 ns | 0.463 ns | - | - | - | - | -| 'Lookup: PersistentMap' | 1000 | 1000 | 25.14 ns | 0.100 ns | 0.089 ns | - | - | - | - | -| | | | | | | | | | | -| **'Build: Sys.ImmutableDict'** | **1000** | **100000** | **110,681,036.36 ns** | **2,111,588.232 ns** | **2,073,861.990 ns** | **285.7143** | **142.8571** | **-** | **6400072 B** | -| 'Build: LangExt.HashMap' | 1000 | 100000 | 97,557,350.10 ns | 551,991.120 ns | 516,332.837 ns | 2000.0000 | 1000.0000 | - | 33861848 B | -| 'Build: LangExt.SortedMap' | 1000 | 100000 | 64,549,795.52 ns | 1,182,362.259 ns | 1,105,982.391 ns | 250.0000 | 125.0000 | - | 5600088 B | -| 'Build: PersistentMap (Iterative)' | 1000 | 100000 | 67,619,915.03 ns | 537,449.664 ns | 502,730.749 ns | 22500.0000 | 15500.0000 | - | 377887656 B | -| 'Build: PersistentMap (Transient)' | 1000 | 100000 | 18,935,946.67 ns | 148,299.648 ns | 138,719.583 ns | 343.7500 | 250.0000 | - | 5797496 B | -| 'Lookup: Sys.ImmutableDict' | 1000 | 100000 | 468.15 ns | 4.581 ns | 4.285 ns | - | - | - | - | -| 'Lookup: Sys.SortedDict' | 1000 | 100000 | 314.21 ns | 0.812 ns | 0.720 ns | - | - | - | - | -| 'Lookup: LangExt.HashMap' | 1000 | 100000 | 485.12 ns | 3.011 ns | 2.817 ns | - | - | - | - | -| 'Lookup: LangExt.SortedMap' | 1000 | 100000 | 312.35 ns | 1.905 ns | 1.782 ns | - | - | - | - | -| 'Lookup: PersistentMap' | 1000 | 100000 | 45.20 ns | 0.272 ns | 0.254 ns | - | - | - | - | -| | | | | | | | | | | -| **'Build: Sys.ImmutableDict'** | **1000** | **1000000** | **1,243,324,259.58 ns** | **23,435,444.565 ns** | **26,048,434.545 ns** | **3000.0000** | **2000.0000** | **-** | **64006232 B** | -| 'Build: LangExt.HashMap' | 1000 | 1000000 | 1,622,230,008.71 ns | 12,545,963.874 ns | 11,121,670.194 ns | 22000.0000 | 20000.0000 | - | 371481912 B | -| 'Build: LangExt.SortedMap' | 1000 | 1000000 | 1,405,640,022.27 ns | 20,672,528.624 ns | 19,337,096.110 ns | 3000.0000 | 2000.0000 | - | 56000088 B | -| 'Build: PersistentMap (Iterative)' | 1000 | 1000000 | 1,576,250,726.60 ns | 21,132,287.760 ns | 19,767,155.091 ns | 250000.0000 | 134000.0000 | - | 4196398744 B | -| 'Build: PersistentMap (Transient)' | 1000 | 1000000 | 399,073,585.07 ns | 7,329,442.854 ns | 6,855,965.396 ns | 3000.0000 | 2000.0000 | - | 57944840 B | -| 'Lookup: Sys.ImmutableDict' | 1000 | 1000000 | 469.34 ns | 2.373 ns | 2.104 ns | - | - | - | - | -| 'Lookup: Sys.SortedDict' | 1000 | 1000000 | 328.66 ns | 1.561 ns | 1.384 ns | - | - | - | - | -| 'Lookup: LangExt.HashMap' | 1000 | 1000000 | 484.32 ns | 3.425 ns | 3.204 ns | - | - | - | - | -| 'Lookup: LangExt.SortedMap' | 1000 | 1000000 | 400.19 ns | 2.649 ns | 2.349 ns | - | - | - | - | -| 'Lookup: PersistentMap' | 1000 | 1000000 | 56.25 ns | 0.147 ns | 0.130 ns | - | - | - | - | diff --git a/PersistentMap/Benchmarks/gh.benchmarks.md b/PersistentMap/Benchmarks/gh.benchmarks.md deleted file mode 100644 index aaafdfe..0000000 --- a/PersistentMap/Benchmarks/gh.benchmarks.md +++ /dev/null @@ -1,78 +0,0 @@ -``` - -BenchmarkDotNet v0.15.8, Linux openSUSE Tumbleweed-Slowroll -AMD Ryzen 9 7900 3.02GHz, 1 CPU, 24 logical and 12 physical cores -.NET SDK 10.0.100 - [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4 - DefaultJob : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4 - - -``` -| Method | Keysize | CollectionSize | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | -|----------------------------------- |----------|--------------- |--------------:|-------------:|-------------:|--------------:|--------:|---------:|--------:|----------:|------------:| -| **'Build: Sys.ImmutableDict'** | **10** | **1000** | **82,965.51 ns** | **1,541.386 ns** | **1,582.889 ns** | **6,986.62** | **146.96** | **3.7842** | **0.6104** | **64072 B** | **NA** | -| 'Build: LangExt.HashMap' | 10 | 1000 | 107,355.82 ns | 1,551.663 ns | 1,451.427 ns | 9,040.55 | 148.52 | 17.2119 | 2.4414 | 289808 B | NA | -| 'Build: LangExt.SortedMap' | 10 | 1000 | 228,660.43 ns | 2,218.079 ns | 1,852.196 ns | 19,255.74 | 243.15 | 3.1738 | 0.4883 | 56088 B | NA | -| 'Build: PersistentMap (Iterative)' | 10 | 1000 | 244,752.37 ns | 2,794.335 ns | 2,333.396 ns | 20,610.86 | 278.75 | 138.1836 | 15.6250 | 2314008 B | NA | -| 'Build: PersistentMap (Transient)' | 10 | 1000 | 86,370.48 ns | 1,150.505 ns | 1,076.183 ns | 7,273.35 | 113.63 | 3.5400 | 0.3662 | 59624 B | NA | -| 'Lookup: Sys.ImmutableDict' | 10 | 1000 | 11.88 ns | 0.130 ns | 0.122 ns | 1.00 | 0.01 | - | - | - | NA | -| 'Lookup: Sys.SortedDict' | 10 | 1000 | 129.81 ns | 1.686 ns | 1.494 ns | 10.93 | 0.16 | - | - | - | NA | -| 'Lookup: LangExt.HashMap' | 10 | 1000 | 24.52 ns | 0.293 ns | 0.274 ns | 2.07 | 0.03 | - | - | - | NA | -| 'Lookup: LangExt.SortedMap' | 10 | 1000 | 202.85 ns | 0.770 ns | 0.682 ns | 17.08 | 0.18 | - | - | - | NA | -| 'Lookup: PersistentMap' | 10 | 1000 | 25.24 ns | 0.159 ns | 0.148 ns | 2.13 | 0.02 | - | - | - | NA | -| | | | | | | | | | | | | -| **'Build: Sys.ImmutableDict'** | **100** | **1000** | **151,217.34 ns** | **1,687.298 ns** | **1,578.300 ns** | **2,930.42** | **31.69** | **3.6621** | **0.4883** | **64072 B** | **NA** | -| 'Build: LangExt.HashMap' | 100 | 1000 | 167,490.07 ns | 2,525.502 ns | 2,362.356 ns | 3,245.77 | 46.06 | 17.3340 | 2.1973 | 293096 B | NA | -| 'Build: LangExt.SortedMap' | 100 | 1000 | 228,731.62 ns | 4,424.010 ns | 4,733.641 ns | 4,432.57 | 90.96 | 3.1738 | 0.4883 | 56088 B | NA | -| 'Build: PersistentMap (Iterative)' | 100 | 1000 | 256,683.18 ns | 2,564.193 ns | 2,141.218 ns | 4,974.24 | 44.31 | 138.1836 | 15.1367 | 2316904 B | NA | -| 'Build: PersistentMap (Transient)' | 100 | 1000 | 87,260.94 ns | 1,667.741 ns | 1,712.648 ns | 1,691.02 | 32.92 | 3.6621 | 0.3662 | 62520 B | NA | -| 'Lookup: Sys.ImmutableDict' | 100 | 1000 | 51.60 ns | 0.263 ns | 0.205 ns | 1.00 | 0.01 | - | - | - | NA | -| 'Lookup: Sys.SortedDict' | 100 | 1000 | 126.22 ns | 0.747 ns | 0.662 ns | 2.45 | 0.02 | - | - | - | NA | -| 'Lookup: LangExt.HashMap' | 100 | 1000 | 65.88 ns | 0.353 ns | 0.295 ns | 1.28 | 0.01 | - | - | - | NA | -| 'Lookup: LangExt.SortedMap' | 100 | 1000 | 168.63 ns | 0.768 ns | 0.718 ns | 3.27 | 0.02 | - | - | - | NA | -| 'Lookup: PersistentMap' | 100 | 1000 | 26.67 ns | 0.149 ns | 0.132 ns | 0.52 | 0.00 | - | - | - | NA | -| | | | | | | | | | | | | -| **'Build: Sys.ImmutableDict'** | **1000** | **1000** | **858,405.87 ns** | **3,122.202 ns** | **2,437.610 ns** | **1,837.79** | **11.46** | **2.9297** | **-** | **64072 B** | **NA** | -| 'Build: LangExt.HashMap' | 1000 | 1000 | 712,616.81 ns | 3,284.251 ns | 2,742.498 ns | 1,525.66 | 10.25 | 16.6016 | 1.9531 | 293600 B | NA | -| 'Build: LangExt.SortedMap' | 1000 | 1000 | 227,785.60 ns | 2,218.172 ns | 1,966.352 ns | 487.67 | 4.90 | 3.1738 | 0.4883 | 56088 B | NA | -| 'Build: PersistentMap (Iterative)' | 1000 | 1000 | 249,242.56 ns | 4,789.243 ns | 7,313.681 ns | 533.61 | 15.71 | 138.1836 | 14.6484 | 2312560 B | NA | -| 'Build: PersistentMap (Transient)' | 1000 | 1000 | 85,297.82 ns | 1,186.402 ns | 990.700 ns | 182.62 | 2.29 | 3.4180 | 0.2441 | 58176 B | NA | -| 'Lookup: Sys.ImmutableDict' | 1000 | 1000 | 467.10 ns | 3.062 ns | 2.714 ns | 1.00 | 0.01 | - | - | - | NA | -| 'Lookup: Sys.SortedDict' | 1000 | 1000 | 131.87 ns | 1.521 ns | 1.348 ns | 0.28 | 0.00 | - | - | - | NA | -| 'Lookup: LangExt.HashMap' | 1000 | 1000 | 478.53 ns | 2.786 ns | 2.606 ns | 1.02 | 0.01 | - | - | - | NA | -| 'Lookup: LangExt.SortedMap' | 1000 | 1000 | 151.49 ns | 1.183 ns | 1.049 ns | 0.32 | 0.00 | - | - | - | NA | -| 'Lookup: PersistentMap' | 1000 | 1000 | 24.98 ns | 0.209 ns | 0.195 ns | 0.05 | 0.00 | - -Here are some better lookup benchmarks that do not just try to lookup the same index: - -| Method | CollectionSize | N | Mean | Error | StdDev | Allocated | -|-------------------------- |--------------- |----- |----------:|----------:|----------:|----------:| -| 'Cyclic: PersistentMap' | 1024 | 10 | 26.68 ns | 0.246 ns | 0.230 ns | - | -| 'Cyclic: Sys.Sorted' | 1024 | 10 | 153.12 ns | 1.591 ns | 1.410 ns | - | -| 'Cyclic: LangExt.HashMap' | 1024 | 10 | 24.80 ns | 0.140 ns | 0.131 ns | - | -| 'Cyclic: LangExt.Sorted' | 1024 | 10 | 180.45 ns | 1.695 ns | 1.415 ns | - | -| | | | | | | | -| 'Cyclic: PersistentMap' | 1024 | 100 | 27.09 ns | 0.142 ns | 0.126 ns | - | -| 'Cyclic: Sys.Sorted' | 1024 | 100 | 154.13 ns | 1.729 ns | 1.444 ns | - | -| 'Cyclic: LangExt.HashMap' | 1024 | 100 | 66.44 ns | 0.501 ns | 0.468 ns | - | -| 'Cyclic: LangExt.Sorted' | 1024 | 100 | 180.61 ns | 3.244 ns | 3.034 ns | - | -| | | | | | | | -| 'Cyclic: PersistentMap' | 1024 | 1000 | 26.84 ns | 0.131 ns | 0.110 ns | - | -| 'Cyclic: Sys.Sorted' | 1024 | 1000 | 171.48 ns | 1.828 ns | 1.710 ns | - | -| 'Cyclic: LangExt.HashMap' | 1024 | 1000 | 497.32 ns | 9.698 ns | 9.071 ns | - | -| 'Cyclic: LangExt.Sorted' | 1024 | 1000 | 180.88 ns | 2.297 ns | 1.918 ns | - | -| | | | | | | | -| 'Cyclic: PersistentMap' | 131072 | 10 | 103.80 ns | 1.740 ns | 1.628 ns | - | -| 'Cyclic: Sys.Sorted' | 131072 | 10 | 459.04 ns | 4.579 ns | 4.283 ns | - | -| 'Cyclic: LangExt.HashMap' | 131072 | 10 | 56.64 ns | 0.654 ns | 0.612 ns | - | -| 'Cyclic: LangExt.Sorted' | 131072 | 10 | 525.38 ns | 10.281 ns | 11.840 ns | - | -| | | | | | | | -| 'Cyclic: PersistentMap' | 131072 | 100 | 118.92 ns | 2.222 ns | 2.967 ns | - | -| 'Cyclic: Sys.Sorted' | 131072 | 100 | 552.77 ns | 10.983 ns | 12.648 ns | - | -| 'Cyclic: LangExt.HashMap' | 131072 | 100 | 169.08 ns | 1.478 ns | 1.234 ns | - | -| 'Cyclic: LangExt.Sorted' | 131072 | 100 | 588.64 ns | 11.473 ns | 11.782 ns | - | -| | | | | | | | -| 'Cyclic: PersistentMap' | 131072 | 1000 | 151.38 ns | 1.432 ns | 1.269 ns | - | -| 'Cyclic: Sys.Sorted' | 131072 | 1000 | 606.19 ns | 9.281 ns | 8.228 ns | - | -| 'Cyclic: LangExt.HashMap' | 131072 | 1000 | 732.79 ns | 6.556 ns | 5.812 ns | - | -| 'Cyclic: LangExt.Sorted' | 131072 | 1000 | 653.56 ns | 9.363 ns | 8.300 ns | - | diff --git a/PersistentMap/Benchmarks/intkeys.md b/PersistentMap/Benchmarks/intkeys.md deleted file mode 100644 index e99f19b..0000000 --- a/PersistentMap/Benchmarks/intkeys.md +++ /dev/null @@ -1,65 +0,0 @@ -This uses integer keys, and thus there are no hashing penalties. This is more or less raw overhead of the data structure. - - -``` -BenchmarkDotNet v0.15.8, Linux openSUSE Tumbleweed-Slowroll -AMD Ryzen 9 7900 3.02GHz, 1 CPU, 24 logical and 12 physical cores -.NET SDK 10.0.100 -[Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4 -ShortRun : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v4 - -Job=ShortRun IterationCount=3 LaunchCount=1 -WarmupCount=3 -``` - -| Method | N | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|------------------------------- |------- |-----------------:|-----------------:|---------------:|----------:|----------:|--------:|-----------:| -| 'Build: NiceBTree (Transient)' | 100 | 3,056.96 ns | 404.797 ns | 22.188 ns | 0.2861 | - | - | 4800 B | -| 'Build: MS Sorted (Builder)' | 100 | 3,099.28 ns | 551.017 ns | 30.203 ns | 0.2899 | 0.0038 | - | 4864 B | -| 'Build: LanguageExt Map (AVL)' | 100 | 6,376.44 ns | 2,154.744 ns | 118.109 ns | 2.2736 | 0.0229 | - | 38144 B | -| 'Build: LanguageExt HashMap' | 100 | 4,577.22 ns | 1,143.256 ns | 62.666 ns | 1.9684 | 0.0076 | - | 33024 B | -| 'Read: NiceBTree' | 100 | 1,281.41 ns | 326.069 ns | 17.873 ns | - | - | - | - | -| 'Read: MS Sorted' | 100 | 484.55 ns | 176.528 ns | 9.676 ns | - | - | - | - | -| 'Read: LanguageExt Map' | 100 | 1,284.80 ns | 770.679 ns | 42.244 ns | - | - | - | - | -| 'Read: LanguageExt HashMap' | 100 | 641.44 ns | 33.663 ns | 1.845 ns | - | - | - | - | -| 'Iterate: NiceBTree' | 100 | 136.77 ns | 14.772 ns | 0.810 ns | - | - | - | - | -| 'Iterate: MS Sorted' | 100 | 425.40 ns | 78.190 ns | 4.286 ns | - | - | - | - | -| 'Iterate: LanguageExt Map' | 100 | 291.45 ns | 113.262 ns | 6.208 ns | 0.0019 | - | - | 32 B | -| 'Iterate: LanguageExt HashMap' | 100 | 763.45 ns | 72.014 ns | 3.947 ns | 0.0648 | - | - | 1088 B | -| 'Set: NiceBTree' | 100 | 60.85 ns | 11.258 ns | 0.617 ns | 0.0678 | 0.0002 | - | 1136 B | -| 'Set: MS Sorted' | 100 | 73.15 ns | 23.712 ns | 1.300 ns | 0.0229 | - | - | 384 B | -| 'Set: LanguageExt Map' | 100 | 58.56 ns | 1.750 ns | 0.096 ns | 0.0219 | - | - | 368 B | -| 'Set: LanguageExt HashMap' | 100 | 36.32 ns | 14.171 ns | 0.777 ns | 0.0206 | - | - | 344 B | -| 'Build: NiceBTree (Transient)' | 1000 | 42,256.55 ns | 4,394.007 ns | 240.850 ns | 2.3804 | 0.1221 | - | 40176 B | -| 'Build: MS Sorted (Builder)' | 1000 | 49,147.19 ns | 3,136.972 ns | 171.948 ns | 2.8687 | 0.4272 | - | 48064 B | -| 'Build: LanguageExt Map (AVL)' | 1000 | 103,207.86 ns | 38,017.513 ns | 2,083.868 ns | 34.6680 | 3.1738 | - | 580688 B | -| 'Build: LanguageExt HashMap' | 1000 | 118,382.76 ns | 8,091.969 ns | 443.548 ns | 45.4102 | 3.2959 | - | 760096 B | -| 'Read: NiceBTree' | 1000 | 13,839.35 ns | 680.579 ns | 37.305 ns | - | - | - | - | -| 'Read: MS Sorted' | 1000 | 8,663.56 ns | 1,007.067 ns | 55.201 ns | - | - | - | - | -| 'Read: LanguageExt Map' | 1000 | 22,507.35 ns | 2,405.937 ns | 131.878 ns | - | - | - | - | -| 'Read: LanguageExt HashMap' | 1000 | 9,727.15 ns | 1,226.266 ns | 67.216 ns | - | - | - | - | -| 'Iterate: NiceBTree' | 1000 | 1,216.36 ns | 264.964 ns | 14.524 ns | - | - | - | - | -| 'Iterate: MS Sorted' | 1000 | 3,870.96 ns | 280.519 ns | 15.376 ns | - | - | - | - | -| 'Iterate: LanguageExt Map' | 1000 | 2,571.58 ns | 422.239 ns | 23.144 ns | - | - | - | 32 B | -| 'Iterate: LanguageExt HashMap' | 1000 | 12,252.69 ns | 2,654.186 ns | 145.485 ns | 1.9226 | - | - | 32320 B | -| 'Set: NiceBTree' | 1000 | 122.89 ns | 31.121 ns | 1.706 ns | 0.0677 | 0.0002 | - | 1136 B | -| 'Set: MS Sorted' | 1000 | 94.78 ns | 68.248 ns | 3.741 ns | 0.0315 | - | - | 528 B | -| 'Set: LanguageExt Map' | 1000 | 81.47 ns | 39.825 ns | 2.183 ns | 0.0305 | - | - | 512 B | -| 'Set: LanguageExt HashMap' | 1000 | 58.48 ns | 22.323 ns | 1.224 ns | 0.0368 | 0.0001 | - | 616 B | -| 'Build: NiceBTree (Transient)' | 100000 | 8,648,357.47 ns | 528,464.456 ns | 28,966.920 ns | 234.3750 | 125.0000 | - | 3952352 B | -| 'Build: MS Sorted (Builder)' | 100000 | 16,518,142.94 ns | 754,623.461 ns | 41,363.458 ns | 281.2500 | 250.0000 | - | 4800064 B | -| 'Build: LanguageExt Map (AVL)' | 100000 | 41,025,632.97 ns | 4,549,124.512 ns | 249,352.866 ns | 5333.3333 | 3333.3333 | - | 89959040 B | -| 'Build: LanguageExt HashMap' | 100000 | 21,273,736.10 ns | 1,167,411.035 ns | 63,989.738 ns | 5781.2500 | 2937.5000 | 31.2500 | 96555424 B | -| 'Read: NiceBTree' | 100000 | 5,469,322.19 ns | 351,350.121 ns | 19,258.686 ns | - | - | - | - | -| 'Read: MS Sorted' | 100000 | 8,066,906.24 ns | 709,413.540 ns | 38,885.350 ns | - | - | - | - | -| 'Read: LanguageExt Map' | 100000 | 10,104,086.04 ns | 1,534,486.048 ns | 84,110.359 ns | - | - | - | - | -| 'Read: LanguageExt HashMap' | 100000 | 1,932,000.56 ns | 247,929.366 ns | 13,589.845 ns | - | - | - | - | -| 'Iterate: NiceBTree' | 100000 | 144,689.84 ns | 57,439.701 ns | 3,148.464 ns | - | - | - | - | -| 'Iterate: MS Sorted' | 100000 | 1,087,465.02 ns | 240,535.824 ns | 13,184.580 ns | - | - | - | - | -| 'Iterate: LanguageExt Map' | 100000 | 774,299.86 ns | 55,922.321 ns | 3,065.291 ns | - | - | - | 32 B | -| 'Iterate: LanguageExt HashMap' | 100000 | 1,230,437.83 ns | 47,039.229 ns | 2,578.379 ns | 64.4531 | - | - | 1082432 B | -| 'Set: NiceBTree' | 100000 | 201.88 ns | 431.379 ns | 23.645 ns | 0.1223 | 0.0007 | - | 2048 B | -| 'Set: MS Sorted' | 100000 | 144.35 ns | 75.135 ns | 4.118 ns | 0.0458 | - | - | 768 B | -| 'Set: LanguageExt Map' | 100000 | 121.02 ns | 31.124 ns | 1.706 ns | 0.0448 | - | - | 752 B | -| 'Set: LanguageExt HashMap' | 100000 | 84.70 ns | 56.667 ns | 3.106 ns | 0.0583 | - | - | 976 B | - From 7af48306fe82eb29202098a507f06eb678388b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Thu, 23 Apr 2026 16:45:18 +0200 Subject: [PATCH 11/22] Optimize the binary search by using unsafe The jit was not able to tell that bounds checks could be elided. Added an extra whistle for value types to allow for inlined comparisons. --- PersistentMap/BTreeFunctions.cs | 16 +- .../KeyStrategies/ComparableStrategy.cs | 17 + PersistentMap/Readme.org | 665 +++++++++--------- 3 files changed, 368 insertions(+), 330 deletions(-) create mode 100644 PersistentMap/KeyStrategies/ComparableStrategy.cs diff --git a/PersistentMap/BTreeFunctions.cs b/PersistentMap/BTreeFunctions.cs index a039e75..51e4b08 100644 --- a/PersistentMap/BTreeFunctions.cs +++ b/PersistentMap/BTreeFunctions.cs @@ -274,39 +274,47 @@ namespace PersistentMap return i; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int BinarySearchKeys(ReadOnlySpan keys, K key, TStrategy strategy) where TStrategy : IKeyStrategy { int low = 0; int high = keys.Length - 1; + ref K keysRef = ref MemoryMarshal.GetReference(keys); + while (low <= high) { int mid = low + ((high - low) >> 1); - int cmp = strategy.Compare(keys[mid], key); + K midKey = Unsafe.Add(ref keysRef, mid); + int cmp = strategy.Compare(midKey, key); + if (cmp == 0) return mid; if (cmp < 0) low = mid + 1; else high = mid - 1; } return low; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int BinaryRoutingKeys(ReadOnlySpan keys, K key, TStrategy strategy) where TStrategy : IKeyStrategy { int low = 0; int high = keys.Length - 1; + ref K keysRef = ref MemoryMarshal.GetReference(keys); + while (low <= high) { int mid = low + ((high - low) >> 1); - int cmp = strategy.Compare(keys[mid], key); + K midKey = Unsafe.Add(ref keysRef, mid); + int cmp = strategy.Compare(midKey, key); + if (cmp <= 0) low = mid + 1; else high = mid - 1; } return low; } - + // --------------------------------------------------------- // Insertion Logic // --------------------------------------------------------- diff --git a/PersistentMap/KeyStrategies/ComparableStrategy.cs b/PersistentMap/KeyStrategies/ComparableStrategy.cs new file mode 100644 index 0000000..d0212f6 --- /dev/null +++ b/PersistentMap/KeyStrategies/ComparableStrategy.cs @@ -0,0 +1,17 @@ +namespace PersistentMap; + +using System.Runtime.CompilerServices; + +// This is a comparable strategy that may squeeze some extra time out of value types + +public readonly struct ComparableStrategy : IKeyStrategy where K : IComparable +{ + public bool UsesPrefixes => false; + public bool UseBinarySearch => true; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long GetPrefix(K key) => 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Compare(K x, K y) => x.CompareTo(y); +} diff --git a/PersistentMap/Readme.org b/PersistentMap/Readme.org index af2ae28..90acd48 100644 --- a/PersistentMap/Readme.org +++ b/PersistentMap/Readme.org @@ -268,336 +268,349 @@ ImmDict is System.Collections.Immutable dictionary. ImmSortedDict is it's sorted * String keys -These benchmarks act like above, but do not insert keys in a specific order. Sorting them before will yield a speed boost. One uses the standardkeystrategy (does linear string comparisons) and one uses the unicodstrategy which encodes the first 8 bytes as a long and uses avx to search for keys. +These benchmarks act like above, but do not insert keys in a specific order. Sorting them before will yield a speed boost. One uses the standardkeystrategy (does a binary search) and one uses the unicodstrategy which encodes the first 8 bytes as a long and uses avx to search for keys. #+begin_src -``` -BenchmarkDotNet v0.15.8, Linux Fedora Linux 43 (Container Image) -AMD Ryzen 9 5900X 3.69GHz, 1 CPU, 24 logical and 12 physical cores -.NET SDK 10.0.104 - [Host] : .NET 10.0.4 (10.0.4, 10.0.426.12010), X64 RyuJIT x86-64-v3 - ShortRun : .NET 10.0.4 (10.0.4, 10.0.426.12010), X64 RyuJIT x86-64-v3 - -Job=ShortRun IterationCount=3 LaunchCount=1 -WarmupCount=3 - -``` -| Method | N | StringLength | Mean | Gen0 | Gen1 | Gen2 | Allocated | -|--------------------------------- |------- |------------- |------------------:|-----------:|----------:|---------:|------------:| -| Build_TransientMap_Standard | 100 | 8 | 42,942.85 ns | 0.7324 | - | - | 6216 B | -| Build_TransientMap_Unicode | 100 | 8 | 12,289.81 ns | 0.8850 | 0.0153 | - | 7528 B | -| Build_ImmDict | 100 | 8 | 14,166.14 ns | 5.4016 | 0.0610 | - | 45280 B | -| Build_ImmSortedDict | 100 | 8 | 22,037.60 ns | 4.2114 | 0.0305 | - | 35472 B | -| Build_ExtMap | 100 | 8 | 22,837.07 ns | 5.6152 | 0.0610 | - | 47104 B | -| Build_ExtHashMap | 100 | 8 | 8,907.91 ns | 3.9673 | 0.0305 | - | 33240 B | -| Retrieve_ImmDict | 100 | 8 | 80.81 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 100 | 8 | 4,956.86 ns | 0.0534 | - | - | 480 B | -| Retrieve_PersistentMap_Unicode | 100 | 8 | 148.61 ns | - | - | - | - | -| Update_ImmDict | 100 | 8 | 1,089.03 ns | 0.4215 | - | - | 3536 B | -| Update_PersistentMap_Standard | 100 | 8 | 8,182.15 ns | 2.3956 | 0.0305 | - | 20160 B | -| Update_PersistentMap_Unicode | 100 | 8 | 1,954.07 ns | 2.7046 | 0.0496 | - | 22640 B | -| Update_TransientMap_Standard | 100 | 8 | 7,007.11 ns | 0.4349 | - | - | 3640 B | -| Update_TransientMap_Unicode | 100 | 8 | 667.55 ns | 0.4644 | 0.0057 | - | 3888 B | -| Update_ImmSortedDict | 100 | 8 | 1,741.49 ns | 0.3662 | - | - | 3072 B | -| Update_ExtMap | 100 | 8 | 1,812.71 ns | 0.4120 | - | - | 3456 B | -| Update_ExtHashMap | 100 | 8 | 767.69 ns | 0.5360 | 0.0010 | - | 4488 B | -| UpdateSet_ImmDict | 100 | 8 | 1,508.45 ns | 0.5589 | 0.0019 | - | 4688 B | -| UpdateSet_PersistentMap_Standard | 100 | 8 | 6,877.98 ns | 2.4033 | 0.0381 | - | 20160 B | -| UpdateSet_PersistentMap_Unicode | 100 | 8 | 2,660.42 ns | 2.7046 | 0.0534 | - | 22640 B | -| UpdateSet_TransientMap_Standard | 100 | 8 | 5,415.27 ns | 0.4349 | - | - | 3640 B | -| UpdateSet_TransientMap_Unicode | 100 | 8 | 1,212.47 ns | 0.4635 | 0.0057 | - | 3888 B | -| UpdateSet_ImmSortedDict | 100 | 8 | 2,424.14 ns | 0.4578 | - | - | 3840 B | -| UpdateSet_ExtMap | 100 | 8 | 2,111.13 ns | 0.4730 | - | - | 3960 B | -| UpdateSet_ExtHashMap | 100 | 8 | 876.06 ns | 0.5646 | 0.0019 | - | 4728 B | -| Iterate_ImmDict | 100 | 8 | 1,272.01 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 100 | 8 | 191.98 ns | - | - | - | - | -| Iterate_ImmSortedDict | 100 | 8 | 475.35 ns | - | - | - | - | -| Iterate_ExtMap | 100 | 8 | 322.36 ns | 0.0038 | - | - | 32 B | -| Iterate_ExtHashMap | 100 | 8 | 1,179.25 ns | 0.2747 | - | - | 2304 B | -| Iterate_PersistentMap_Unicode | 100 | 8 | 191.41 ns | - | - | - | - | -| Remove_ImmDict | 100 | 8 | 1,420.61 ns | 0.5360 | 0.0019 | - | 4496 B | -| Remove_PersistentMap_Standard | 100 | 8 | 8,294.72 ns | 2.4261 | 0.0305 | - | 20400 B | -| Remove_PersistentMap_Unicode | 100 | 8 | 2,597.23 ns | 2.6779 | 0.0496 | - | 22400 B | -| Remove_TransientMap_Standard | 100 | 8 | 7,178.94 ns | 0.4654 | - | - | 3928 B | -| Remove_TransientMap_Unicode | 100 | 8 | 1,446.12 ns | 0.4406 | 0.0057 | - | 3688 B | -| Remove_ImmSortedDict | 100 | 8 | 2,031.56 ns | 0.3777 | - | - | 3168 B | -| Remove_ExtMap | 100 | 8 | 2,274.33 ns | 0.5798 | - | - | 4856 B | -| Remove_ExtHashMap | 100 | 8 | 817.02 ns | 0.5102 | 0.0019 | - | 4272 B | -| Build_TransientMap_Standard | 100 | 50 | 43,518.39 ns | 0.7324 | - | - | 6216 B | -| Build_TransientMap_Unicode | 100 | 50 | 12,061.37 ns | 0.8850 | 0.0153 | - | 7528 B | -| Build_ImmDict | 100 | 50 | 16,497.93 ns | 5.4016 | 0.0610 | - | 45216 B | -| Build_ImmSortedDict | 100 | 50 | 22,820.20 ns | 4.2114 | 0.0305 | - | 35232 B | -| Build_ExtMap | 100 | 50 | 22,925.57 ns | 5.6458 | 0.0610 | - | 47328 B | -| Build_ExtHashMap | 100 | 50 | 13,019.93 ns | 4.3945 | 0.0305 | - | 36776 B | -| Retrieve_ImmDict | 100 | 50 | 286.77 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 100 | 50 | 4,620.16 ns | 0.0534 | - | - | 480 B | -| Retrieve_PersistentMap_Unicode | 100 | 50 | 158.67 ns | - | - | - | - | -| Update_ImmDict | 100 | 50 | 1,490.11 ns | 0.4902 | 0.0019 | - | 4112 B | -| Update_PersistentMap_Standard | 100 | 50 | 7,007.05 ns | 2.4033 | 0.0381 | - | 20160 B | -| Update_PersistentMap_Unicode | 100 | 50 | 1,976.44 ns | 2.7046 | 0.0534 | - | 22640 B | -| Update_TransientMap_Standard | 100 | 50 | 6,208.54 ns | 0.4349 | - | - | 3640 B | -| Update_TransientMap_Unicode | 100 | 50 | 665.76 ns | 0.4644 | 0.0057 | - | 3888 B | -| Update_ImmSortedDict | 100 | 50 | 2,133.72 ns | 0.4120 | - | - | 3456 B | -| Update_ExtMap | 100 | 50 | 1,842.17 ns | 0.4253 | - | - | 3568 B | -| Update_ExtHashMap | 100 | 50 | 971.20 ns | 0.5474 | 0.0019 | - | 4592 B | -| UpdateSet_ImmDict | 100 | 50 | 1,816.66 ns | 0.6046 | 0.0019 | - | 5072 B | -| UpdateSet_PersistentMap_Standard | 100 | 50 | 6,616.38 ns | 2.4033 | 0.0381 | - | 20160 B | -| UpdateSet_PersistentMap_Unicode | 100 | 50 | 2,517.24 ns | 2.7046 | 0.0496 | - | 22640 B | -| UpdateSet_TransientMap_Standard | 100 | 50 | 5,282.92 ns | 0.4349 | - | - | 3640 B | -| UpdateSet_TransientMap_Unicode | 100 | 50 | 1,214.38 ns | 0.4635 | 0.0057 | - | 3888 B | -| UpdateSet_ImmSortedDict | 100 | 50 | 2,531.36 ns | 0.4730 | - | - | 3984 B | -| UpdateSet_ExtMap | 100 | 50 | 2,399.90 ns | 0.5913 | - | - | 4968 B | -| UpdateSet_ExtHashMap | 100 | 50 | 1,041.57 ns | 0.5512 | 0.0019 | - | 4624 B | -| Iterate_ImmDict | 100 | 50 | 1,265.60 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 100 | 50 | 183.51 ns | - | - | - | - | -| Iterate_ImmSortedDict | 100 | 50 | 487.68 ns | - | - | - | - | -| Iterate_ExtMap | 100 | 50 | 313.58 ns | 0.0038 | - | - | 32 B | -| Iterate_ExtHashMap | 100 | 50 | 1,174.73 ns | 0.2747 | - | - | 2304 B | -| Iterate_PersistentMap_Unicode | 100 | 50 | 190.36 ns | - | - | - | - | -| Remove_ImmDict | 100 | 50 | 1,566.27 ns | 0.5283 | 0.0019 | - | 4432 B | -| Remove_PersistentMap_Standard | 100 | 50 | 7,539.09 ns | 2.4338 | 0.0381 | - | 20400 B | -| Remove_PersistentMap_Unicode | 100 | 50 | 2,572.60 ns | 2.6779 | 0.0458 | - | 22400 B | -| Remove_TransientMap_Standard | 100 | 50 | 6,450.13 ns | 0.4654 | - | - | 3928 B | -| Remove_TransientMap_Unicode | 100 | 50 | 1,408.78 ns | 0.4406 | 0.0057 | - | 3688 B | -| Remove_ImmSortedDict | 100 | 50 | 2,241.88 ns | 0.3891 | - | - | 3264 B | -| Remove_ExtMap | 100 | 50 | 2,156.69 ns | 0.4387 | - | - | 3680 B | -| Remove_ExtHashMap | 100 | 50 | 927.47 ns | 0.4807 | 0.0010 | - | 4024 B | -| Build_TransientMap_Standard | 1000 | 8 | 741,046.07 ns | 5.8594 | - | - | 49512 B | -| Build_TransientMap_Unicode | 1000 | 8 | 176,282.35 ns | 7.3242 | 0.7324 | - | 62248 B | -| Build_ImmDict | 1000 | 8 | 251,462.15 ns | 79.1016 | 8.3008 | - | 663488 B | -| Build_ImmSortedDict | 1000 | 8 | 420,903.44 ns | 61.0352 | 4.8828 | - | 513312 B | -| Build_ExtMap | 1000 | 8 | 426,942.00 ns | 79.1016 | 7.3242 | - | 662448 B | -| Build_ExtHashMap | 1000 | 8 | 154,509.50 ns | 70.3125 | 4.8828 | - | 589264 B | -| Retrieve_ImmDict | 1000 | 8 | 1,057.94 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 1000 | 8 | 71,081.84 ns | 0.4883 | - | - | 4800 B | -| Retrieve_PersistentMap_Unicode | 1000 | 8 | 2,056.30 ns | - | - | - | - | -| Update_ImmDict | 1000 | 8 | 20,613.44 ns | 7.4768 | 0.2747 | - | 62688 B | -| Update_PersistentMap_Standard | 1000 | 8 | 91,445.80 ns | 24.0479 | 2.3193 | - | 201600 B | -| Update_PersistentMap_Unicode | 1000 | 8 | 29,005.34 ns | 27.0386 | 2.5330 | - | 226400 B | -| Update_TransientMap_Standard | 1000 | 8 | 85,352.16 ns | 4.5166 | 0.4883 | - | 38184 B | -| Update_TransientMap_Unicode | 1000 | 8 | 7,968.40 ns | 4.4250 | 0.4425 | - | 37024 B | -| Update_ImmSortedDict | 1000 | 8 | 32,334.14 ns | 5.7373 | 0.1221 | - | 48288 B | -| Update_ExtMap | 1000 | 8 | 34,280.75 ns | 6.4087 | 0.1831 | - | 53824 B | -| Update_ExtHashMap | 1000 | 8 | 11,163.54 ns | 7.1716 | 0.2289 | - | 60016 B | -| UpdateSet_ImmDict | 1000 | 8 | 23,570.66 ns | 8.3618 | 0.3967 | - | 69984 B | -| UpdateSet_PersistentMap_Standard | 1000 | 8 | 103,655.77 ns | 24.0479 | 2.1973 | - | 201600 B | -| UpdateSet_PersistentMap_Unicode | 1000 | 8 | 33,858.71 ns | 27.0386 | 2.6245 | - | 226400 B | -| UpdateSet_TransientMap_Standard | 1000 | 8 | 86,914.93 ns | 4.1504 | 0.3662 | - | 35368 B | -| UpdateSet_TransientMap_Unicode | 1000 | 8 | 12,939.69 ns | 4.5929 | 0.4883 | - | 38432 B | -| UpdateSet_ImmSortedDict | 1000 | 8 | 37,789.08 ns | 6.3477 | 0.2441 | - | 53232 B | -| UpdateSet_ExtMap | 1000 | 8 | 38,304.46 ns | 7.6904 | 0.3052 | - | 64576 B | -| UpdateSet_ExtHashMap | 1000 | 8 | 14,695.01 ns | 7.7057 | 0.2747 | - | 64464 B | -| Iterate_ImmDict | 1000 | 8 | 13,240.07 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 1000 | 8 | 1,628.88 ns | - | - | - | - | -| Iterate_ImmSortedDict | 1000 | 8 | 4,933.29 ns | - | - | - | - | -| Iterate_ExtMap | 1000 | 8 | 3,191.58 ns | 0.0038 | - | - | 32 B | -| Iterate_ExtHashMap | 1000 | 8 | 14,380.64 ns | 2.6550 | - | - | 22320 B | -| Iterate_PersistentMap_Unicode | 1000 | 8 | 1,647.93 ns | - | - | - | - | -| Remove_ImmDict | 1000 | 8 | 20,527.32 ns | 7.6599 | 0.3052 | - | 64160 B | -| Remove_PersistentMap_Standard | 1000 | 8 | 106,140.73 ns | 24.2920 | 1.5869 | - | 204000 B | -| Remove_PersistentMap_Unicode | 1000 | 8 | 30,242.16 ns | 26.7639 | 2.4414 | - | 224000 B | -| Remove_TransientMap_Standard | 1000 | 8 | 91,057.33 ns | 4.6387 | 0.4883 | - | 39224 B | -| Remove_TransientMap_Unicode | 1000 | 8 | 14,218.28 ns | 3.7994 | 0.3662 | - | 31848 B | -| Remove_ImmSortedDict | 1000 | 8 | 33,413.24 ns | 5.6763 | 0.1221 | - | 47616 B | -| Remove_ExtMap | 1000 | 8 | 37,472.01 ns | 6.8970 | 0.1831 | - | 57744 B | -| Remove_ExtHashMap | 1000 | 8 | 12,185.93 ns | 7.9803 | 0.2441 | - | 66864 B | -| Build_TransientMap_Standard | 1000 | 50 | 727,350.80 ns | 4.8828 | - | - | 44992 B | -| Build_TransientMap_Unicode | 1000 | 50 | 176,948.61 ns | 7.0801 | 0.4883 | - | 59368 B | -| Build_ImmDict | 1000 | 50 | 285,938.59 ns | 78.6133 | 8.3008 | - | 660096 B | -| Build_ImmSortedDict | 1000 | 50 | 433,882.40 ns | 61.0352 | 5.3711 | - | 511344 B | -| Build_ExtMap | 1000 | 50 | 412,922.58 ns | 78.1250 | 7.3242 | - | 656288 B | -| Build_ExtHashMap | 1000 | 50 | 185,309.94 ns | 70.3125 | 4.8828 | - | 589128 B | -| Retrieve_ImmDict | 1000 | 50 | 3,151.77 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 1000 | 50 | 76,705.78 ns | 0.4883 | - | - | 4800 B | -| Retrieve_PersistentMap_Unicode | 1000 | 50 | 2,077.37 ns | - | - | - | - | -| Update_ImmDict | 1000 | 50 | 23,312.29 ns | 7.5073 | 0.3052 | - | 62880 B | -| Update_PersistentMap_Standard | 1000 | 50 | 93,945.90 ns | 24.0479 | 2.0752 | - | 201600 B | -| Update_PersistentMap_Unicode | 1000 | 50 | 29,787.05 ns | 27.0386 | 2.2888 | - | 226400 B | -| Update_TransientMap_Standard | 1000 | 50 | 85,349.42 ns | 3.7842 | 0.2441 | - | 32552 B | -| Update_TransientMap_Unicode | 1000 | 50 | 7,349.19 ns | 4.0894 | 0.4044 | - | 34208 B | -| Update_ImmSortedDict | 1000 | 50 | 32,705.50 ns | 5.6763 | 0.1221 | - | 47952 B | -| Update_ExtMap | 1000 | 50 | 33,742.93 ns | 6.5308 | 0.1831 | - | 54720 B | -| Update_ExtHashMap | 1000 | 50 | 13,720.77 ns | 7.2479 | 0.2289 | - | 60688 B | -| UpdateSet_ImmDict | 1000 | 50 | 24,725.01 ns | 8.0566 | 0.3967 | - | 67424 B | -| UpdateSet_PersistentMap_Standard | 1000 | 50 | 106,965.60 ns | 24.1699 | 1.9531 | - | 202504 B | -| UpdateSet_PersistentMap_Unicode | 1000 | 50 | 34,398.72 ns | 27.2217 | 2.6245 | - | 227840 B | -| UpdateSet_TransientMap_Standard | 1000 | 50 | 89,942.92 ns | 3.9063 | 0.3662 | - | 33456 B | -| UpdateSet_TransientMap_Unicode | 1000 | 50 | 13,555.52 ns | 4.4250 | 0.4578 | - | 37056 B | -| UpdateSet_ImmSortedDict | 1000 | 50 | 37,035.94 ns | 6.2866 | 0.1831 | - | 53088 B | -| UpdateSet_ExtMap | 1000 | 50 | 39,025.94 ns | 7.8735 | 0.3052 | - | 66200 B | -| UpdateSet_ExtHashMap | 1000 | 50 | 15,654.51 ns | 7.5989 | 0.2441 | - | 63608 B | -| Iterate_ImmDict | 1000 | 50 | 12,858.73 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 1000 | 50 | 1,674.98 ns | - | - | - | - | -| Iterate_ImmSortedDict | 1000 | 50 | 4,882.38 ns | - | - | - | - | -| Iterate_ExtMap | 1000 | 50 | 3,498.40 ns | 0.0038 | - | - | 32 B | -| Iterate_ExtHashMap | 1000 | 50 | 14,585.65 ns | 2.7008 | - | - | 22608 B | -| Iterate_PersistentMap_Unicode | 1000 | 50 | 1,654.17 ns | - | - | - | - | -| Remove_ImmDict | 1000 | 50 | 24,001.50 ns | 7.6904 | 0.3052 | - | 64480 B | -| Remove_PersistentMap_Standard | 1000 | 50 | 115,583.75 ns | 24.2920 | 1.7090 | - | 204000 B | -| Remove_PersistentMap_Unicode | 1000 | 50 | 30,177.90 ns | 26.7639 | 2.2583 | - | 224000 B | -| Remove_TransientMap_Standard | 1000 | 50 | 104,629.87 ns | 3.9063 | 0.2441 | - | 33592 B | -| Remove_TransientMap_Unicode | 1000 | 50 | 13,859.15 ns | 3.2959 | 0.2899 | - | 27624 B | -| Remove_ImmSortedDict | 1000 | 50 | 34,305.81 ns | 5.6763 | 0.1221 | - | 47664 B | -| Remove_ExtMap | 1000 | 50 | 36,962.41 ns | 6.9580 | 0.2441 | - | 58640 B | -| Remove_ExtHashMap | 1000 | 50 | 14,045.01 ns | 7.8583 | 0.1984 | - | 65800 B | -| Build_TransientMap_Standard | 10000 | 8 | 10,481,091.44 ns | 46.8750 | 15.6250 | - | 453272 B | -| Build_TransientMap_Unicode | 10000 | 8 | 2,288,777.17 ns | 66.4063 | 19.5313 | - | 576584 B | -| Build_ImmDict | 10000 | 8 | 4,451,716.58 ns | 1046.8750 | 500.0000 | - | 8790528 B | -| Build_ImmSortedDict | 10000 | 8 | 6,589,276.41 ns | 804.6875 | 281.2500 | - | 6767232 B | -| Build_ExtMap | 10000 | 8 | 6,466,222.77 ns | 1015.6250 | 437.5000 | - | 8542480 B | -| Build_ExtHashMap | 10000 | 8 | 1,987,067.22 ns | 945.3125 | 312.5000 | - | 7921664 B | -| Retrieve_ImmDict | 10000 | 8 | 23,653.66 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 10000 | 8 | 1,067,006.38 ns | 7.8125 | - | - | 72000 B | -| Retrieve_PersistentMap_Unicode | 10000 | 8 | 33,794.37 ns | - | - | - | - | -| Update_ImmDict | 10000 | 8 | 393,904.16 ns | 100.5859 | 25.3906 | - | 844992 B | -| Update_PersistentMap_Standard | 10000 | 8 | 1,373,414.67 ns | 304.6875 | 132.8125 | - | 2560000 B | -| Update_PersistentMap_Unicode | 10000 | 8 | 416,266.85 ns | 366.2109 | 160.1563 | - | 3064000 B | -| Update_TransientMap_Standard | 10000 | 8 | 1,160,717.93 ns | 41.0156 | 13.6719 | - | 346280 B | -| Update_TransientMap_Unicode | 10000 | 8 | 154,306.58 ns | 40.7715 | 16.6016 | - | 341792 B | -| Update_ImmSortedDict | 10000 | 8 | 583,839.85 ns | 76.1719 | 15.6250 | - | 640368 B | -| Update_ExtMap | 10000 | 8 | 571,924.12 ns | 87.8906 | 22.4609 | - | 735640 B | -| Update_ExtHashMap | 10000 | 8 | 164,331.80 ns | 102.7832 | 22.9492 | - | 860152 B | -| UpdateSet_ImmDict | 10000 | 8 | 433,669.48 ns | 108.3984 | 29.2969 | - | 907072 B | -| UpdateSet_PersistentMap_Standard | 10000 | 8 | 1,426,063.43 ns | 306.6406 | 130.8594 | - | 2572328 B | -| UpdateSet_PersistentMap_Unicode | 10000 | 8 | 502,233.19 ns | 368.1641 | 154.2969 | - | 3086432 B | -| UpdateSet_TransientMap_Standard | 10000 | 8 | 1,183,758.86 ns | 41.0156 | 15.6250 | - | 357200 B | -| UpdateSet_TransientMap_Unicode | 10000 | 8 | 224,362.69 ns | 43.7012 | 16.3574 | - | 365632 B | -| UpdateSet_ImmSortedDict | 10000 | 8 | 648,222.74 ns | 82.0313 | 19.5313 | - | 687648 B | -| UpdateSet_ExtMap | 10000 | 8 | 638,998.68 ns | 99.6094 | 25.3906 | - | 833752 B | -| UpdateSet_ExtHashMap | 10000 | 8 | 188,297.97 ns | 104.9805 | 23.9258 | - | 878640 B | -| Iterate_ImmDict | 10000 | 8 | 180,912.56 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 10000 | 8 | 17,134.49 ns | - | - | - | - | -| Iterate_ImmSortedDict | 10000 | 8 | 55,355.92 ns | - | - | - | - | -| Iterate_ExtMap | 10000 | 8 | 66,171.51 ns | - | - | - | 32 B | -| Iterate_ExtHashMap | 10000 | 8 | 173,270.08 ns | 20.2637 | - | - | 171360 B | -| Iterate_PersistentMap_Unicode | 10000 | 8 | 17,291.12 ns | - | - | - | - | -| Remove_ImmDict | 10000 | 8 | 398,833.85 ns | 102.0508 | 24.9023 | - | 857216 B | -| Remove_PersistentMap_Standard | 10000 | 8 | 1,488,081.64 ns | 310.5469 | 123.0469 | - | 2608000 B | -| Remove_PersistentMap_Unicode | 10000 | 8 | 492,391.59 ns | 363.2813 | 180.6641 | - | 3041408 B | -| Remove_TransientMap_Standard | 10000 | 8 | 1,334,741.70 ns | 44.9219 | 15.6250 | - | 388696 B | -| Remove_TransientMap_Unicode | 10000 | 8 | 224,694.52 ns | 38.0859 | 15.1367 | - | 319240 B | -| Remove_ImmSortedDict | 10000 | 8 | 624,219.05 ns | 77.1484 | 15.6250 | - | 652944 B | -| Remove_ExtMap | 10000 | 8 | 648,812.07 ns | 91.7969 | 20.5078 | - | 774000 B | -| Remove_ExtHashMap | 10000 | 8 | 187,697.63 ns | 104.2480 | 21.4844 | - | 873600 B | -| Build_TransientMap_Standard | 10000 | 50 | 11,121,008.03 ns | 46.8750 | 15.6250 | - | 453272 B | -| Build_TransientMap_Unicode | 10000 | 50 | 2,345,447.27 ns | 66.4063 | 23.4375 | - | 567112 B | -| Build_ImmDict | 10000 | 50 | 4,711,900.43 ns | 1039.0625 | 507.8125 | - | 8746752 B | -| Build_ImmSortedDict | 10000 | 50 | 6,774,329.88 ns | 804.6875 | 265.6250 | - | 6751584 B | -| Build_ExtMap | 10000 | 50 | 6,748,429.60 ns | 1015.6250 | 429.6875 | - | 8544720 B | -| Build_ExtHashMap | 10000 | 50 | 2,211,770.12 ns | 941.4063 | 332.0313 | - | 7894224 B | -| Retrieve_ImmDict | 10000 | 50 | 53,674.39 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 10000 | 50 | 1,099,621.23 ns | 7.8125 | - | - | 72000 B | -| Retrieve_PersistentMap_Unicode | 10000 | 50 | 34,282.21 ns | - | - | - | - | -| Update_ImmDict | 10000 | 50 | 416,110.39 ns | 101.0742 | 24.9023 | - | 845696 B | -| Update_PersistentMap_Standard | 10000 | 50 | 1,432,050.69 ns | 304.6875 | 125.0000 | - | 2560000 B | -| Update_PersistentMap_Unicode | 10000 | 50 | 482,376.79 ns | 366.2109 | 158.2031 | - | 3064000 B | -| Update_TransientMap_Standard | 10000 | 50 | 1,185,346.37 ns | 39.0625 | 11.7188 | - | 339240 B | -| Update_TransientMap_Unicode | 10000 | 50 | 153,479.49 ns | 39.5508 | 13.9160 | - | 331136 B | -| Update_ImmSortedDict | 10000 | 50 | 578,927.03 ns | 76.1719 | 14.6484 | - | 641616 B | -| Update_ExtMap | 10000 | 50 | 582,689.89 ns | 86.9141 | 20.5078 | - | 732896 B | -| Update_ExtHashMap | 10000 | 50 | 195,769.80 ns | 102.5391 | 20.5078 | - | 859328 B | -| UpdateSet_ImmDict | 10000 | 50 | 460,644.35 ns | 109.3750 | 30.2734 | - | 915264 B | -| UpdateSet_PersistentMap_Standard | 10000 | 50 | 1,488,123.25 ns | 306.6406 | 132.8125 | - | 2575040 B | -| UpdateSet_PersistentMap_Unicode | 10000 | 50 | 516,710.59 ns | 368.1641 | 152.3438 | - | 3085600 B | -| UpdateSet_TransientMap_Standard | 10000 | 50 | 1,257,184.91 ns | 41.0156 | 13.6719 | - | 355688 B | -| UpdateSet_TransientMap_Unicode | 10000 | 50 | 222,602.17 ns | 42.2363 | 15.8691 | - | 354144 B | -| UpdateSet_ImmSortedDict | 10000 | 50 | 659,407.65 ns | 82.0313 | 19.5313 | - | 691728 B | -| UpdateSet_ExtMap | 10000 | 50 | 652,772.00 ns | 98.6328 | 23.4375 | - | 829552 B | -| UpdateSet_ExtHashMap | 10000 | 50 | 216,637.22 ns | 104.9805 | 24.1699 | - | 878840 B | -| Iterate_ImmDict | 10000 | 50 | 172,211.95 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 10000 | 50 | 17,227.48 ns | - | - | - | - | -| Iterate_ImmSortedDict | 10000 | 50 | 56,105.18 ns | - | - | - | - | -| Iterate_ExtMap | 10000 | 50 | 70,322.76 ns | - | - | - | 32 B | -| Iterate_ExtHashMap | 10000 | 50 | 176,803.08 ns | 20.0195 | - | - | 168264 B | -| Iterate_PersistentMap_Unicode | 10000 | 50 | 17,473.52 ns | - | - | - | - | -| Remove_ImmDict | 10000 | 50 | 429,520.72 ns | 102.5391 | 28.3203 | - | 860032 B | -| Remove_PersistentMap_Standard | 10000 | 50 | 1,558,056.72 ns | 310.5469 | 125.0000 | - | 2610816 B | -| Remove_PersistentMap_Unicode | 10000 | 50 | 522,677.95 ns | 363.2813 | 158.2031 | - | 3042816 B | -| Remove_TransientMap_Standard | 10000 | 50 | 1,333,105.09 ns | 44.9219 | 15.6250 | - | 391512 B | -| Remove_TransientMap_Unicode | 10000 | 50 | 223,026.23 ns | 37.3535 | 13.1836 | - | 312808 B | -| Remove_ImmSortedDict | 10000 | 50 | 611,137.62 ns | 77.1484 | 15.6250 | - | 651840 B | -| Remove_ExtMap | 10000 | 50 | 630,117.55 ns | 91.7969 | 21.4844 | - | 771256 B | -| Remove_ExtHashMap | 10000 | 50 | 208,051.51 ns | 104.7363 | 20.7520 | - | 877216 B | -| Build_TransientMap_Standard | 100000 | 8 | 158,074,169.00 ns | 500.0000 | 250.0000 | - | 4524208 B | -| Build_TransientMap_Unicode | 100000 | 8 | 40,354,289.36 ns | 692.3077 | 538.4615 | - | 5798728 B | -| Build_ImmDict | 100000 | 8 | 95,840,918.80 ns | 13200.0000 | 4600.0000 | 200.0000 | 109352965 B | -| Build_ImmSortedDict | 100000 | 8 | 145,451,097.25 ns | 10000.0000 | 3750.0000 | - | 83946432 B | -| Build_ExtMap | 100000 | 8 | 121,026,729.25 ns | 12500.0000 | 5750.0000 | - | 104660688 B | -| Build_ExtHashMap | 100000 | 8 | 45,582,232.06 ns | 12000.0000 | 2583.3333 | 83.3333 | 99799303 B | -| Retrieve_ImmDict | 100000 | 8 | 1,518,946.53 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 100000 | 8 | 15,954,066.23 ns | 93.7500 | - | - | 960000 B | -| Retrieve_PersistentMap_Unicode | 100000 | 8 | 1,344,373.24 ns | - | - | - | - | -| Update_ImmDict | 100000 | 8 | 9,168,537.50 ns | 1265.6250 | 1062.5000 | - | 10588288 B | -| Update_PersistentMap_Standard | 100000 | 8 | 24,537,720.52 ns | 3718.7500 | 2906.2500 | 31.2500 | 31040034 B | -| Update_PersistentMap_Unicode | 100000 | 8 | 11,821,730.59 ns | 4671.8750 | 2500.0000 | 62.5000 | 38640073 B | -| Update_TransientMap_Standard | 100000 | 8 | 18,410,364.31 ns | 406.2500 | 312.5000 | - | 3415688 B | -| Update_TransientMap_Unicode | 100000 | 8 | 3,724,912.85 ns | 414.0625 | 351.5625 | - | 3487208 B | -| Update_ImmSortedDict | 100000 | 8 | 11,002,667.72 ns | 953.1250 | 781.2500 | - | 8020080 B | -| Update_ExtMap | 100000 | 8 | 11,289,391.77 ns | 1093.7500 | 937.5000 | - | 9246400 B | -| Update_ExtHashMap | 100000 | 8 | 3,795,553.44 ns | 1265.6250 | 968.7500 | - | 10624032 B | -| UpdateSet_ImmDict | 100000 | 8 | 9,324,688.19 ns | 1343.7500 | 1046.8750 | - | 11264256 B | -| UpdateSet_PersistentMap_Standard | 100000 | 8 | 28,227,498.72 ns | 3750.0000 | 2593.7500 | 31.2500 | 31143252 B | -| UpdateSet_PersistentMap_Unicode | 100000 | 8 | 12,980,484.49 ns | 4703.1250 | 2250.0000 | 62.5000 | 38809434 B | -| UpdateSet_TransientMap_Standard | 100000 | 8 | 18,455,887.50 ns | 406.2500 | 312.5000 | - | 3514688 B | -| UpdateSet_TransientMap_Unicode | 100000 | 8 | 4,599,394.95 ns | 429.6875 | 382.8125 | - | 3636872 B | -| UpdateSet_ImmSortedDict | 100000 | 8 | 11,611,425.71 ns | 1015.6250 | 875.0000 | - | 8495856 B | -| UpdateSet_ExtMap | 100000 | 8 | 13,256,339.44 ns | 1218.7500 | 1015.6250 | - | 10287328 B | -| UpdateSet_ExtHashMap | 100000 | 8 | 4,056,833.05 ns | 1281.2500 | 992.1875 | - | 10752664 B | -| Iterate_ImmDict | 100000 | 8 | 2,245,167.14 ns | - | - | - | 96 B | -| Iterate_PersistentMap_Standard | 100000 | 8 | 226,078.34 ns | - | - | - | - | -| Iterate_ImmSortedDict | 100000 | 8 | 752,117.01 ns | - | - | - | - | -| Iterate_ExtMap | 100000 | 8 | 1,153,701.70 ns | - | - | - | 32 B | -| Iterate_ExtHashMap | 100000 | 8 | 2,747,144.18 ns | 273.4375 | - | - | 2318432 B | -| Iterate_PersistentMap_Unicode | 100000 | 8 | 241,814.65 ns | - | - | - | - | -| Remove_ImmDict | 100000 | 8 | 8,922,500.09 ns | 1281.2500 | 1031.2500 | - | 10744896 B | -| Remove_PersistentMap_Standard | 100000 | 8 | 28,366,797.02 ns | 3812.5000 | 2750.0000 | 31.2500 | 31781143 B | -| Remove_PersistentMap_Unicode | 100000 | 8 | 12,499,542.97 ns | 4640.6250 | 2875.0000 | 62.5000 | 38412730 B | -| Remove_TransientMap_Standard | 100000 | 8 | 21,085,953.04 ns | 468.7500 | 343.7500 | - | 4134328 B | -| Remove_TransientMap_Unicode | 100000 | 8 | 3,227,138.07 ns | 382.8125 | 324.2188 | - | 3226088 B | -| Remove_ImmSortedDict | 100000 | 8 | 10,475,303.83 ns | 953.1250 | 781.2500 | - | 8031888 B | -| Remove_ExtMap | 100000 | 8 | 11,473,612.68 ns | 1140.6250 | 937.5000 | - | 9630448 B | -| Remove_ExtHashMap | 100000 | 8 | 3,920,078.08 ns | 1265.6250 | 914.0625 | - | 10624344 B | -| Build_TransientMap_Standard | 100000 | 50 | 186,991,900.50 ns | 500.0000 | - | - | 4511632 B | -| Build_TransientMap_Unicode | 100000 | 50 | 42,267,081.83 ns | 666.6667 | 500.0000 | - | 5763784 B | -| Build_ImmDict | 100000 | 50 | 91,363,990.94 ns | 13166.6667 | 4833.3333 | 166.6667 | 109457840 B | -| Build_ImmSortedDict | 100000 | 50 | 134,058,261.08 ns | 10000.0000 | 4000.0000 | - | 83731632 B | -| Build_ExtMap | 100000 | 50 | 130,083,384.42 ns | 12250.0000 | 5250.0000 | - | 104371560 B | -| Build_ExtHashMap | 100000 | 50 | 50,176,040.06 ns | 11909.0909 | 2545.4545 | - | 99721040 B | -| Retrieve_ImmDict | 100000 | 50 | 1,878,376.76 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 100000 | 50 | 22,662,880.57 ns | 93.7500 | - | - | 960000 B | -| Retrieve_PersistentMap_Unicode | 100000 | 50 | 1,421,803.13 ns | - | - | - | - | -| Update_ImmDict | 100000 | 50 | 8,957,341.32 ns | 1265.6250 | 1062.5000 | - | 10646144 B | -| Update_PersistentMap_Standard | 100000 | 50 | 37,471,664.44 ns | 3692.3077 | 2769.2308 | - | 31040000 B | -| Update_PersistentMap_Unicode | 100000 | 50 | 12,856,950.49 ns | 4656.2500 | 2781.2500 | 46.8750 | 38640047 B | -| Update_TransientMap_Standard | 100000 | 50 | 26,563,266.79 ns | 406.2500 | 312.5000 | - | 3414056 B | -| Update_TransientMap_Unicode | 100000 | 50 | 4,173,539.70 ns | 406.2500 | 335.9375 | - | 3440328 B | -| Update_ImmSortedDict | 100000 | 50 | 11,812,577.59 ns | 953.1250 | 781.2500 | - | 8012928 B | -| Update_ExtMap | 100000 | 50 | 11,842,757.42 ns | 1093.7500 | 906.2500 | - | 9233632 B | -| Update_ExtHashMap | 100000 | 50 | 4,189,884.01 ns | 1265.6250 | 984.3750 | - | 10632736 B | -| UpdateSet_ImmDict | 100000 | 50 | 10,459,268.37 ns | 1343.7500 | 1031.2500 | - | 11256960 B | -| UpdateSet_PersistentMap_Standard | 100000 | 50 | 41,029,434.25 ns | 3666.6667 | 2500.0000 | - | 31141992 B | -| UpdateSet_PersistentMap_Unicode | 100000 | 50 | 14,028,512.79 ns | 4687.5000 | 2421.8750 | 46.8750 | 38835953 B | -| UpdateSet_TransientMap_Standard | 100000 | 50 | 28,314,403.39 ns | 406.2500 | 312.5000 | - | 3509008 B | -| UpdateSet_TransientMap_Unicode | 100000 | 50 | 5,267,848.18 ns | 429.6875 | 359.3750 | - | 3630600 B | -| UpdateSet_ImmSortedDict | 100000 | 50 | 14,089,168.54 ns | 1015.6250 | 890.6250 | - | 8501136 B | -| UpdateSet_ExtMap | 100000 | 50 | 12,785,268.31 ns | 1218.7500 | 1015.6250 | - | 10265544 B | -| UpdateSet_ExtHashMap | 100000 | 50 | 4,418,089.19 ns | 1281.2500 | 992.1875 | - | 10750000 B | -| Iterate_ImmDict | 100000 | 50 | 2,338,660.26 ns | - | - | - | 384 B | -| Iterate_PersistentMap_Standard | 100000 | 50 | 211,359.08 ns | - | - | - | - | -| Iterate_ImmSortedDict | 100000 | 50 | 715,600.57 ns | - | - | - | - | -| Iterate_ExtMap | 100000 | 50 | 1,117,575.94 ns | - | - | - | 32 B | -| Iterate_ExtHashMap | 100000 | 50 | 2,975,826.72 ns | 277.3438 | - | - | 2319904 B | -| Iterate_PersistentMap_Unicode | 100000 | 50 | 217,594.04 ns | - | - | - | - | -| Remove_ImmDict | 100000 | 50 | 9,190,906.43 ns | 1281.2500 | 1046.8750 | - | 10741824 B | -| Remove_PersistentMap_Standard | 100000 | 50 | 42,259,057.45 ns | 3727.2727 | 2636.3636 | - | 31774080 B | -| Remove_PersistentMap_Unicode | 100000 | 50 | 11,946,176.86 ns | 4625.0000 | 2859.3750 | 46.8750 | 38402868 B | -| Remove_TransientMap_Standard | 100000 | 50 | 26,120,804.07 ns | 468.7500 | 343.7500 | - | 4127064 B | -| Remove_TransientMap_Unicode | 100000 | 50 | 3,583,417.50 ns | 382.8125 | 312.5000 | - | 3205960 B | -| Remove_ImmSortedDict | 100000 | 50 | 14,164,094.39 ns | 953.1250 | 781.2500 | - | 8032656 B | -| Remove_ExtMap | 100000 | 50 | 13,861,593.95 ns | 1140.6250 | 921.8750 | - | 9617232 B | -| Remove_ExtHashMap | 100000 | 50 | 4,797,368.31 ns | 1265.6250 | 937.5000 | - | 10628320 B | +| Method | Size | keylen | Mean | Gen0 | Gen1 | Gen2 | Allocated | +|----------------------------------|--------|--------|------------------:|-----------:|----------:|---------:|------------:| +| Build_TransientMap_Standard | 100 | 8 | 17,993.68 ns | 0.7324 | - | - | 6216 B | +| Build_TransientMap_Unicode | 100 | 8 | 12,240.69 ns | 0.8850 | 0.0153 | - | 7528 B | +| Build_ImmDict | 100 | 8 | 13,807.87 ns | 5.3406 | 0.0610 | - | 44704 B | +| Build_ImmSortedDict | 100 | 8 | 21,920.45 ns | 4.2114 | 0.0305 | - | 35472 B | +| Build_ExtMap | 100 | 8 | 22,192.67 ns | 5.6152 | 0.0610 | - | 47104 B | +| Build_ExtHashMap | 100 | 8 | 10,569.35 ns | 4.5013 | 0.0305 | - | 37680 B | +| Retrieve_ImmDict | 100 | 8 | 79.60 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100 | 8 | 1,009.56 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 100 | 8 | 148.82 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 100 | 8 | 1,198.42 ns | - | - | - | - | +| Retrieve_ExtMap | 100 | 8 | 1,305.45 ns | - | - | - | - | +| Retrieve_ExtHashMap | 100 | 8 | 197.96 ns | - | - | - | - | +| Update_ImmDict | 100 | 8 | 1,232.22 ns | 0.4826 | 0.0019 | - | 4048 B | +| Update_PersistentMap_Standard | 100 | 8 | 2,884.71 ns | 2.4071 | 0.0420 | - | 20160 B | +| Update_PersistentMap_Unicode | 100 | 8 | 1,955.50 ns | 2.7046 | 0.0496 | - | 22640 B | +| Update_TransientMap_Standard | 100 | 8 | 1,621.74 ns | 0.4349 | 0.0038 | - | 3640 B | +| Update_TransientMap_Unicode | 100 | 8 | 674.43 ns | 0.4644 | 0.0057 | - | 3888 B | +| Update_ImmSortedDict | 100 | 8 | 1,701.62 ns | 0.3662 | - | - | 3072 B | +| Update_ExtMap | 100 | 8 | 1,803.33 ns | 0.4120 | - | - | 3456 B | +| Update_ExtHashMap | 100 | 8 | 752.46 ns | 0.5083 | 0.0010 | - | 4256 B | +| UpdateSet_ImmDict | 100 | 8 | 1,450.07 ns | 0.5512 | 0.0019 | - | 4624 B | +| UpdateSet_PersistentMap_Standard | 100 | 8 | 3,246.39 ns | 2.4071 | 0.0420 | - | 20160 B | +| UpdateSet_PersistentMap_Unicode | 100 | 8 | 2,587.39 ns | 2.7046 | 0.0534 | - | 22640 B | +| UpdateSet_TransientMap_Standard | 100 | 8 | 1,970.08 ns | 0.4349 | 0.0038 | - | 3640 B | +| UpdateSet_TransientMap_Unicode | 100 | 8 | 1,184.37 ns | 0.4635 | 0.0057 | - | 3888 B | +| UpdateSet_ImmSortedDict | 100 | 8 | 2,352.86 ns | 0.4578 | - | - | 3840 B | +| UpdateSet_ExtMap | 100 | 8 | 2,089.91 ns | 0.4730 | - | - | 3960 B | +| UpdateSet_ExtHashMap | 100 | 8 | 819.11 ns | 0.5445 | 0.0010 | - | 4560 B | +| Iterate_ImmDict | 100 | 8 | 1,338.64 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 100 | 8 | 183.94 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100 | 8 | 490.25 ns | - | - | - | - | +| Iterate_ExtMap | 100 | 8 | 332.42 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 100 | 8 | 1,162.47 ns | 0.2747 | - | - | 2304 B | +| Iterate_PersistentMap_Unicode | 100 | 8 | 186.95 ns | - | - | - | - | +| Remove_ImmDict | 100 | 8 | 1,396.87 ns | 0.4978 | 0.0019 | - | 4176 B | +| Remove_PersistentMap_Standard | 100 | 8 | 3,197.32 ns | 2.3804 | 0.0420 | - | 19920 B | +| Remove_PersistentMap_Unicode | 100 | 8 | 2,522.69 ns | 2.6779 | 0.0496 | - | 22400 B | +| Remove_TransientMap_Standard | 100 | 8 | 1,973.57 ns | 0.4120 | 0.0038 | - | 3448 B | +| Remove_TransientMap_Unicode | 100 | 8 | 1,320.29 ns | 0.4406 | 0.0057 | - | 3688 B | +| Remove_ImmSortedDict | 100 | 8 | 1,950.87 ns | 0.3777 | - | - | 3168 B | +| Remove_ExtMap | 100 | 8 | 2,240.33 ns | 0.5798 | - | - | 4856 B | +| Remove_ExtHashMap | 100 | 8 | 746.28 ns | 0.5140 | 0.0019 | - | 4304 B | +| Build_TransientMap_Standard | 100 | 50 | 18,239.16 ns | 0.7324 | - | - | 6216 B | +| Build_TransientMap_Unicode | 100 | 50 | 11,800.99 ns | 0.8850 | 0.0153 | - | 7528 B | +| Build_ImmDict | 100 | 50 | 16,343.07 ns | 5.3406 | 0.0610 | - | 44832 B | +| Build_ImmSortedDict | 100 | 50 | 21,709.91 ns | 4.2114 | 0.0305 | - | 35232 B | +| Build_ExtMap | 100 | 50 | 22,948.94 ns | 5.6458 | 0.0610 | - | 47328 B | +| Build_ExtHashMap | 100 | 50 | 12,107.35 ns | 4.0894 | 0.0305 | - | 34280 B | +| Retrieve_ImmDict | 100 | 50 | 286.13 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100 | 50 | 984.90 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 100 | 50 | 151.45 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 100 | 50 | 1,308.09 ns | - | - | - | - | +| Retrieve_ExtMap | 100 | 50 | 1,164.25 ns | - | - | - | - | +| Retrieve_ExtHashMap | 100 | 50 | 398.29 ns | - | - | - | - | +| Update_ImmDict | 100 | 50 | 1,390.29 ns | 0.4749 | 0.0019 | - | 3984 B | +| Update_PersistentMap_Standard | 100 | 50 | 2,932.88 ns | 2.4071 | 0.0381 | - | 20160 B | +| Update_PersistentMap_Unicode | 100 | 50 | 1,947.26 ns | 2.7046 | 0.0534 | - | 22640 B | +| Update_TransientMap_Standard | 100 | 50 | 1,545.90 ns | 0.4349 | 0.0038 | - | 3640 B | +| Update_TransientMap_Unicode | 100 | 50 | 646.55 ns | 0.4644 | 0.0057 | - | 3888 B | +| Update_ImmSortedDict | 100 | 50 | 2,010.40 ns | 0.4120 | - | - | 3456 B | +| Update_ExtMap | 100 | 50 | 1,961.80 ns | 0.4234 | - | - | 3568 B | +| Update_ExtHashMap | 100 | 50 | 1,013.87 ns | 0.5188 | 0.0019 | - | 4344 B | +| UpdateSet_ImmDict | 100 | 50 | 1,753.26 ns | 0.5665 | 0.0019 | - | 4752 B | +| UpdateSet_PersistentMap_Standard | 100 | 50 | 3,393.56 ns | 2.4071 | 0.0420 | - | 20160 B | +| UpdateSet_PersistentMap_Unicode | 100 | 50 | 2,495.39 ns | 2.7046 | 0.0496 | - | 22640 B | +| UpdateSet_TransientMap_Standard | 100 | 50 | 2,073.78 ns | 0.4349 | 0.0038 | - | 3640 B | +| UpdateSet_TransientMap_Unicode | 100 | 50 | 1,258.03 ns | 0.4635 | 0.0057 | - | 3888 B | +| UpdateSet_ImmSortedDict | 100 | 50 | 2,456.28 ns | 0.4730 | - | - | 3984 B | +| UpdateSet_ExtMap | 100 | 50 | 2,420.97 ns | 0.5913 | - | - | 4968 B | +| UpdateSet_ExtHashMap | 100 | 50 | 894.89 ns | 0.4902 | 0.0019 | - | 4104 B | +| Iterate_ImmDict | 100 | 50 | 1,311.46 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 100 | 50 | 189.59 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100 | 50 | 482.89 ns | - | - | - | - | +| Iterate_ExtMap | 100 | 50 | 357.71 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 100 | 50 | 1,241.61 ns | 0.2918 | - | - | 2448 B | +| Iterate_PersistentMap_Unicode | 100 | 50 | 195.23 ns | - | - | - | - | +| Remove_ImmDict | 100 | 50 | 1,412.10 ns | 0.4826 | 0.0019 | - | 4048 B | +| Remove_PersistentMap_Standard | 100 | 50 | 3,421.53 ns | 2.3804 | 0.0420 | - | 19920 B | +| Remove_PersistentMap_Unicode | 100 | 50 | 2,640.76 ns | 2.6779 | 0.0458 | - | 22400 B | +| Remove_TransientMap_Standard | 100 | 50 | 2,333.67 ns | 0.4120 | 0.0038 | - | 3448 B | +| Remove_TransientMap_Unicode | 100 | 50 | 1,393.38 ns | 0.4406 | 0.0057 | - | 3688 B | +| Remove_ImmSortedDict | 100 | 50 | 2,034.54 ns | 0.3891 | - | - | 3264 B | +| Remove_ExtMap | 100 | 50 | 2,105.53 ns | 0.4387 | - | - | 3680 B | +| Remove_ExtHashMap | 100 | 50 | 914.57 ns | 0.4635 | 0.0010 | - | 3880 B | +| Build_TransientMap_Standard | 1000 | 8 | 337,908.27 ns | 5.8594 | - | - | 49512 B | +| Build_TransientMap_Unicode | 1000 | 8 | 174,485.27 ns | 7.3242 | 0.7324 | - | 62248 B | +| Build_ImmDict | 1000 | 8 | 258,419.20 ns | 79.1016 | 8.7891 | - | 664448 B | +| Build_ImmSortedDict | 1000 | 8 | 425,563.19 ns | 61.0352 | 4.8828 | - | 513312 B | +| Build_ExtMap | 1000 | 8 | 405,979.91 ns | 79.1016 | 7.3242 | - | 662448 B | +| Build_ExtHashMap | 1000 | 8 | 149,392.88 ns | 70.5566 | 4.6387 | - | 590896 B | +| Retrieve_ImmDict | 1000 | 8 | 1,055.73 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 1000 | 8 | 20,720.92 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 1000 | 8 | 1,920.99 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 1000 | 8 | 21,922.86 ns | - | - | - | - | +| Retrieve_ExtMap | 1000 | 8 | 24,281.46 ns | - | - | - | - | +| Retrieve_ExtHashMap | 1000 | 8 | 2,588.04 ns | - | - | - | - | +| Update_ImmDict | 1000 | 8 | 19,804.39 ns | 7.3853 | 0.3052 | - | 61792 B | +| Update_PersistentMap_Standard | 1000 | 8 | 39,488.86 ns | 24.0479 | 2.3193 | - | 201600 B | +| Update_PersistentMap_Unicode | 1000 | 8 | 28,346.74 ns | 27.0386 | 2.5330 | - | 226400 B | +| Update_TransientMap_Standard | 1000 | 8 | 24,653.11 ns | 4.5471 | 0.4883 | - | 38184 B | +| Update_TransientMap_Unicode | 1000 | 8 | 7,849.22 ns | 4.4250 | 0.4425 | - | 37024 B | +| Update_ImmSortedDict | 1000 | 8 | 31,925.02 ns | 5.7373 | 0.1221 | - | 48288 B | +| Update_ExtMap | 1000 | 8 | 31,958.66 ns | 6.4087 | 0.1831 | - | 53824 B | +| Update_ExtHashMap | 1000 | 8 | 10,986.98 ns | 7.2327 | 0.2441 | - | 60568 B | +| UpdateSet_ImmDict | 1000 | 8 | 23,205.06 ns | 8.4839 | 0.4272 | - | 71072 B | +| UpdateSet_PersistentMap_Standard | 1000 | 8 | 46,680.76 ns | 24.0479 | 2.1973 | - | 201600 B | +| UpdateSet_PersistentMap_Unicode | 1000 | 8 | 33,833.67 ns | 27.0386 | 2.6245 | - | 226400 B | +| UpdateSet_TransientMap_Standard | 1000 | 8 | 30,683.30 ns | 4.2114 | 0.4272 | - | 35368 B | +| UpdateSet_TransientMap_Unicode | 1000 | 8 | 12,650.99 ns | 4.5929 | 0.4883 | - | 38432 B | +| UpdateSet_ImmSortedDict | 1000 | 8 | 37,438.25 ns | 6.3477 | 0.2441 | - | 53232 B | +| UpdateSet_ExtMap | 1000 | 8 | 37,901.44 ns | 7.6904 | 0.3052 | - | 64576 B | +| UpdateSet_ExtHashMap | 1000 | 8 | 18,651.16 ns | 7.4768 | 0.2441 | - | 62768 B | +| Iterate_ImmDict | 1000 | 8 | 12,693.24 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 1000 | 8 | 1,629.13 ns | - | - | - | - | +| Iterate_ImmSortedDict | 1000 | 8 | 4,901.80 ns | - | - | - | - | +| Iterate_ExtMap | 1000 | 8 | 3,685.00 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 1000 | 8 | 14,519.26 ns | 2.6550 | - | - | 22320 B | +| Iterate_PersistentMap_Unicode | 1000 | 8 | 1,612.41 ns | - | - | - | - | +| Remove_ImmDict | 1000 | 8 | 19,718.44 ns | 7.6294 | 0.3052 | - | 63840 B | +| Remove_PersistentMap_Standard | 1000 | 8 | 45,580.39 ns | 23.8037 | 2.0752 | - | 199200 B | +| Remove_PersistentMap_Unicode | 1000 | 8 | 29,970.54 ns | 26.7639 | 2.4414 | - | 224000 B | +| Remove_TransientMap_Standard | 1000 | 8 | 29,451.58 ns | 4.0894 | 0.3967 | - | 34424 B | +| Remove_TransientMap_Unicode | 1000 | 8 | 14,136.83 ns | 3.7994 | 0.3662 | - | 31848 B | +| Remove_ImmSortedDict | 1000 | 8 | 33,400.75 ns | 5.6763 | 0.1221 | - | 47616 B | +| Remove_ExtMap | 1000 | 8 | 36,167.45 ns | 6.8970 | 0.1831 | - | 57744 B | +| Remove_ExtHashMap | 1000 | 8 | 12,681.31 ns | 8.0414 | 0.2289 | - | 67288 B | +| Build_TransientMap_Standard | 1000 | 50 | 342,796.72 ns | 5.3711 | - | - | 44992 B | +| Build_TransientMap_Unicode | 1000 | 50 | 173,477.79 ns | 7.0801 | 0.4883 | - | 59368 B | +| Build_ImmDict | 1000 | 50 | 275,481.11 ns | 79.1016 | 9.2773 | - | 662528 B | +| Build_ImmSortedDict | 1000 | 50 | 417,349.29 ns | 61.0352 | 5.3711 | - | 511344 B | +| Build_ExtMap | 1000 | 50 | 406,348.46 ns | 78.1250 | 7.3242 | - | 656288 B | +| Build_ExtHashMap | 1000 | 50 | 180,890.87 ns | 69.5801 | 4.8828 | - | 582472 B | +| Retrieve_ImmDict | 1000 | 50 | 3,137.95 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 1000 | 50 | 20,432.33 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 1000 | 50 | 2,096.52 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 1000 | 50 | 20,986.51 ns | - | - | - | - | +| Retrieve_ExtMap | 1000 | 50 | 23,287.17 ns | - | - | - | - | +| Retrieve_ExtHashMap | 1000 | 50 | 4,776.52 ns | - | - | - | - | +| Update_ImmDict | 1000 | 50 | 25,749.96 ns | 7.5989 | 0.3052 | - | 63776 B | +| Update_PersistentMap_Standard | 1000 | 50 | 39,878.09 ns | 24.0479 | 2.0752 | - | 201600 B | +| Update_PersistentMap_Unicode | 1000 | 50 | 28,771.02 ns | 27.0386 | 2.2888 | - | 226400 B | +| Update_TransientMap_Standard | 1000 | 50 | 24,641.84 ns | 3.8757 | 0.3357 | - | 32552 B | +| Update_TransientMap_Unicode | 1000 | 50 | 7,785.56 ns | 4.0894 | 0.3967 | - | 34208 B | +| Update_ImmSortedDict | 1000 | 50 | 32,625.21 ns | 5.6763 | 0.1221 | - | 47952 B | +| Update_ExtMap | 1000 | 50 | 33,656.18 ns | 6.5308 | 0.1831 | - | 54720 B | +| Update_ExtHashMap | 1000 | 50 | 13,197.36 ns | 7.1869 | 0.2289 | - | 60168 B | +| UpdateSet_ImmDict | 1000 | 50 | 24,838.46 ns | 8.2397 | 0.3967 | - | 68960 B | +| UpdateSet_PersistentMap_Standard | 1000 | 50 | 47,257.42 ns | 24.1699 | 2.0142 | - | 202504 B | +| UpdateSet_PersistentMap_Unicode | 1000 | 50 | 34,871.45 ns | 27.2217 | 2.6245 | - | 227840 B | +| UpdateSet_TransientMap_Standard | 1000 | 50 | 30,705.12 ns | 3.9673 | 0.3662 | - | 33456 B | +| UpdateSet_TransientMap_Unicode | 1000 | 50 | 13,306.26 ns | 4.4250 | 0.4578 | - | 37056 B | +| UpdateSet_ImmSortedDict | 1000 | 50 | 37,358.03 ns | 6.2866 | 0.1831 | - | 53088 B | +| UpdateSet_ExtMap | 1000 | 50 | 39,040.99 ns | 7.8735 | 0.3052 | - | 66200 B | +| UpdateSet_ExtHashMap | 1000 | 50 | 18,993.92 ns | 7.9346 | 0.2747 | - | 66584 B | +| Iterate_ImmDict | 1000 | 50 | 12,796.55 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 1000 | 50 | 1,601.24 ns | - | - | - | - | +| Iterate_ImmSortedDict | 1000 | 50 | 4,897.01 ns | - | - | - | - | +| Iterate_ExtMap | 1000 | 50 | 3,590.09 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 1000 | 50 | 14,570.64 ns | 2.6550 | - | - | 22320 B | +| Iterate_PersistentMap_Unicode | 1000 | 50 | 1,624.65 ns | - | - | - | - | +| Remove_ImmDict | 1000 | 50 | 22,247.23 ns | 7.7209 | 0.3357 | - | 64800 B | +| Remove_PersistentMap_Standard | 1000 | 50 | 47,236.28 ns | 23.8037 | 2.0752 | - | 199200 B | +| Remove_PersistentMap_Unicode | 1000 | 50 | 30,337.50 ns | 26.7639 | 2.2583 | - | 224000 B | +| Remove_TransientMap_Standard | 1000 | 50 | 30,224.03 ns | 3.4180 | 0.3052 | - | 28792 B | +| Remove_TransientMap_Unicode | 1000 | 50 | 13,980.33 ns | 3.2959 | 0.2899 | - | 27624 B | +| Remove_ImmSortedDict | 1000 | 50 | 34,150.76 ns | 5.6763 | 0.1221 | - | 47664 B | +| Remove_ExtMap | 1000 | 50 | 36,453.25 ns | 6.9580 | 0.2441 | - | 58640 B | +| Remove_ExtHashMap | 1000 | 50 | 14,511.93 ns | 7.9041 | 0.2136 | - | 66176 B | +| Build_TransientMap_Standard | 10000 | 8 | 4,972,737.60 ns | 46.8750 | 7.8125 | - | 453272 B | +| Build_TransientMap_Unicode | 10000 | 8 | 2,273,723.34 ns | 66.4063 | 19.5313 | - | 576584 B | +| Build_ImmDict | 10000 | 8 | 4,474,574.70 ns | 1039.0625 | 507.8125 | - | 8751104 B | +| Build_ImmSortedDict | 10000 | 8 | 6,548,873.72 ns | 804.6875 | 281.2500 | - | 6767232 B | +| Build_ExtMap | 10000 | 8 | 6,462,311.12 ns | 1015.6250 | 437.5000 | - | 8542480 B | +| Build_ExtHashMap | 10000 | 8 | 1,921,265.07 ns | 945.3125 | 326.1719 | - | 7907368 B | +| Retrieve_ImmDict | 10000 | 8 | 21,054.43 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 10000 | 8 | 386,477.89 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 10000 | 8 | 32,919.53 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 10000 | 8 | 383,296.28 ns | - | - | - | - | +| Retrieve_ExtMap | 10000 | 8 | 447,733.80 ns | - | - | - | - | +| Retrieve_ExtHashMap | 10000 | 8 | 38,576.71 ns | - | - | - | - | +| Update_ImmDict | 10000 | 8 | 395,715.60 ns | 101.0742 | 27.3438 | - | 848128 B | +| Update_PersistentMap_Standard | 10000 | 8 | 662,654.83 ns | 305.6641 | 131.8359 | - | 2560000 B | +| Update_PersistentMap_Unicode | 10000 | 8 | 425,935.07 ns | 366.2109 | 160.1563 | - | 3064000 B | +| Update_TransientMap_Standard | 10000 | 8 | 442,700.87 ns | 41.0156 | 14.1602 | - | 346280 B | +| Update_TransientMap_Unicode | 10000 | 8 | 152,961.90 ns | 40.7715 | 16.6016 | - | 341792 B | +| Update_ImmSortedDict | 10000 | 8 | 599,396.52 ns | 76.1719 | 15.6250 | - | 640368 B | +| Update_ExtMap | 10000 | 8 | 565,510.82 ns | 87.8906 | 22.4609 | - | 735640 B | +| Update_ExtHashMap | 10000 | 8 | 165,164.46 ns | 102.5391 | 20.9961 | - | 859160 B | +| UpdateSet_ImmDict | 10000 | 8 | 436,729.87 ns | 108.3984 | 29.2969 | - | 908672 B | +| UpdateSet_PersistentMap_Standard | 10000 | 8 | 751,649.78 ns | 306.6406 | 131.8359 | - | 2572328 B | +| UpdateSet_PersistentMap_Unicode | 10000 | 8 | 499,253.41 ns | 368.1641 | 154.2969 | - | 3086432 B | +| UpdateSet_TransientMap_Standard | 10000 | 8 | 538,898.72 ns | 41.9922 | 16.6016 | - | 357200 B | +| UpdateSet_TransientMap_Unicode | 10000 | 8 | 227,890.12 ns | 43.7012 | 16.3574 | - | 365632 B | +| UpdateSet_ImmSortedDict | 10000 | 8 | 650,234.49 ns | 82.0313 | 19.5313 | - | 687648 B | +| UpdateSet_ExtMap | 10000 | 8 | 641,265.51 ns | 99.6094 | 25.3906 | - | 833752 B | +| UpdateSet_ExtHashMap | 10000 | 8 | 194,749.46 ns | 104.9805 | 23.9258 | - | 878808 B | +| Iterate_ImmDict | 10000 | 8 | 174,946.48 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 10000 | 8 | 17,152.00 ns | - | - | - | - | +| Iterate_ImmSortedDict | 10000 | 8 | 52,817.11 ns | - | - | - | - | +| Iterate_ExtMap | 10000 | 8 | 66,083.05 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 10000 | 8 | 174,496.27 ns | 20.2637 | - | - | 169704 B | +| Iterate_PersistentMap_Unicode | 10000 | 8 | 17,945.71 ns | - | - | - | - | +| Remove_ImmDict | 10000 | 8 | 402,693.02 ns | 102.0508 | 23.4375 | - | 855488 B | +| Remove_PersistentMap_Standard | 10000 | 8 | 731,170.91 ns | 302.7344 | 117.1875 | - | 2536000 B | +| Remove_PersistentMap_Unicode | 10000 | 8 | 493,821.11 ns | 363.2813 | 180.6641 | - | 3041408 B | +| Remove_TransientMap_Standard | 10000 | 8 | 511,709.67 ns | 37.1094 | 13.6719 | - | 316696 B | +| Remove_TransientMap_Unicode | 10000 | 8 | 223,881.92 ns | 38.0859 | 15.1367 | - | 319240 B | +| Remove_ImmSortedDict | 10000 | 8 | 600,831.24 ns | 77.1484 | 15.6250 | - | 652944 B | +| Remove_ExtMap | 10000 | 8 | 617,128.15 ns | 91.7969 | 20.5078 | - | 774000 B | +| Remove_ExtHashMap | 10000 | 8 | 179,929.34 ns | 104.4922 | 22.4609 | - | 874984 B | +| Build_TransientMap_Standard | 10000 | 50 | 5,124,826.21 ns | 46.8750 | 7.8125 | - | 453272 B | +| Build_TransientMap_Unicode | 10000 | 50 | 2,251,023.03 ns | 66.4063 | 23.4375 | - | 567112 B | +| Build_ImmDict | 10000 | 50 | 4,751,856.80 ns | 1046.8750 | 507.8125 | - | 8757376 B | +| Build_ImmSortedDict | 10000 | 50 | 6,556,900.60 ns | 804.6875 | 265.6250 | - | 6751584 B | +| Build_ExtMap | 10000 | 50 | 6,985,344.36 ns | 1015.6250 | 429.6875 | - | 8544720 B | +| Build_ExtHashMap | 10000 | 50 | 2,218,415.86 ns | 945.3125 | 308.5938 | - | 7925728 B | +| Retrieve_ImmDict | 10000 | 50 | 46,389.46 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 10000 | 50 | 397,729.79 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 10000 | 50 | 33,945.57 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 10000 | 50 | 385,767.81 ns | - | - | - | - | +| Retrieve_ExtMap | 10000 | 50 | 426,750.47 ns | - | - | - | - | +| Retrieve_ExtHashMap | 10000 | 50 | 61,891.51 ns | - | - | - | - | +| Update_ImmDict | 10000 | 50 | 419,181.33 ns | 101.0742 | 27.3438 | - | 848768 B | +| Update_PersistentMap_Standard | 10000 | 50 | 674,365.10 ns | 305.6641 | 125.9766 | - | 2560000 B | +| Update_PersistentMap_Unicode | 10000 | 50 | 423,829.00 ns | 366.2109 | 158.2031 | - | 3064000 B | +| Update_TransientMap_Standard | 10000 | 50 | 455,884.40 ns | 40.5273 | 13.6719 | - | 339240 B | +| Update_TransientMap_Unicode | 10000 | 50 | 151,927.19 ns | 39.5508 | 13.9160 | - | 331136 B | +| Update_ImmSortedDict | 10000 | 50 | 599,787.55 ns | 76.1719 | 14.6484 | - | 641616 B | +| Update_ExtMap | 10000 | 50 | 576,111.14 ns | 86.9141 | 20.5078 | - | 732896 B | +| Update_ExtHashMap | 10000 | 50 | 186,299.39 ns | 102.5391 | 20.2637 | - | 859392 B | +| UpdateSet_ImmDict | 10000 | 50 | 459,230.81 ns | 108.3984 | 29.2969 | - | 908672 B | +| UpdateSet_PersistentMap_Standard | 10000 | 50 | 758,257.06 ns | 307.6172 | 133.7891 | - | 2575040 B | +| UpdateSet_PersistentMap_Unicode | 10000 | 50 | 499,618.94 ns | 368.1641 | 152.3438 | - | 3085600 B | +| UpdateSet_TransientMap_Standard | 10000 | 50 | 552,006.66 ns | 41.9922 | 15.6250 | - | 355688 B | +| UpdateSet_TransientMap_Unicode | 10000 | 50 | 225,461.43 ns | 42.2363 | 15.8691 | - | 354144 B | +| UpdateSet_ImmSortedDict | 10000 | 50 | 660,239.03 ns | 82.0313 | 19.5313 | - | 691728 B | +| UpdateSet_ExtMap | 10000 | 50 | 642,281.65 ns | 98.6328 | 23.4375 | - | 829552 B | +| UpdateSet_ExtHashMap | 10000 | 50 | 213,376.13 ns | 104.9805 | 23.4375 | - | 878248 B | +| Iterate_ImmDict | 10000 | 50 | 181,714.98 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 10000 | 50 | 17,120.93 ns | - | - | - | - | +| Iterate_ImmSortedDict | 10000 | 50 | 54,752.59 ns | - | - | - | - | +| Iterate_ExtMap | 10000 | 50 | 66,116.25 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 10000 | 50 | 175,406.16 ns | 19.7754 | - | - | 166824 B | +| Iterate_PersistentMap_Unicode | 10000 | 50 | 17,271.03 ns | - | - | - | - | +| Remove_ImmDict | 10000 | 50 | 424,174.08 ns | 102.0508 | 23.9258 | - | 854784 B | +| Remove_PersistentMap_Standard | 10000 | 50 | 739,059.87 ns | 302.7344 | 119.1406 | - | 2538816 B | +| Remove_PersistentMap_Unicode | 10000 | 50 | 491,577.81 ns | 363.2813 | 158.2031 | - | 3042816 B | +| Remove_TransientMap_Standard | 10000 | 50 | 522,589.23 ns | 38.0859 | 12.6953 | - | 319512 B | +| Remove_TransientMap_Unicode | 10000 | 50 | 223,052.49 ns | 37.3535 | 13.1836 | - | 312808 B | +| Remove_ImmSortedDict | 10000 | 50 | 612,756.00 ns | 77.1484 | 15.6250 | - | 651840 B | +| Remove_ExtMap | 10000 | 50 | 622,977.64 ns | 91.7969 | 21.4844 | - | 771256 B | +| Remove_ExtHashMap | 10000 | 50 | 202,442.11 ns | 104.0039 | 20.9961 | - | 871064 B | +| Build_TransientMap_Standard | 100000 | 8 | 72,441,876.47 ns | 428.5714 | 285.7143 | - | 4524208 B | +| Build_TransientMap_Unicode | 100000 | 8 | 39,704,110.55 ns | 692.3077 | 538.4615 | - | 5798728 B | +| Build_ImmDict | 100000 | 8 | 92,116,430.52 ns | 13166.6667 | 4833.3333 | 166.6667 | 109376373 B | +| Build_ImmSortedDict | 100000 | 8 | 119,704,496.44 ns | 10000.0000 | 3800.0000 | - | 83946432 B | +| Build_ExtMap | 100000 | 8 | 119,087,830.57 ns | 12400.0000 | 5600.0000 | - | 104660688 B | +| Build_ExtHashMap | 100000 | 8 | 45,457,387.28 ns | 12000.0000 | 2583.3333 | 83.3333 | 99806394 B | +| Retrieve_ImmDict | 100000 | 8 | 1,516,808.05 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100000 | 8 | 6,444,842.15 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 100000 | 8 | 1,351,906.08 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 100000 | 8 | 6,176,362.41 ns | - | - | - | - | +| Retrieve_ExtMap | 100000 | 8 | 6,613,900.92 ns | - | - | - | - | +| Retrieve_ExtHashMap | 100000 | 8 | 1,046,707.73 ns | - | - | - | - | +| Update_ImmDict | 100000 | 8 | 8,149,123.59 ns | 1265.6250 | 1046.8750 | - | 10618880 B | +| Update_PersistentMap_Standard | 100000 | 8 | 14,827,486.05 ns | 3750.0000 | 2953.1250 | 46.8750 | 31040047 B | +| Update_PersistentMap_Unicode | 100000 | 8 | 11,607,930.86 ns | 4671.8750 | 2500.0000 | 62.5000 | 38640058 B | +| Update_TransientMap_Standard | 100000 | 8 | 7,358,177.48 ns | 406.2500 | 335.9375 | - | 3415688 B | +| Update_TransientMap_Unicode | 100000 | 8 | 3,826,038.54 ns | 414.0625 | 351.5625 | - | 3487168 B | +| Update_ImmSortedDict | 100000 | 8 | 10,318,908.01 ns | 953.1250 | 781.2500 | - | 8020080 B | +| Update_ExtMap | 100000 | 8 | 10,354,686.58 ns | 1093.7500 | 937.5000 | - | 9246400 B | +| Update_ExtHashMap | 100000 | 8 | 3,752,714.02 ns | 1269.5313 | 988.2813 | - | 10626504 B | +| UpdateSet_ImmDict | 100000 | 8 | 8,658,752.54 ns | 1343.7500 | 1046.8750 | - | 11262464 B | +| UpdateSet_PersistentMap_Standard | 100000 | 8 | 16,011,001.98 ns | 3750.0000 | 2593.7500 | 31.2500 | 31143252 B | +| UpdateSet_PersistentMap_Unicode | 100000 | 8 | 12,524,719.72 ns | 4687.5000 | 2281.2500 | 62.5000 | 38809433 B | +| UpdateSet_TransientMap_Standard | 100000 | 8 | 8,280,433.69 ns | 406.2500 | 328.1250 | - | 3514688 B | +| UpdateSet_TransientMap_Unicode | 100000 | 8 | 4,465,123.76 ns | 429.6875 | 382.8125 | - | 3636832 B | +| UpdateSet_ImmSortedDict | 100000 | 8 | 11,403,173.00 ns | 1015.6250 | 875.0000 | - | 8495856 B | +| UpdateSet_ExtMap | 100000 | 8 | 11,808,286.34 ns | 1218.7500 | 1015.6250 | - | 10287328 B | +| UpdateSet_ExtHashMap | 100000 | 8 | 4,073,301.12 ns | 1281.2500 | 992.1875 | - | 10759216 B | +| Iterate_ImmDict | 100000 | 8 | 2,210,260.69 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 100000 | 8 | 221,859.64 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100000 | 8 | 712,098.18 ns | - | - | - | - | +| Iterate_ExtMap | 100000 | 8 | 1,122,895.56 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 100000 | 8 | 2,722,789.29 ns | 273.4375 | - | - | 2314696 B | +| Iterate_PersistentMap_Unicode | 100000 | 8 | 235,131.71 ns | - | - | - | - | +| Remove_ImmDict | 100000 | 8 | 7,950,218.64 ns | 1281.2500 | 1125.0000 | - | 10756416 B | +| Remove_PersistentMap_Standard | 100000 | 8 | 15,366,222.73 ns | 3718.7500 | 3328.1250 | 46.8750 | 30821152 B | +| Remove_PersistentMap_Unicode | 100000 | 8 | 12,092,756.69 ns | 4656.2500 | 2875.0000 | 62.5000 | 38412727 B | +| Remove_TransientMap_Standard | 100000 | 8 | 7,973,868.64 ns | 375.0000 | 312.5000 | - | 3174328 B | +| Remove_TransientMap_Unicode | 100000 | 8 | 3,161,959.43 ns | 382.8125 | 324.2188 | - | 3226088 B | +| Remove_ImmSortedDict | 100000 | 8 | 10,396,202.26 ns | 953.1250 | 781.2500 | - | 8031888 B | +| Remove_ExtMap | 100000 | 8 | 11,561,856.15 ns | 1140.6250 | 937.5000 | - | 9630448 B | +| Remove_ExtHashMap | 100000 | 8 | 4,009,364.62 ns | 1265.6250 | 921.8750 | - | 10607248 B | +| Build_TransientMap_Standard | 100000 | 50 | 81,360,172.13 ns | 428.5714 | 285.7143 | - | 4511632 B | +| Build_TransientMap_Unicode | 100000 | 50 | 41,339,351.49 ns | 666.6667 | 500.0000 | - | 5763784 B | +| Build_ImmDict | 100000 | 50 | 97,054,583.11 ns | 13166.6667 | 4833.3333 | 166.6667 | 109218477 B | +| Build_ImmSortedDict | 100000 | 50 | 129,585,060.60 ns | 10000.0000 | 4000.0000 | - | 83731632 B | +| Build_ExtMap | 100000 | 50 | 130,490,141.33 ns | 12250.0000 | 5250.0000 | - | 104371560 B | +| Build_ExtHashMap | 100000 | 50 | 49,945,252.31 ns | 11900.0000 | 2400.0000 | - | 99832472 B | +| Retrieve_ImmDict | 100000 | 50 | 1,933,934.21 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100000 | 50 | 7,474,711.16 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 100000 | 50 | 1,439,009.47 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 100000 | 50 | 7,341,270.59 ns | - | - | - | - | +| Retrieve_ExtMap | 100000 | 50 | 7,144,334.50 ns | - | - | - | - | +| Retrieve_ExtHashMap | 100000 | 50 | 1,359,724.09 ns | - | - | - | - | +| Update_ImmDict | 100000 | 50 | 8,224,368.98 ns | 1265.6250 | 1062.5000 | - | 10585792 B | +| Update_PersistentMap_Standard | 100000 | 50 | 16,195,436.66 ns | 3718.7500 | 2875.0000 | 31.2500 | 31040034 B | +| Update_PersistentMap_Unicode | 100000 | 50 | 11,872,157.79 ns | 4656.2500 | 2765.6250 | 46.8750 | 38640047 B | +| Update_TransientMap_Standard | 100000 | 50 | 8,421,668.39 ns | 406.2500 | 343.7500 | - | 3414056 B | +| Update_TransientMap_Unicode | 100000 | 50 | 3,920,098.30 ns | 406.2500 | 335.9375 | - | 3440288 B | +| Update_ImmSortedDict | 100000 | 50 | 11,196,973.30 ns | 953.1250 | 781.2500 | - | 8012928 B | +| Update_ExtMap | 100000 | 50 | 10,904,107.16 ns | 1093.7500 | 906.2500 | - | 9233632 B | +| Update_ExtHashMap | 100000 | 50 | 4,099,435.72 ns | 1265.6250 | 976.5625 | - | 10625992 B | +| UpdateSet_ImmDict | 100000 | 50 | 8,815,363.39 ns | 1343.7500 | 1031.2500 | - | 11286912 B | +| UpdateSet_PersistentMap_Standard | 100000 | 50 | 17,059,344.47 ns | 3718.7500 | 2562.5000 | - | 31141992 B | +| UpdateSet_PersistentMap_Unicode | 100000 | 50 | 13,129,653.12 ns | 4687.5000 | 2437.5000 | 46.8750 | 38835954 B | +| UpdateSet_TransientMap_Standard | 100000 | 50 | 9,618,034.32 ns | 406.2500 | 328.1250 | - | 3509008 B | +| UpdateSet_TransientMap_Unicode | 100000 | 50 | 4,850,003.99 ns | 429.6875 | 359.3750 | - | 3630560 B | +| UpdateSet_ImmSortedDict | 100000 | 50 | 12,656,577.42 ns | 1015.6250 | 890.6250 | - | 8501136 B | +| UpdateSet_ExtMap | 100000 | 50 | 12,813,242.59 ns | 1218.7500 | 1015.6250 | - | 10265544 B | +| UpdateSet_ExtHashMap | 100000 | 50 | 4,430,259.78 ns | 1281.2500 | 1000.0000 | - | 10765376 B | +| Iterate_ImmDict | 100000 | 50 | 2,381,194.68 ns | - | - | - | 96 B | +| Iterate_PersistentMap_Standard | 100000 | 50 | 211,225.33 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100000 | 50 | 713,700.90 ns | - | - | - | - | +| Iterate_ExtMap | 100000 | 50 | 1,157,631.41 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 100000 | 50 | 2,676,695.95 ns | 273.4375 | - | - | 2310024 B | +| Iterate_PersistentMap_Unicode | 100000 | 50 | 212,687.91 ns | - | - | - | - | +| Remove_ImmDict | 100000 | 50 | 8,177,782.10 ns | 1281.2500 | 984.3750 | - | 10749824 B | +| Remove_PersistentMap_Standard | 100000 | 50 | 16,635,857.83 ns | 3687.5000 | 3343.7500 | 31.2500 | 30814118 B | +| Remove_PersistentMap_Unicode | 100000 | 50 | 12,383,735.25 ns | 4625.0000 | 2859.3750 | 46.8750 | 38402877 B | +| Remove_TransientMap_Standard | 100000 | 50 | 8,797,592.89 ns | 375.0000 | 312.5000 | - | 3167064 B | +| Remove_TransientMap_Unicode | 100000 | 50 | 3,335,966.12 ns | 382.8125 | 312.5000 | - | 3205960 B | +| Remove_ImmSortedDict | 100000 | 50 | 11,619,906.87 ns | 953.1250 | 781.2500 | - | 8032656 B | +| Remove_ExtMap | 100000 | 50 | 12,051,548.01 ns | 1140.6250 | 921.8750 | - | 9617232 B | +| Remove_ExtHashMap | 100000 | 50 | 4,258,918.68 ns | 1265.6250 | 929.6875 | - | 10612072 B | #+end_src From a54b17167eb17d8e7dc4b8b17d441131efade3ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Thu, 23 Apr 2026 17:08:12 +0200 Subject: [PATCH 12/22] Fixed the unicode strategy to avoid boxing --- PersistentMap/KeyStrategies.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/PersistentMap/KeyStrategies.cs b/PersistentMap/KeyStrategies.cs index 2bc14a6..f53d5a2 100644 --- a/PersistentMap/KeyStrategies.cs +++ b/PersistentMap/KeyStrategies.cs @@ -16,7 +16,6 @@ public interface IKeyStrategy bool UsesPrefixes => true; - // bool IsLossless => false; bool UseBinarySearch => false; } @@ -26,6 +25,10 @@ public interface IKeyStrategy public struct UnicodeStrategy : IKeyStrategy { + + bool UsesPrefixes => true; + bool UseBinarySearch => false; + bool IsLossLess => false; [MethodImpl(MethodImplOptions.AggressiveInlining)] public int Compare(string? x, string? y) => string.CompareOrdinal(x, y); @@ -58,7 +61,6 @@ public struct UnicodeStrategy : IKeyStrategy return packed ^ unchecked((long)0x8080808080808080); } - public bool UsesPrefixes => true; } From c363cae1369efa32b39856b34e025869738ad4d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Fri, 24 Apr 2026 19:42:53 +0200 Subject: [PATCH 13/22] make sure standardstrategy is inlined huge performance benefits. no seriously. quite spectacular --- .../KeyStrategies/StandardStrategy.cs | 16 ++++++++++ benchmarks/MyBenchMarks/StringBenchmarks.cs | 30 +++++++++++-------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/PersistentMap/KeyStrategies/StandardStrategy.cs b/PersistentMap/KeyStrategies/StandardStrategy.cs index 77f6701..94cc05c 100644 --- a/PersistentMap/KeyStrategies/StandardStrategy.cs +++ b/PersistentMap/KeyStrategies/StandardStrategy.cs @@ -35,3 +35,19 @@ public readonly struct StandardStrategy : IKeyStrategy return _comparer.Compare(x, y); } } +public readonly struct StandardStrategy2 : IKeyStrategy + where TComparer : struct, IComparer + { + private readonly TComparer _comparer; + + public StandardStrategy2(TComparer comparer) => _comparer = comparer; + + public bool UsesPrefixes => false; + public bool UseBinarySearch => true; + +[MethodImpl(MethodImplOptions.AggressiveInlining)] +public int Compare(K x, K y) => _comparer.Compare(x, y); + + public long GetPrefix(K key) => 0; + } + diff --git a/benchmarks/MyBenchMarks/StringBenchmarks.cs b/benchmarks/MyBenchMarks/StringBenchmarks.cs index 302e5d1..6f1dc30 100644 --- a/benchmarks/MyBenchMarks/StringBenchmarks.cs +++ b/benchmarks/MyBenchMarks/StringBenchmarks.cs @@ -5,9 +5,15 @@ using System.Linq; using BenchmarkDotNet.Attributes; using LanguageExt; using PersistentMap; - +using System.Runtime.CompilerServices; namespace MapBenchmarks; +public readonly struct OrdinalComparer : IComparer +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Compare(string? x, string? y) => string.CompareOrdinal(x, y); +} + [MemoryDiagnoser] public class StringMapBenchmarks { @@ -28,10 +34,10 @@ public class StringMapBenchmarks private LanguageExt.Map _extMap; private LanguageExt.HashMap _extHashMap; - private PersistentMap> _persistentMapStandard; + private PersistentMap> _persistentMapStandard; private PersistentMap _persistentMapUnicode; - private readonly StandardStrategy _stdStrategy = new StandardStrategy(); + private readonly StandardStrategy2 _stdStrategy = new StandardStrategy2(new OrdinalComparer()); private readonly UnicodeStrategy _uniStrategy = new UnicodeStrategy(); [GlobalSetup] @@ -71,7 +77,7 @@ public class StringMapBenchmarks _extHashMap = _extHashMap.AddOrUpdate(_allKeys[i], i); } - var transStd = BaseOrderedMap>.CreateTransient(_stdStrategy); + var transStd = BaseOrderedMap>.CreateTransient(_stdStrategy); var transUni = BaseOrderedMap.CreateTransient(_uniStrategy); for (int i = 0; i < _allKeys.Length; i++) { @@ -91,9 +97,9 @@ public class StringMapBenchmarks // --- 1. BUILD --- [Benchmark] - public PersistentMap> Build_TransientMap_Standard() + public PersistentMap> Build_TransientMap_Standard() { - var map = BaseOrderedMap>.CreateTransient(_stdStrategy); + var map = BaseOrderedMap>.CreateTransient(_stdStrategy); for (int i = 0; i < _allKeys.Length; i++) map.Set(_allKeys[i], i); return map.ToPersistent(); } @@ -207,7 +213,7 @@ public class StringMapBenchmarks } [Benchmark] - public PersistentMap> Update_PersistentMap_Standard() + public PersistentMap> Update_PersistentMap_Standard() { var map = _persistentMapStandard; foreach (var k in _updateKeys) map = map.Set(k, 999); @@ -222,7 +228,7 @@ public class StringMapBenchmarks } [Benchmark] - public PersistentMap> Update_TransientMap_Standard() + public PersistentMap> Update_TransientMap_Standard() { var transient = _persistentMapStandard.ToTransient(); foreach (var k in _updateKeys) transient.Set(k, 999); @@ -272,7 +278,7 @@ public class StringMapBenchmarks } [Benchmark] - public PersistentMap> UpdateSet_PersistentMap_Standard() + public PersistentMap> UpdateSet_PersistentMap_Standard() { var map = _persistentMapStandard; foreach (var k in _mixedKeys) map = map.Set(k, 999); @@ -288,7 +294,7 @@ public class StringMapBenchmarks } [Benchmark] - public PersistentMap> UpdateSet_TransientMap_Standard() + public PersistentMap> UpdateSet_TransientMap_Standard() { var transient = _persistentMapStandard.ToTransient(); foreach (var k in _mixedKeys) transient.Set(k, 999); @@ -388,7 +394,7 @@ public class StringMapBenchmarks } [Benchmark] - public PersistentMap> Remove_PersistentMap_Standard() + public PersistentMap> Remove_PersistentMap_Standard() { var map = _persistentMapStandard; foreach (var k in _removeKeys) map = map.Remove(k); @@ -404,7 +410,7 @@ public class StringMapBenchmarks } [Benchmark] - public PersistentMap> Remove_TransientMap_Standard() + public PersistentMap> Remove_TransientMap_Standard() { var transient = _persistentMapStandard.ToTransient(); foreach (var k in _removeKeys) transient.Remove(k); From d21b9cbe069dca5d7cc525f0f9b2e7e08f070ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Fri, 24 Apr 2026 20:29:51 +0200 Subject: [PATCH 14/22] addee retrieve bemchmarks tobreadme. more is comibg. amazing speedups. --- PersistentMap/Readme.org | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/PersistentMap/Readme.org b/PersistentMap/Readme.org index 90acd48..bb278dd 100644 --- a/PersistentMap/Readme.org +++ b/PersistentMap/Readme.org @@ -282,7 +282,7 @@ These benchmarks act like above, but do not insert keys in a specific order. Sor | Build_ExtMap | 100 | 8 | 22,192.67 ns | 5.6152 | 0.0610 | - | 47104 B | | Build_ExtHashMap | 100 | 8 | 10,569.35 ns | 4.5013 | 0.0305 | - | 37680 B | | Retrieve_ImmDict | 100 | 8 | 79.60 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 100 | 8 | 1,009.56 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100 | 8 | 112.5 ns | - | - | - | - | | Retrieve_PersistentMap_Unicode | 100 | 8 | 148.82 ns | - | - | - | - | | Retrieve_ImmSortedDict | 100 | 8 | 1,198.42 ns | - | - | - | - | | Retrieve_ExtMap | 100 | 8 | 1,305.45 ns | - | - | - | - | @@ -325,6 +325,7 @@ These benchmarks act like above, but do not insert keys in a specific order. Sor | Build_ExtHashMap | 100 | 50 | 12,107.35 ns | 4.0894 | 0.0305 | - | 34280 B | | Retrieve_ImmDict | 100 | 50 | 286.13 ns | - | - | - | - | | Retrieve_PersistentMap_Standard | 100 | 50 | 984.90 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100 | 50 | 105.3 ns | - | - | - | - | | Retrieve_PersistentMap_Unicode | 100 | 50 | 151.45 ns | - | - | - | - | | Retrieve_ImmSortedDict | 100 | 50 | 1,308.09 ns | - | - | - | - | | Retrieve_ExtMap | 100 | 50 | 1,164.25 ns | - | - | - | - | @@ -366,7 +367,7 @@ These benchmarks act like above, but do not insert keys in a specific order. Sor | Build_ExtMap | 1000 | 8 | 405,979.91 ns | 79.1016 | 7.3242 | - | 662448 B | | Build_ExtHashMap | 1000 | 8 | 149,392.88 ns | 70.5566 | 4.6387 | - | 590896 B | | Retrieve_ImmDict | 1000 | 8 | 1,055.73 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 1000 | 8 | 20,720.92 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 1000 | 8 | 1,778.70 ns | - | - | - | - | | Retrieve_PersistentMap_Unicode | 1000 | 8 | 1,920.99 ns | - | - | - | - | | Retrieve_ImmSortedDict | 1000 | 8 | 21,922.86 ns | - | - | - | - | | Retrieve_ExtMap | 1000 | 8 | 24,281.46 ns | - | - | - | - | @@ -408,7 +409,7 @@ These benchmarks act like above, but do not insert keys in a specific order. Sor | Build_ExtMap | 1000 | 50 | 406,348.46 ns | 78.1250 | 7.3242 | - | 656288 B | | Build_ExtHashMap | 1000 | 50 | 180,890.87 ns | 69.5801 | 4.8828 | - | 582472 B | | Retrieve_ImmDict | 1000 | 50 | 3,137.95 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 1000 | 50 | 20,432.33 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 1000 | 50 | 1,798.00 ns | - | - | - | - | | Retrieve_PersistentMap_Unicode | 1000 | 50 | 2,096.52 ns | - | - | - | - | | Retrieve_ImmSortedDict | 1000 | 50 | 20,986.51 ns | - | - | - | - | | Retrieve_ExtMap | 1000 | 50 | 23,287.17 ns | - | - | - | - | @@ -451,6 +452,7 @@ These benchmarks act like above, but do not insert keys in a specific order. Sor | Build_ExtHashMap | 10000 | 8 | 1,921,265.07 ns | 945.3125 | 326.1719 | - | 7907368 B | | Retrieve_ImmDict | 10000 | 8 | 21,054.43 ns | - | - | - | - | | Retrieve_PersistentMap_Standard | 10000 | 8 | 386,477.89 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 10000 | 8 | 46,945.00 ns | - | - | - | - | | Retrieve_PersistentMap_Unicode | 10000 | 8 | 32,919.53 ns | - | - | - | - | | Retrieve_ImmSortedDict | 10000 | 8 | 383,296.28 ns | - | - | - | - | | Retrieve_ExtMap | 10000 | 8 | 447,733.80 ns | - | - | - | - | @@ -492,7 +494,7 @@ These benchmarks act like above, but do not insert keys in a specific order. Sor | Build_ExtMap | 10000 | 50 | 6,985,344.36 ns | 1015.6250 | 429.6875 | - | 8544720 B | | Build_ExtHashMap | 10000 | 50 | 2,218,415.86 ns | 945.3125 | 308.5938 | - | 7925728 B | | Retrieve_ImmDict | 10000 | 50 | 46,389.46 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 10000 | 50 | 397,729.79 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 10000 | 50 | 46,342.93 ns | - | - | - | - | | Retrieve_PersistentMap_Unicode | 10000 | 50 | 33,945.57 ns | - | - | - | - | | Retrieve_ImmSortedDict | 10000 | 50 | 385,767.81 ns | - | - | - | - | | Retrieve_ExtMap | 10000 | 50 | 426,750.47 ns | - | - | - | - | @@ -534,7 +536,7 @@ These benchmarks act like above, but do not insert keys in a specific order. Sor | Build_ExtMap | 100000 | 8 | 119,087,830.57 ns | 12400.0000 | 5600.0000 | - | 104660688 B | | Build_ExtHashMap | 100000 | 8 | 45,457,387.28 ns | 12000.0000 | 2583.3333 | 83.3333 | 99806394 B | | Retrieve_ImmDict | 100000 | 8 | 1,516,808.05 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 100000 | 8 | 6,444,842.15 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100000 | 8 | 1,760,464.44 ns | - | - | - | - | | Retrieve_PersistentMap_Unicode | 100000 | 8 | 1,351,906.08 ns | - | - | - | - | | Retrieve_ImmSortedDict | 100000 | 8 | 6,176,362.41 ns | - | - | - | - | | Retrieve_ExtMap | 100000 | 8 | 6,613,900.92 ns | - | - | - | - | @@ -576,7 +578,7 @@ These benchmarks act like above, but do not insert keys in a specific order. Sor | Build_ExtMap | 100000 | 50 | 130,490,141.33 ns | 12250.0000 | 5250.0000 | - | 104371560 B | | Build_ExtHashMap | 100000 | 50 | 49,945,252.31 ns | 11900.0000 | 2400.0000 | - | 99832472 B | | Retrieve_ImmDict | 100000 | 50 | 1,933,934.21 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 100000 | 50 | 7,474,711.16 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100000 | 50 | 2,275,491.43 ns | - | - | - | - | | Retrieve_PersistentMap_Unicode | 100000 | 50 | 1,439,009.47 ns | - | - | - | - | | Retrieve_ImmSortedDict | 100000 | 50 | 7,341,270.59 ns | - | - | - | - | | Retrieve_ExtMap | 100000 | 50 | 7,144,334.50 ns | - | - | - | - | From 06e5144d82e7d8aa502a0af771d57b8349047d0b Mon Sep 17 00:00:00 2001 From: bjoli Date: Sat, 25 Apr 2026 16:26:37 +0000 Subject: [PATCH 15/22] Update PersistentMap/Readme.org --- PersistentMap/Readme.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PersistentMap/Readme.org b/PersistentMap/Readme.org index bb278dd..7563a71 100644 --- a/PersistentMap/Readme.org +++ b/PersistentMap/Readme.org @@ -2,7 +2,7 @@ A high-performance, persistent (Copy-on-Write) B+ Tree implemented in C#. -It is designed for zero-overhead reads, SIMD-accelerated key routing, and allocation-free range queries. It supports both fully immutable usage and "Transient" mode for high-throughput bulk mutations. +It is designed for zero-overhead reads, SIMD-accelerated key routing, and allocation-free range queries. It supports both fully immutable usage and "Transient" mode for high-throughput bulk mutations. The primary use case is for when editing operations are in bulk. Updating single elements many times will fail to distinguish this collection from other more mature collections. Bulk writes are very fast using transient interfaces, random reads are fastish depending on key type and entropy, Sequential reads and min/max queries are very fast. ** Features - *Copy-on-Write Semantics*: Thread-safe, immutable tree states. Modifying the tre yields a new version while sharing unmodified nodes. From 2207e5e87b5d2918c1b65fb251e3344381556006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Tue, 28 Apr 2026 07:59:46 +0200 Subject: [PATCH 16/22] Fix stupid mistake that led to boxing updated benchmarks --- PersistentMap/KeyStrategies.cs | 6 +- PersistentMap/Readme.org | 681 ++++++++++++++++----------------- 2 files changed, 342 insertions(+), 345 deletions(-) diff --git a/PersistentMap/KeyStrategies.cs b/PersistentMap/KeyStrategies.cs index f53d5a2..f355921 100644 --- a/PersistentMap/KeyStrategies.cs +++ b/PersistentMap/KeyStrategies.cs @@ -26,9 +26,9 @@ public interface IKeyStrategy public struct UnicodeStrategy : IKeyStrategy { - bool UsesPrefixes => true; - bool UseBinarySearch => false; - bool IsLossLess => false; + public bool UsesPrefixes => true; + public bool UseBinarySearch => false; + public bool IsLossLess => false; [MethodImpl(MethodImplOptions.AggressiveInlining)] public int Compare(string? x, string? y) => string.CompareOrdinal(x, y); diff --git a/PersistentMap/Readme.org b/PersistentMap/Readme.org index 7563a71..b79672c 100644 --- a/PersistentMap/Readme.org +++ b/PersistentMap/Readme.org @@ -273,349 +273,346 @@ These benchmarks act like above, but do not insert keys in a specific order. Sor #+begin_src -| Method | Size | keylen | Mean | Gen0 | Gen1 | Gen2 | Allocated | -|----------------------------------|--------|--------|------------------:|-----------:|----------:|---------:|------------:| -| Build_TransientMap_Standard | 100 | 8 | 17,993.68 ns | 0.7324 | - | - | 6216 B | -| Build_TransientMap_Unicode | 100 | 8 | 12,240.69 ns | 0.8850 | 0.0153 | - | 7528 B | -| Build_ImmDict | 100 | 8 | 13,807.87 ns | 5.3406 | 0.0610 | - | 44704 B | -| Build_ImmSortedDict | 100 | 8 | 21,920.45 ns | 4.2114 | 0.0305 | - | 35472 B | -| Build_ExtMap | 100 | 8 | 22,192.67 ns | 5.6152 | 0.0610 | - | 47104 B | -| Build_ExtHashMap | 100 | 8 | 10,569.35 ns | 4.5013 | 0.0305 | - | 37680 B | -| Retrieve_ImmDict | 100 | 8 | 79.60 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 100 | 8 | 112.5 ns | - | - | - | - | -| Retrieve_PersistentMap_Unicode | 100 | 8 | 148.82 ns | - | - | - | - | -| Retrieve_ImmSortedDict | 100 | 8 | 1,198.42 ns | - | - | - | - | -| Retrieve_ExtMap | 100 | 8 | 1,305.45 ns | - | - | - | - | -| Retrieve_ExtHashMap | 100 | 8 | 197.96 ns | - | - | - | - | -| Update_ImmDict | 100 | 8 | 1,232.22 ns | 0.4826 | 0.0019 | - | 4048 B | -| Update_PersistentMap_Standard | 100 | 8 | 2,884.71 ns | 2.4071 | 0.0420 | - | 20160 B | -| Update_PersistentMap_Unicode | 100 | 8 | 1,955.50 ns | 2.7046 | 0.0496 | - | 22640 B | -| Update_TransientMap_Standard | 100 | 8 | 1,621.74 ns | 0.4349 | 0.0038 | - | 3640 B | -| Update_TransientMap_Unicode | 100 | 8 | 674.43 ns | 0.4644 | 0.0057 | - | 3888 B | -| Update_ImmSortedDict | 100 | 8 | 1,701.62 ns | 0.3662 | - | - | 3072 B | -| Update_ExtMap | 100 | 8 | 1,803.33 ns | 0.4120 | - | - | 3456 B | -| Update_ExtHashMap | 100 | 8 | 752.46 ns | 0.5083 | 0.0010 | - | 4256 B | -| UpdateSet_ImmDict | 100 | 8 | 1,450.07 ns | 0.5512 | 0.0019 | - | 4624 B | -| UpdateSet_PersistentMap_Standard | 100 | 8 | 3,246.39 ns | 2.4071 | 0.0420 | - | 20160 B | -| UpdateSet_PersistentMap_Unicode | 100 | 8 | 2,587.39 ns | 2.7046 | 0.0534 | - | 22640 B | -| UpdateSet_TransientMap_Standard | 100 | 8 | 1,970.08 ns | 0.4349 | 0.0038 | - | 3640 B | -| UpdateSet_TransientMap_Unicode | 100 | 8 | 1,184.37 ns | 0.4635 | 0.0057 | - | 3888 B | -| UpdateSet_ImmSortedDict | 100 | 8 | 2,352.86 ns | 0.4578 | - | - | 3840 B | -| UpdateSet_ExtMap | 100 | 8 | 2,089.91 ns | 0.4730 | - | - | 3960 B | -| UpdateSet_ExtHashMap | 100 | 8 | 819.11 ns | 0.5445 | 0.0010 | - | 4560 B | -| Iterate_ImmDict | 100 | 8 | 1,338.64 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 100 | 8 | 183.94 ns | - | - | - | - | -| Iterate_ImmSortedDict | 100 | 8 | 490.25 ns | - | - | - | - | -| Iterate_ExtMap | 100 | 8 | 332.42 ns | 0.0038 | - | - | 32 B | -| Iterate_ExtHashMap | 100 | 8 | 1,162.47 ns | 0.2747 | - | - | 2304 B | -| Iterate_PersistentMap_Unicode | 100 | 8 | 186.95 ns | - | - | - | - | -| Remove_ImmDict | 100 | 8 | 1,396.87 ns | 0.4978 | 0.0019 | - | 4176 B | -| Remove_PersistentMap_Standard | 100 | 8 | 3,197.32 ns | 2.3804 | 0.0420 | - | 19920 B | -| Remove_PersistentMap_Unicode | 100 | 8 | 2,522.69 ns | 2.6779 | 0.0496 | - | 22400 B | -| Remove_TransientMap_Standard | 100 | 8 | 1,973.57 ns | 0.4120 | 0.0038 | - | 3448 B | -| Remove_TransientMap_Unicode | 100 | 8 | 1,320.29 ns | 0.4406 | 0.0057 | - | 3688 B | -| Remove_ImmSortedDict | 100 | 8 | 1,950.87 ns | 0.3777 | - | - | 3168 B | -| Remove_ExtMap | 100 | 8 | 2,240.33 ns | 0.5798 | - | - | 4856 B | -| Remove_ExtHashMap | 100 | 8 | 746.28 ns | 0.5140 | 0.0019 | - | 4304 B | -| Build_TransientMap_Standard | 100 | 50 | 18,239.16 ns | 0.7324 | - | - | 6216 B | -| Build_TransientMap_Unicode | 100 | 50 | 11,800.99 ns | 0.8850 | 0.0153 | - | 7528 B | -| Build_ImmDict | 100 | 50 | 16,343.07 ns | 5.3406 | 0.0610 | - | 44832 B | -| Build_ImmSortedDict | 100 | 50 | 21,709.91 ns | 4.2114 | 0.0305 | - | 35232 B | -| Build_ExtMap | 100 | 50 | 22,948.94 ns | 5.6458 | 0.0610 | - | 47328 B | -| Build_ExtHashMap | 100 | 50 | 12,107.35 ns | 4.0894 | 0.0305 | - | 34280 B | -| Retrieve_ImmDict | 100 | 50 | 286.13 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 100 | 50 | 984.90 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 100 | 50 | 105.3 ns | - | - | - | - | -| Retrieve_PersistentMap_Unicode | 100 | 50 | 151.45 ns | - | - | - | - | -| Retrieve_ImmSortedDict | 100 | 50 | 1,308.09 ns | - | - | - | - | -| Retrieve_ExtMap | 100 | 50 | 1,164.25 ns | - | - | - | - | -| Retrieve_ExtHashMap | 100 | 50 | 398.29 ns | - | - | - | - | -| Update_ImmDict | 100 | 50 | 1,390.29 ns | 0.4749 | 0.0019 | - | 3984 B | -| Update_PersistentMap_Standard | 100 | 50 | 2,932.88 ns | 2.4071 | 0.0381 | - | 20160 B | -| Update_PersistentMap_Unicode | 100 | 50 | 1,947.26 ns | 2.7046 | 0.0534 | - | 22640 B | -| Update_TransientMap_Standard | 100 | 50 | 1,545.90 ns | 0.4349 | 0.0038 | - | 3640 B | -| Update_TransientMap_Unicode | 100 | 50 | 646.55 ns | 0.4644 | 0.0057 | - | 3888 B | -| Update_ImmSortedDict | 100 | 50 | 2,010.40 ns | 0.4120 | - | - | 3456 B | -| Update_ExtMap | 100 | 50 | 1,961.80 ns | 0.4234 | - | - | 3568 B | -| Update_ExtHashMap | 100 | 50 | 1,013.87 ns | 0.5188 | 0.0019 | - | 4344 B | -| UpdateSet_ImmDict | 100 | 50 | 1,753.26 ns | 0.5665 | 0.0019 | - | 4752 B | -| UpdateSet_PersistentMap_Standard | 100 | 50 | 3,393.56 ns | 2.4071 | 0.0420 | - | 20160 B | -| UpdateSet_PersistentMap_Unicode | 100 | 50 | 2,495.39 ns | 2.7046 | 0.0496 | - | 22640 B | -| UpdateSet_TransientMap_Standard | 100 | 50 | 2,073.78 ns | 0.4349 | 0.0038 | - | 3640 B | -| UpdateSet_TransientMap_Unicode | 100 | 50 | 1,258.03 ns | 0.4635 | 0.0057 | - | 3888 B | -| UpdateSet_ImmSortedDict | 100 | 50 | 2,456.28 ns | 0.4730 | - | - | 3984 B | -| UpdateSet_ExtMap | 100 | 50 | 2,420.97 ns | 0.5913 | - | - | 4968 B | -| UpdateSet_ExtHashMap | 100 | 50 | 894.89 ns | 0.4902 | 0.0019 | - | 4104 B | -| Iterate_ImmDict | 100 | 50 | 1,311.46 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 100 | 50 | 189.59 ns | - | - | - | - | -| Iterate_ImmSortedDict | 100 | 50 | 482.89 ns | - | - | - | - | -| Iterate_ExtMap | 100 | 50 | 357.71 ns | 0.0038 | - | - | 32 B | -| Iterate_ExtHashMap | 100 | 50 | 1,241.61 ns | 0.2918 | - | - | 2448 B | -| Iterate_PersistentMap_Unicode | 100 | 50 | 195.23 ns | - | - | - | - | -| Remove_ImmDict | 100 | 50 | 1,412.10 ns | 0.4826 | 0.0019 | - | 4048 B | -| Remove_PersistentMap_Standard | 100 | 50 | 3,421.53 ns | 2.3804 | 0.0420 | - | 19920 B | -| Remove_PersistentMap_Unicode | 100 | 50 | 2,640.76 ns | 2.6779 | 0.0458 | - | 22400 B | -| Remove_TransientMap_Standard | 100 | 50 | 2,333.67 ns | 0.4120 | 0.0038 | - | 3448 B | -| Remove_TransientMap_Unicode | 100 | 50 | 1,393.38 ns | 0.4406 | 0.0057 | - | 3688 B | -| Remove_ImmSortedDict | 100 | 50 | 2,034.54 ns | 0.3891 | - | - | 3264 B | -| Remove_ExtMap | 100 | 50 | 2,105.53 ns | 0.4387 | - | - | 3680 B | -| Remove_ExtHashMap | 100 | 50 | 914.57 ns | 0.4635 | 0.0010 | - | 3880 B | -| Build_TransientMap_Standard | 1000 | 8 | 337,908.27 ns | 5.8594 | - | - | 49512 B | -| Build_TransientMap_Unicode | 1000 | 8 | 174,485.27 ns | 7.3242 | 0.7324 | - | 62248 B | -| Build_ImmDict | 1000 | 8 | 258,419.20 ns | 79.1016 | 8.7891 | - | 664448 B | -| Build_ImmSortedDict | 1000 | 8 | 425,563.19 ns | 61.0352 | 4.8828 | - | 513312 B | -| Build_ExtMap | 1000 | 8 | 405,979.91 ns | 79.1016 | 7.3242 | - | 662448 B | -| Build_ExtHashMap | 1000 | 8 | 149,392.88 ns | 70.5566 | 4.6387 | - | 590896 B | -| Retrieve_ImmDict | 1000 | 8 | 1,055.73 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 1000 | 8 | 1,778.70 ns | - | - | - | - | -| Retrieve_PersistentMap_Unicode | 1000 | 8 | 1,920.99 ns | - | - | - | - | -| Retrieve_ImmSortedDict | 1000 | 8 | 21,922.86 ns | - | - | - | - | -| Retrieve_ExtMap | 1000 | 8 | 24,281.46 ns | - | - | - | - | -| Retrieve_ExtHashMap | 1000 | 8 | 2,588.04 ns | - | - | - | - | -| Update_ImmDict | 1000 | 8 | 19,804.39 ns | 7.3853 | 0.3052 | - | 61792 B | -| Update_PersistentMap_Standard | 1000 | 8 | 39,488.86 ns | 24.0479 | 2.3193 | - | 201600 B | -| Update_PersistentMap_Unicode | 1000 | 8 | 28,346.74 ns | 27.0386 | 2.5330 | - | 226400 B | -| Update_TransientMap_Standard | 1000 | 8 | 24,653.11 ns | 4.5471 | 0.4883 | - | 38184 B | -| Update_TransientMap_Unicode | 1000 | 8 | 7,849.22 ns | 4.4250 | 0.4425 | - | 37024 B | -| Update_ImmSortedDict | 1000 | 8 | 31,925.02 ns | 5.7373 | 0.1221 | - | 48288 B | -| Update_ExtMap | 1000 | 8 | 31,958.66 ns | 6.4087 | 0.1831 | - | 53824 B | -| Update_ExtHashMap | 1000 | 8 | 10,986.98 ns | 7.2327 | 0.2441 | - | 60568 B | -| UpdateSet_ImmDict | 1000 | 8 | 23,205.06 ns | 8.4839 | 0.4272 | - | 71072 B | -| UpdateSet_PersistentMap_Standard | 1000 | 8 | 46,680.76 ns | 24.0479 | 2.1973 | - | 201600 B | -| UpdateSet_PersistentMap_Unicode | 1000 | 8 | 33,833.67 ns | 27.0386 | 2.6245 | - | 226400 B | -| UpdateSet_TransientMap_Standard | 1000 | 8 | 30,683.30 ns | 4.2114 | 0.4272 | - | 35368 B | -| UpdateSet_TransientMap_Unicode | 1000 | 8 | 12,650.99 ns | 4.5929 | 0.4883 | - | 38432 B | -| UpdateSet_ImmSortedDict | 1000 | 8 | 37,438.25 ns | 6.3477 | 0.2441 | - | 53232 B | -| UpdateSet_ExtMap | 1000 | 8 | 37,901.44 ns | 7.6904 | 0.3052 | - | 64576 B | -| UpdateSet_ExtHashMap | 1000 | 8 | 18,651.16 ns | 7.4768 | 0.2441 | - | 62768 B | -| Iterate_ImmDict | 1000 | 8 | 12,693.24 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 1000 | 8 | 1,629.13 ns | - | - | - | - | -| Iterate_ImmSortedDict | 1000 | 8 | 4,901.80 ns | - | - | - | - | -| Iterate_ExtMap | 1000 | 8 | 3,685.00 ns | 0.0038 | - | - | 32 B | -| Iterate_ExtHashMap | 1000 | 8 | 14,519.26 ns | 2.6550 | - | - | 22320 B | -| Iterate_PersistentMap_Unicode | 1000 | 8 | 1,612.41 ns | - | - | - | - | -| Remove_ImmDict | 1000 | 8 | 19,718.44 ns | 7.6294 | 0.3052 | - | 63840 B | -| Remove_PersistentMap_Standard | 1000 | 8 | 45,580.39 ns | 23.8037 | 2.0752 | - | 199200 B | -| Remove_PersistentMap_Unicode | 1000 | 8 | 29,970.54 ns | 26.7639 | 2.4414 | - | 224000 B | -| Remove_TransientMap_Standard | 1000 | 8 | 29,451.58 ns | 4.0894 | 0.3967 | - | 34424 B | -| Remove_TransientMap_Unicode | 1000 | 8 | 14,136.83 ns | 3.7994 | 0.3662 | - | 31848 B | -| Remove_ImmSortedDict | 1000 | 8 | 33,400.75 ns | 5.6763 | 0.1221 | - | 47616 B | -| Remove_ExtMap | 1000 | 8 | 36,167.45 ns | 6.8970 | 0.1831 | - | 57744 B | -| Remove_ExtHashMap | 1000 | 8 | 12,681.31 ns | 8.0414 | 0.2289 | - | 67288 B | -| Build_TransientMap_Standard | 1000 | 50 | 342,796.72 ns | 5.3711 | - | - | 44992 B | -| Build_TransientMap_Unicode | 1000 | 50 | 173,477.79 ns | 7.0801 | 0.4883 | - | 59368 B | -| Build_ImmDict | 1000 | 50 | 275,481.11 ns | 79.1016 | 9.2773 | - | 662528 B | -| Build_ImmSortedDict | 1000 | 50 | 417,349.29 ns | 61.0352 | 5.3711 | - | 511344 B | -| Build_ExtMap | 1000 | 50 | 406,348.46 ns | 78.1250 | 7.3242 | - | 656288 B | -| Build_ExtHashMap | 1000 | 50 | 180,890.87 ns | 69.5801 | 4.8828 | - | 582472 B | -| Retrieve_ImmDict | 1000 | 50 | 3,137.95 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 1000 | 50 | 1,798.00 ns | - | - | - | - | -| Retrieve_PersistentMap_Unicode | 1000 | 50 | 2,096.52 ns | - | - | - | - | -| Retrieve_ImmSortedDict | 1000 | 50 | 20,986.51 ns | - | - | - | - | -| Retrieve_ExtMap | 1000 | 50 | 23,287.17 ns | - | - | - | - | -| Retrieve_ExtHashMap | 1000 | 50 | 4,776.52 ns | - | - | - | - | -| Update_ImmDict | 1000 | 50 | 25,749.96 ns | 7.5989 | 0.3052 | - | 63776 B | -| Update_PersistentMap_Standard | 1000 | 50 | 39,878.09 ns | 24.0479 | 2.0752 | - | 201600 B | -| Update_PersistentMap_Unicode | 1000 | 50 | 28,771.02 ns | 27.0386 | 2.2888 | - | 226400 B | -| Update_TransientMap_Standard | 1000 | 50 | 24,641.84 ns | 3.8757 | 0.3357 | - | 32552 B | -| Update_TransientMap_Unicode | 1000 | 50 | 7,785.56 ns | 4.0894 | 0.3967 | - | 34208 B | -| Update_ImmSortedDict | 1000 | 50 | 32,625.21 ns | 5.6763 | 0.1221 | - | 47952 B | -| Update_ExtMap | 1000 | 50 | 33,656.18 ns | 6.5308 | 0.1831 | - | 54720 B | -| Update_ExtHashMap | 1000 | 50 | 13,197.36 ns | 7.1869 | 0.2289 | - | 60168 B | -| UpdateSet_ImmDict | 1000 | 50 | 24,838.46 ns | 8.2397 | 0.3967 | - | 68960 B | -| UpdateSet_PersistentMap_Standard | 1000 | 50 | 47,257.42 ns | 24.1699 | 2.0142 | - | 202504 B | -| UpdateSet_PersistentMap_Unicode | 1000 | 50 | 34,871.45 ns | 27.2217 | 2.6245 | - | 227840 B | -| UpdateSet_TransientMap_Standard | 1000 | 50 | 30,705.12 ns | 3.9673 | 0.3662 | - | 33456 B | -| UpdateSet_TransientMap_Unicode | 1000 | 50 | 13,306.26 ns | 4.4250 | 0.4578 | - | 37056 B | -| UpdateSet_ImmSortedDict | 1000 | 50 | 37,358.03 ns | 6.2866 | 0.1831 | - | 53088 B | -| UpdateSet_ExtMap | 1000 | 50 | 39,040.99 ns | 7.8735 | 0.3052 | - | 66200 B | -| UpdateSet_ExtHashMap | 1000 | 50 | 18,993.92 ns | 7.9346 | 0.2747 | - | 66584 B | -| Iterate_ImmDict | 1000 | 50 | 12,796.55 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 1000 | 50 | 1,601.24 ns | - | - | - | - | -| Iterate_ImmSortedDict | 1000 | 50 | 4,897.01 ns | - | - | - | - | -| Iterate_ExtMap | 1000 | 50 | 3,590.09 ns | 0.0038 | - | - | 32 B | -| Iterate_ExtHashMap | 1000 | 50 | 14,570.64 ns | 2.6550 | - | - | 22320 B | -| Iterate_PersistentMap_Unicode | 1000 | 50 | 1,624.65 ns | - | - | - | - | -| Remove_ImmDict | 1000 | 50 | 22,247.23 ns | 7.7209 | 0.3357 | - | 64800 B | -| Remove_PersistentMap_Standard | 1000 | 50 | 47,236.28 ns | 23.8037 | 2.0752 | - | 199200 B | -| Remove_PersistentMap_Unicode | 1000 | 50 | 30,337.50 ns | 26.7639 | 2.2583 | - | 224000 B | -| Remove_TransientMap_Standard | 1000 | 50 | 30,224.03 ns | 3.4180 | 0.3052 | - | 28792 B | -| Remove_TransientMap_Unicode | 1000 | 50 | 13,980.33 ns | 3.2959 | 0.2899 | - | 27624 B | -| Remove_ImmSortedDict | 1000 | 50 | 34,150.76 ns | 5.6763 | 0.1221 | - | 47664 B | -| Remove_ExtMap | 1000 | 50 | 36,453.25 ns | 6.9580 | 0.2441 | - | 58640 B | -| Remove_ExtHashMap | 1000 | 50 | 14,511.93 ns | 7.9041 | 0.2136 | - | 66176 B | -| Build_TransientMap_Standard | 10000 | 8 | 4,972,737.60 ns | 46.8750 | 7.8125 | - | 453272 B | -| Build_TransientMap_Unicode | 10000 | 8 | 2,273,723.34 ns | 66.4063 | 19.5313 | - | 576584 B | -| Build_ImmDict | 10000 | 8 | 4,474,574.70 ns | 1039.0625 | 507.8125 | - | 8751104 B | -| Build_ImmSortedDict | 10000 | 8 | 6,548,873.72 ns | 804.6875 | 281.2500 | - | 6767232 B | -| Build_ExtMap | 10000 | 8 | 6,462,311.12 ns | 1015.6250 | 437.5000 | - | 8542480 B | -| Build_ExtHashMap | 10000 | 8 | 1,921,265.07 ns | 945.3125 | 326.1719 | - | 7907368 B | -| Retrieve_ImmDict | 10000 | 8 | 21,054.43 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 10000 | 8 | 386,477.89 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 10000 | 8 | 46,945.00 ns | - | - | - | - | -| Retrieve_PersistentMap_Unicode | 10000 | 8 | 32,919.53 ns | - | - | - | - | -| Retrieve_ImmSortedDict | 10000 | 8 | 383,296.28 ns | - | - | - | - | -| Retrieve_ExtMap | 10000 | 8 | 447,733.80 ns | - | - | - | - | -| Retrieve_ExtHashMap | 10000 | 8 | 38,576.71 ns | - | - | - | - | -| Update_ImmDict | 10000 | 8 | 395,715.60 ns | 101.0742 | 27.3438 | - | 848128 B | -| Update_PersistentMap_Standard | 10000 | 8 | 662,654.83 ns | 305.6641 | 131.8359 | - | 2560000 B | -| Update_PersistentMap_Unicode | 10000 | 8 | 425,935.07 ns | 366.2109 | 160.1563 | - | 3064000 B | -| Update_TransientMap_Standard | 10000 | 8 | 442,700.87 ns | 41.0156 | 14.1602 | - | 346280 B | -| Update_TransientMap_Unicode | 10000 | 8 | 152,961.90 ns | 40.7715 | 16.6016 | - | 341792 B | -| Update_ImmSortedDict | 10000 | 8 | 599,396.52 ns | 76.1719 | 15.6250 | - | 640368 B | -| Update_ExtMap | 10000 | 8 | 565,510.82 ns | 87.8906 | 22.4609 | - | 735640 B | -| Update_ExtHashMap | 10000 | 8 | 165,164.46 ns | 102.5391 | 20.9961 | - | 859160 B | -| UpdateSet_ImmDict | 10000 | 8 | 436,729.87 ns | 108.3984 | 29.2969 | - | 908672 B | -| UpdateSet_PersistentMap_Standard | 10000 | 8 | 751,649.78 ns | 306.6406 | 131.8359 | - | 2572328 B | -| UpdateSet_PersistentMap_Unicode | 10000 | 8 | 499,253.41 ns | 368.1641 | 154.2969 | - | 3086432 B | -| UpdateSet_TransientMap_Standard | 10000 | 8 | 538,898.72 ns | 41.9922 | 16.6016 | - | 357200 B | -| UpdateSet_TransientMap_Unicode | 10000 | 8 | 227,890.12 ns | 43.7012 | 16.3574 | - | 365632 B | -| UpdateSet_ImmSortedDict | 10000 | 8 | 650,234.49 ns | 82.0313 | 19.5313 | - | 687648 B | -| UpdateSet_ExtMap | 10000 | 8 | 641,265.51 ns | 99.6094 | 25.3906 | - | 833752 B | -| UpdateSet_ExtHashMap | 10000 | 8 | 194,749.46 ns | 104.9805 | 23.9258 | - | 878808 B | -| Iterate_ImmDict | 10000 | 8 | 174,946.48 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 10000 | 8 | 17,152.00 ns | - | - | - | - | -| Iterate_ImmSortedDict | 10000 | 8 | 52,817.11 ns | - | - | - | - | -| Iterate_ExtMap | 10000 | 8 | 66,083.05 ns | - | - | - | 32 B | -| Iterate_ExtHashMap | 10000 | 8 | 174,496.27 ns | 20.2637 | - | - | 169704 B | -| Iterate_PersistentMap_Unicode | 10000 | 8 | 17,945.71 ns | - | - | - | - | -| Remove_ImmDict | 10000 | 8 | 402,693.02 ns | 102.0508 | 23.4375 | - | 855488 B | -| Remove_PersistentMap_Standard | 10000 | 8 | 731,170.91 ns | 302.7344 | 117.1875 | - | 2536000 B | -| Remove_PersistentMap_Unicode | 10000 | 8 | 493,821.11 ns | 363.2813 | 180.6641 | - | 3041408 B | -| Remove_TransientMap_Standard | 10000 | 8 | 511,709.67 ns | 37.1094 | 13.6719 | - | 316696 B | -| Remove_TransientMap_Unicode | 10000 | 8 | 223,881.92 ns | 38.0859 | 15.1367 | - | 319240 B | -| Remove_ImmSortedDict | 10000 | 8 | 600,831.24 ns | 77.1484 | 15.6250 | - | 652944 B | -| Remove_ExtMap | 10000 | 8 | 617,128.15 ns | 91.7969 | 20.5078 | - | 774000 B | -| Remove_ExtHashMap | 10000 | 8 | 179,929.34 ns | 104.4922 | 22.4609 | - | 874984 B | -| Build_TransientMap_Standard | 10000 | 50 | 5,124,826.21 ns | 46.8750 | 7.8125 | - | 453272 B | -| Build_TransientMap_Unicode | 10000 | 50 | 2,251,023.03 ns | 66.4063 | 23.4375 | - | 567112 B | -| Build_ImmDict | 10000 | 50 | 4,751,856.80 ns | 1046.8750 | 507.8125 | - | 8757376 B | -| Build_ImmSortedDict | 10000 | 50 | 6,556,900.60 ns | 804.6875 | 265.6250 | - | 6751584 B | -| Build_ExtMap | 10000 | 50 | 6,985,344.36 ns | 1015.6250 | 429.6875 | - | 8544720 B | -| Build_ExtHashMap | 10000 | 50 | 2,218,415.86 ns | 945.3125 | 308.5938 | - | 7925728 B | -| Retrieve_ImmDict | 10000 | 50 | 46,389.46 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 10000 | 50 | 46,342.93 ns | - | - | - | - | -| Retrieve_PersistentMap_Unicode | 10000 | 50 | 33,945.57 ns | - | - | - | - | -| Retrieve_ImmSortedDict | 10000 | 50 | 385,767.81 ns | - | - | - | - | -| Retrieve_ExtMap | 10000 | 50 | 426,750.47 ns | - | - | - | - | -| Retrieve_ExtHashMap | 10000 | 50 | 61,891.51 ns | - | - | - | - | -| Update_ImmDict | 10000 | 50 | 419,181.33 ns | 101.0742 | 27.3438 | - | 848768 B | -| Update_PersistentMap_Standard | 10000 | 50 | 674,365.10 ns | 305.6641 | 125.9766 | - | 2560000 B | -| Update_PersistentMap_Unicode | 10000 | 50 | 423,829.00 ns | 366.2109 | 158.2031 | - | 3064000 B | -| Update_TransientMap_Standard | 10000 | 50 | 455,884.40 ns | 40.5273 | 13.6719 | - | 339240 B | -| Update_TransientMap_Unicode | 10000 | 50 | 151,927.19 ns | 39.5508 | 13.9160 | - | 331136 B | -| Update_ImmSortedDict | 10000 | 50 | 599,787.55 ns | 76.1719 | 14.6484 | - | 641616 B | -| Update_ExtMap | 10000 | 50 | 576,111.14 ns | 86.9141 | 20.5078 | - | 732896 B | -| Update_ExtHashMap | 10000 | 50 | 186,299.39 ns | 102.5391 | 20.2637 | - | 859392 B | -| UpdateSet_ImmDict | 10000 | 50 | 459,230.81 ns | 108.3984 | 29.2969 | - | 908672 B | -| UpdateSet_PersistentMap_Standard | 10000 | 50 | 758,257.06 ns | 307.6172 | 133.7891 | - | 2575040 B | -| UpdateSet_PersistentMap_Unicode | 10000 | 50 | 499,618.94 ns | 368.1641 | 152.3438 | - | 3085600 B | -| UpdateSet_TransientMap_Standard | 10000 | 50 | 552,006.66 ns | 41.9922 | 15.6250 | - | 355688 B | -| UpdateSet_TransientMap_Unicode | 10000 | 50 | 225,461.43 ns | 42.2363 | 15.8691 | - | 354144 B | -| UpdateSet_ImmSortedDict | 10000 | 50 | 660,239.03 ns | 82.0313 | 19.5313 | - | 691728 B | -| UpdateSet_ExtMap | 10000 | 50 | 642,281.65 ns | 98.6328 | 23.4375 | - | 829552 B | -| UpdateSet_ExtHashMap | 10000 | 50 | 213,376.13 ns | 104.9805 | 23.4375 | - | 878248 B | -| Iterate_ImmDict | 10000 | 50 | 181,714.98 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 10000 | 50 | 17,120.93 ns | - | - | - | - | -| Iterate_ImmSortedDict | 10000 | 50 | 54,752.59 ns | - | - | - | - | -| Iterate_ExtMap | 10000 | 50 | 66,116.25 ns | - | - | - | 32 B | -| Iterate_ExtHashMap | 10000 | 50 | 175,406.16 ns | 19.7754 | - | - | 166824 B | -| Iterate_PersistentMap_Unicode | 10000 | 50 | 17,271.03 ns | - | - | - | - | -| Remove_ImmDict | 10000 | 50 | 424,174.08 ns | 102.0508 | 23.9258 | - | 854784 B | -| Remove_PersistentMap_Standard | 10000 | 50 | 739,059.87 ns | 302.7344 | 119.1406 | - | 2538816 B | -| Remove_PersistentMap_Unicode | 10000 | 50 | 491,577.81 ns | 363.2813 | 158.2031 | - | 3042816 B | -| Remove_TransientMap_Standard | 10000 | 50 | 522,589.23 ns | 38.0859 | 12.6953 | - | 319512 B | -| Remove_TransientMap_Unicode | 10000 | 50 | 223,052.49 ns | 37.3535 | 13.1836 | - | 312808 B | -| Remove_ImmSortedDict | 10000 | 50 | 612,756.00 ns | 77.1484 | 15.6250 | - | 651840 B | -| Remove_ExtMap | 10000 | 50 | 622,977.64 ns | 91.7969 | 21.4844 | - | 771256 B | -| Remove_ExtHashMap | 10000 | 50 | 202,442.11 ns | 104.0039 | 20.9961 | - | 871064 B | -| Build_TransientMap_Standard | 100000 | 8 | 72,441,876.47 ns | 428.5714 | 285.7143 | - | 4524208 B | -| Build_TransientMap_Unicode | 100000 | 8 | 39,704,110.55 ns | 692.3077 | 538.4615 | - | 5798728 B | -| Build_ImmDict | 100000 | 8 | 92,116,430.52 ns | 13166.6667 | 4833.3333 | 166.6667 | 109376373 B | -| Build_ImmSortedDict | 100000 | 8 | 119,704,496.44 ns | 10000.0000 | 3800.0000 | - | 83946432 B | -| Build_ExtMap | 100000 | 8 | 119,087,830.57 ns | 12400.0000 | 5600.0000 | - | 104660688 B | -| Build_ExtHashMap | 100000 | 8 | 45,457,387.28 ns | 12000.0000 | 2583.3333 | 83.3333 | 99806394 B | -| Retrieve_ImmDict | 100000 | 8 | 1,516,808.05 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 100000 | 8 | 1,760,464.44 ns | - | - | - | - | -| Retrieve_PersistentMap_Unicode | 100000 | 8 | 1,351,906.08 ns | - | - | - | - | -| Retrieve_ImmSortedDict | 100000 | 8 | 6,176,362.41 ns | - | - | - | - | -| Retrieve_ExtMap | 100000 | 8 | 6,613,900.92 ns | - | - | - | - | -| Retrieve_ExtHashMap | 100000 | 8 | 1,046,707.73 ns | - | - | - | - | -| Update_ImmDict | 100000 | 8 | 8,149,123.59 ns | 1265.6250 | 1046.8750 | - | 10618880 B | -| Update_PersistentMap_Standard | 100000 | 8 | 14,827,486.05 ns | 3750.0000 | 2953.1250 | 46.8750 | 31040047 B | -| Update_PersistentMap_Unicode | 100000 | 8 | 11,607,930.86 ns | 4671.8750 | 2500.0000 | 62.5000 | 38640058 B | -| Update_TransientMap_Standard | 100000 | 8 | 7,358,177.48 ns | 406.2500 | 335.9375 | - | 3415688 B | -| Update_TransientMap_Unicode | 100000 | 8 | 3,826,038.54 ns | 414.0625 | 351.5625 | - | 3487168 B | -| Update_ImmSortedDict | 100000 | 8 | 10,318,908.01 ns | 953.1250 | 781.2500 | - | 8020080 B | -| Update_ExtMap | 100000 | 8 | 10,354,686.58 ns | 1093.7500 | 937.5000 | - | 9246400 B | -| Update_ExtHashMap | 100000 | 8 | 3,752,714.02 ns | 1269.5313 | 988.2813 | - | 10626504 B | -| UpdateSet_ImmDict | 100000 | 8 | 8,658,752.54 ns | 1343.7500 | 1046.8750 | - | 11262464 B | -| UpdateSet_PersistentMap_Standard | 100000 | 8 | 16,011,001.98 ns | 3750.0000 | 2593.7500 | 31.2500 | 31143252 B | -| UpdateSet_PersistentMap_Unicode | 100000 | 8 | 12,524,719.72 ns | 4687.5000 | 2281.2500 | 62.5000 | 38809433 B | -| UpdateSet_TransientMap_Standard | 100000 | 8 | 8,280,433.69 ns | 406.2500 | 328.1250 | - | 3514688 B | -| UpdateSet_TransientMap_Unicode | 100000 | 8 | 4,465,123.76 ns | 429.6875 | 382.8125 | - | 3636832 B | -| UpdateSet_ImmSortedDict | 100000 | 8 | 11,403,173.00 ns | 1015.6250 | 875.0000 | - | 8495856 B | -| UpdateSet_ExtMap | 100000 | 8 | 11,808,286.34 ns | 1218.7500 | 1015.6250 | - | 10287328 B | -| UpdateSet_ExtHashMap | 100000 | 8 | 4,073,301.12 ns | 1281.2500 | 992.1875 | - | 10759216 B | -| Iterate_ImmDict | 100000 | 8 | 2,210,260.69 ns | - | - | - | - | -| Iterate_PersistentMap_Standard | 100000 | 8 | 221,859.64 ns | - | - | - | - | -| Iterate_ImmSortedDict | 100000 | 8 | 712,098.18 ns | - | - | - | - | -| Iterate_ExtMap | 100000 | 8 | 1,122,895.56 ns | - | - | - | 32 B | -| Iterate_ExtHashMap | 100000 | 8 | 2,722,789.29 ns | 273.4375 | - | - | 2314696 B | -| Iterate_PersistentMap_Unicode | 100000 | 8 | 235,131.71 ns | - | - | - | - | -| Remove_ImmDict | 100000 | 8 | 7,950,218.64 ns | 1281.2500 | 1125.0000 | - | 10756416 B | -| Remove_PersistentMap_Standard | 100000 | 8 | 15,366,222.73 ns | 3718.7500 | 3328.1250 | 46.8750 | 30821152 B | -| Remove_PersistentMap_Unicode | 100000 | 8 | 12,092,756.69 ns | 4656.2500 | 2875.0000 | 62.5000 | 38412727 B | -| Remove_TransientMap_Standard | 100000 | 8 | 7,973,868.64 ns | 375.0000 | 312.5000 | - | 3174328 B | -| Remove_TransientMap_Unicode | 100000 | 8 | 3,161,959.43 ns | 382.8125 | 324.2188 | - | 3226088 B | -| Remove_ImmSortedDict | 100000 | 8 | 10,396,202.26 ns | 953.1250 | 781.2500 | - | 8031888 B | -| Remove_ExtMap | 100000 | 8 | 11,561,856.15 ns | 1140.6250 | 937.5000 | - | 9630448 B | -| Remove_ExtHashMap | 100000 | 8 | 4,009,364.62 ns | 1265.6250 | 921.8750 | - | 10607248 B | -| Build_TransientMap_Standard | 100000 | 50 | 81,360,172.13 ns | 428.5714 | 285.7143 | - | 4511632 B | -| Build_TransientMap_Unicode | 100000 | 50 | 41,339,351.49 ns | 666.6667 | 500.0000 | - | 5763784 B | -| Build_ImmDict | 100000 | 50 | 97,054,583.11 ns | 13166.6667 | 4833.3333 | 166.6667 | 109218477 B | -| Build_ImmSortedDict | 100000 | 50 | 129,585,060.60 ns | 10000.0000 | 4000.0000 | - | 83731632 B | -| Build_ExtMap | 100000 | 50 | 130,490,141.33 ns | 12250.0000 | 5250.0000 | - | 104371560 B | -| Build_ExtHashMap | 100000 | 50 | 49,945,252.31 ns | 11900.0000 | 2400.0000 | - | 99832472 B | -| Retrieve_ImmDict | 100000 | 50 | 1,933,934.21 ns | - | - | - | - | -| Retrieve_PersistentMap_Standard | 100000 | 50 | 2,275,491.43 ns | - | - | - | - | -| Retrieve_PersistentMap_Unicode | 100000 | 50 | 1,439,009.47 ns | - | - | - | - | -| Retrieve_ImmSortedDict | 100000 | 50 | 7,341,270.59 ns | - | - | - | - | -| Retrieve_ExtMap | 100000 | 50 | 7,144,334.50 ns | - | - | - | - | -| Retrieve_ExtHashMap | 100000 | 50 | 1,359,724.09 ns | - | - | - | - | -| Update_ImmDict | 100000 | 50 | 8,224,368.98 ns | 1265.6250 | 1062.5000 | - | 10585792 B | -| Update_PersistentMap_Standard | 100000 | 50 | 16,195,436.66 ns | 3718.7500 | 2875.0000 | 31.2500 | 31040034 B | -| Update_PersistentMap_Unicode | 100000 | 50 | 11,872,157.79 ns | 4656.2500 | 2765.6250 | 46.8750 | 38640047 B | -| Update_TransientMap_Standard | 100000 | 50 | 8,421,668.39 ns | 406.2500 | 343.7500 | - | 3414056 B | -| Update_TransientMap_Unicode | 100000 | 50 | 3,920,098.30 ns | 406.2500 | 335.9375 | - | 3440288 B | -| Update_ImmSortedDict | 100000 | 50 | 11,196,973.30 ns | 953.1250 | 781.2500 | - | 8012928 B | -| Update_ExtMap | 100000 | 50 | 10,904,107.16 ns | 1093.7500 | 906.2500 | - | 9233632 B | -| Update_ExtHashMap | 100000 | 50 | 4,099,435.72 ns | 1265.6250 | 976.5625 | - | 10625992 B | -| UpdateSet_ImmDict | 100000 | 50 | 8,815,363.39 ns | 1343.7500 | 1031.2500 | - | 11286912 B | -| UpdateSet_PersistentMap_Standard | 100000 | 50 | 17,059,344.47 ns | 3718.7500 | 2562.5000 | - | 31141992 B | -| UpdateSet_PersistentMap_Unicode | 100000 | 50 | 13,129,653.12 ns | 4687.5000 | 2437.5000 | 46.8750 | 38835954 B | -| UpdateSet_TransientMap_Standard | 100000 | 50 | 9,618,034.32 ns | 406.2500 | 328.1250 | - | 3509008 B | -| UpdateSet_TransientMap_Unicode | 100000 | 50 | 4,850,003.99 ns | 429.6875 | 359.3750 | - | 3630560 B | -| UpdateSet_ImmSortedDict | 100000 | 50 | 12,656,577.42 ns | 1015.6250 | 890.6250 | - | 8501136 B | -| UpdateSet_ExtMap | 100000 | 50 | 12,813,242.59 ns | 1218.7500 | 1015.6250 | - | 10265544 B | -| UpdateSet_ExtHashMap | 100000 | 50 | 4,430,259.78 ns | 1281.2500 | 1000.0000 | - | 10765376 B | -| Iterate_ImmDict | 100000 | 50 | 2,381,194.68 ns | - | - | - | 96 B | -| Iterate_PersistentMap_Standard | 100000 | 50 | 211,225.33 ns | - | - | - | - | -| Iterate_ImmSortedDict | 100000 | 50 | 713,700.90 ns | - | - | - | - | -| Iterate_ExtMap | 100000 | 50 | 1,157,631.41 ns | - | - | - | 32 B | -| Iterate_ExtHashMap | 100000 | 50 | 2,676,695.95 ns | 273.4375 | - | - | 2310024 B | -| Iterate_PersistentMap_Unicode | 100000 | 50 | 212,687.91 ns | - | - | - | - | -| Remove_ImmDict | 100000 | 50 | 8,177,782.10 ns | 1281.2500 | 984.3750 | - | 10749824 B | -| Remove_PersistentMap_Standard | 100000 | 50 | 16,635,857.83 ns | 3687.5000 | 3343.7500 | 31.2500 | 30814118 B | -| Remove_PersistentMap_Unicode | 100000 | 50 | 12,383,735.25 ns | 4625.0000 | 2859.3750 | 46.8750 | 38402877 B | -| Remove_TransientMap_Standard | 100000 | 50 | 8,797,592.89 ns | 375.0000 | 312.5000 | - | 3167064 B | -| Remove_TransientMap_Unicode | 100000 | 50 | 3,335,966.12 ns | 382.8125 | 312.5000 | - | 3205960 B | -| Remove_ImmSortedDict | 100000 | 50 | 11,619,906.87 ns | 953.1250 | 781.2500 | - | 8032656 B | -| Remove_ExtMap | 100000 | 50 | 12,051,548.01 ns | 1140.6250 | 921.8750 | - | 9617232 B | -| Remove_ExtHashMap | 100000 | 50 | 4,258,918.68 ns | 1265.6250 | 929.6875 | - | 10612072 B | - -#+end_src +``` +| Method | N | StringLength | Mean | Gen0 | Gen1 | Gen2 | Allocated | +|----------------------------------|------------|--------------|---------------------:|-------------:|-------------:|---------:|--------------:| +| **Build_TransientMap_Standard** | **100** | **8** | **5,170.76 ns** | **0.7401** | **0.0076** | **-** | **6200 B** | +| Build_TransientMap_Unicode | 100 | 8 | 12,251.17 ns | 0.8850 | 0.0153 | - | 7528 B | +| Build_ImmDict | 100 | 8 | 13,926.78 ns | 5.3253 | 0.0610 | - | 44640 B | +| Build_ImmSortedDict | 100 | 8 | 21,789.13 ns | 4.2114 | 0.0305 | - | 35472 B | +| Build_ExtMap | 100 | 8 | 22,791.98 ns | 5.6152 | 0.0610 | - | 47104 B | +| Build_ExtHashMap | 100 | 8 | 9,993.13 ns | 4.0894 | 0.0305 | - | 34216 B | +| Retrieve_ImmDict | 100 | 8 | 78.83 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100 | 8 | 105.97 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 100 | 8 | 149.07 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 100 | 8 | 1,203.47 ns | - | - | - | - | +| Retrieve_ExtMap | 100 | 8 | 1,297.20 ns | - | - | - | - | +| Retrieve_ExtHashMap | 100 | 8 | 189.50 ns | - | - | - | - | +| Update_ImmDict | 100 | 8 | 1,343.93 ns | 0.5207 | 0.0019 | - | 4368 B | +| Update_PersistentMap_Standard | 100 | 8 | 1,593.27 ns | 2.3994 | 0.0401 | - | 20080 B | +| Update_PersistentMap_Unicode | 100 | 8 | 1,913.07 ns | 2.7046 | 0.0496 | - | 22640 B | +| Update_TransientMap_Standard | 100 | 8 | 520.49 ns | 0.4339 | 0.0048 | - | 3632 B | +| Update_TransientMap_Unicode | 100 | 8 | 690.74 ns | 0.4644 | 0.0057 | - | 3888 B | +| Update_ImmSortedDict | 100 | 8 | 1,748.06 ns | 0.3662 | - | - | 3072 B | +| Update_ExtMap | 100 | 8 | 1,793.32 ns | 0.4120 | - | - | 3456 B | +| Update_ExtHashMap | 100 | 8 | 754.97 ns | 0.5264 | 0.0010 | - | 4408 B | +| UpdateSet_ImmDict | 100 | 8 | 1,361.32 ns | 0.5207 | 0.0019 | - | 4368 B | +| UpdateSet_PersistentMap_Standard | 100 | 8 | 1,838.15 ns | 2.3994 | 0.0420 | - | 20080 B | +| UpdateSet_PersistentMap_Unicode | 100 | 8 | 2,427.91 ns | 2.7046 | 0.0534 | - | 22640 B | +| UpdateSet_TransientMap_Standard | 100 | 8 | 711.17 ns | 0.4339 | 0.0048 | - | 3632 B | +| UpdateSet_TransientMap_Unicode | 100 | 8 | 1,247.32 ns | 0.4635 | 0.0057 | - | 3888 B | +| UpdateSet_ImmSortedDict | 100 | 8 | 2,399.76 ns | 0.4578 | - | - | 3840 B | +| UpdateSet_ExtMap | 100 | 8 | 2,202.70 ns | 0.4730 | - | - | 3960 B | +| UpdateSet_ExtHashMap | 100 | 8 | 773.22 ns | 0.5274 | 0.0019 | - | 4416 B | +| Iterate_ImmDict | 100 | 8 | 1,335.18 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 100 | 8 | 189.19 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100 | 8 | 485.69 ns | - | - | - | - | +| Iterate_ExtMap | 100 | 8 | 327.67 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 100 | 8 | 1,089.89 ns | 0.2480 | - | - | 2088 B | +| Iterate_PersistentMap_Unicode | 100 | 8 | 187.62 ns | - | - | - | - | +| Remove_ImmDict | 100 | 8 | 1,272.43 ns | 0.5131 | - | - | 4304 B | +| Remove_PersistentMap_Standard | 100 | 8 | 1,969.67 ns | 2.3689 | 0.0381 | - | 19840 B | +| Remove_PersistentMap_Unicode | 100 | 8 | 2,487.99 ns | 2.6779 | 0.0496 | - | 22400 B | +| Remove_TransientMap_Standard | 100 | 8 | 836.95 ns | 0.4101 | 0.0048 | - | 3432 B | +| Remove_TransientMap_Unicode | 100 | 8 | 1,333.99 ns | 0.4406 | 0.0057 | - | 3688 B | +| Remove_ImmSortedDict | 100 | 8 | 1,946.19 ns | 0.3777 | - | - | 3168 B | +| Remove_ExtMap | 100 | 8 | 2,221.31 ns | 0.5798 | - | - | 4856 B | +| Remove_ExtHashMap | 100 | 8 | 770.43 ns | 0.4721 | 0.0019 | - | 3952 B | +| **Build_TransientMap_Standard** | **100** | **50** | **5,216.44 ns** | **0.7401** | **0.0076** | **-** | **6200 B** | +| Build_TransientMap_Unicode | 100 | 50 | 11,944.76 ns | 0.8850 | 0.0153 | - | 7528 B | +| Build_ImmDict | 100 | 50 | 15,771.58 ns | 5.3101 | 0.0610 | - | 44576 B | +| Build_ImmSortedDict | 100 | 50 | 22,171.62 ns | 4.2114 | 0.0305 | - | 35232 B | +| Build_ExtMap | 100 | 50 | 27,705.49 ns | 5.6458 | 0.0610 | - | 47328 B | +| Build_ExtHashMap | 100 | 50 | 15,207.38 ns | 4.4861 | 0.0305 | - | 37672 B | +| Retrieve_ImmDict | 100 | 50 | 286.21 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100 | 50 | 103.79 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 100 | 50 | 151.04 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 100 | 50 | 1,317.60 ns | - | - | - | - | +| Retrieve_ExtMap | 100 | 50 | 1,162.96 ns | - | - | - | - | +| Retrieve_ExtHashMap | 100 | 50 | 412.76 ns | - | - | - | - | +| Update_ImmDict | 100 | 50 | 1,359.95 ns | 0.4368 | - | - | 3664 B | +| Update_PersistentMap_Standard | 100 | 50 | 1,583.22 ns | 2.3994 | 0.0420 | - | 20080 B | +| Update_PersistentMap_Unicode | 100 | 50 | 1,861.53 ns | 2.7065 | 0.0515 | - | 22640 B | +| Update_TransientMap_Standard | 100 | 50 | 543.81 ns | 0.4339 | 0.0048 | - | 3632 B | +| Update_TransientMap_Unicode | 100 | 50 | 633.60 ns | 0.4644 | 0.0057 | - | 3888 B | +| Update_ImmSortedDict | 100 | 50 | 1,986.02 ns | 0.4120 | - | - | 3456 B | +| Update_ExtMap | 100 | 50 | 1,892.16 ns | 0.4253 | - | - | 3568 B | +| Update_ExtHashMap | 100 | 50 | 1,088.83 ns | 0.4997 | - | - | 4184 B | +| UpdateSet_ImmDict | 100 | 50 | 1,799.24 ns | 0.5817 | 0.0019 | - | 4880 B | +| UpdateSet_PersistentMap_Standard | 100 | 50 | 1,847.71 ns | 2.3994 | 0.0401 | - | 20080 B | +| UpdateSet_PersistentMap_Unicode | 100 | 50 | 2,510.91 ns | 2.7046 | 0.0496 | - | 22640 B | +| UpdateSet_TransientMap_Standard | 100 | 50 | 754.14 ns | 0.4339 | 0.0048 | - | 3632 B | +| UpdateSet_TransientMap_Unicode | 100 | 50 | 1,274.76 ns | 0.4635 | 0.0057 | - | 3888 B | +| UpdateSet_ImmSortedDict | 100 | 50 | 2,463.36 ns | 0.4730 | - | - | 3984 B | +| UpdateSet_ExtMap | 100 | 50 | 2,364.62 ns | 0.5913 | - | - | 4968 B | +| UpdateSet_ExtHashMap | 100 | 50 | 1,026.26 ns | 0.5074 | - | - | 4248 B | +| Iterate_ImmDict | 100 | 50 | 1,223.85 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 100 | 50 | 187.31 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100 | 50 | 484.33 ns | - | - | - | - | +| Iterate_ExtMap | 100 | 50 | 358.22 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 100 | 50 | 1,149.01 ns | 0.2575 | - | - | 2160 B | +| Iterate_PersistentMap_Unicode | 100 | 50 | 187.47 ns | - | - | - | - | +| Remove_ImmDict | 100 | 50 | 1,589.38 ns | 0.5283 | 0.0019 | - | 4432 B | +| Remove_PersistentMap_Standard | 100 | 50 | 1,976.63 ns | 2.3689 | 0.0381 | - | 19840 B | +| Remove_PersistentMap_Unicode | 100 | 50 | 2,568.69 ns | 2.6779 | 0.0458 | - | 22400 B | +| Remove_TransientMap_Standard | 100 | 50 | 839.46 ns | 0.4101 | 0.0048 | - | 3432 B | +| Remove_TransientMap_Unicode | 100 | 50 | 1,399.50 ns | 0.4406 | 0.0057 | - | 3688 B | +| Remove_ImmSortedDict | 100 | 50 | 2,069.17 ns | 0.3891 | - | - | 3264 B | +| Remove_ExtMap | 100 | 50 | 2,124.59 ns | 0.4387 | - | - | 3680 B | +| Remove_ExtHashMap | 100 | 50 | 1,029.42 ns | 0.5112 | - | - | 4288 B | +| **Build_TransientMap_Standard** | **1000** | **8** | **102,292.92 ns** | **5.7373** | **0.3662** | **-** | **48592 B** | +| Build_TransientMap_Unicode | 1000 | 8 | 172,854.88 ns | 7.3242 | 0.7324 | - | 62248 B | +| Build_ImmDict | 1000 | 8 | 247,732.93 ns | 79.1016 | 8.3008 | - | 662016 B | +| Build_ImmSortedDict | 1000 | 8 | 429,391.31 ns | 61.0352 | 4.8828 | - | 513312 B | +| Build_ExtMap | 1000 | 8 | 416,823.27 ns | 79.1016 | 7.3242 | - | 662448 B | +| Build_ExtHashMap | 1000 | 8 | 154,417.86 ns | 70.8008 | 4.6387 | - | 592920 B | +| Retrieve_ImmDict | 1000 | 8 | 1,043.45 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 1000 | 8 | 1,783.56 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 1000 | 8 | 2,026.44 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 1000 | 8 | 21,724.33 ns | - | - | - | - | +| Retrieve_ExtMap | 1000 | 8 | 24,401.52 ns | - | - | - | - | +| Retrieve_ExtHashMap | 1000 | 8 | 2,472.28 ns | - | - | - | - | +| Update_ImmDict | 1000 | 8 | 19,547.84 ns | 7.2937 | 0.2747 | - | 61024 B | +| Update_PersistentMap_Standard | 1000 | 8 | 19,846.20 ns | 23.9868 | 2.2278 | - | 200800 B | +| Update_PersistentMap_Unicode | 1000 | 8 | 27,309.69 ns | 27.0386 | 2.5330 | - | 226400 B | +| Update_TransientMap_Standard | 1000 | 8 | 6,888.79 ns | 4.3945 | 0.4501 | - | 36768 B | +| Update_TransientMap_Unicode | 1000 | 8 | 7,801.38 ns | 4.4250 | 0.4425 | - | 37024 B | +| Update_ImmSortedDict | 1000 | 8 | 32,192.51 ns | 5.7373 | 0.1221 | - | 48288 B | +| Update_ExtMap | 1000 | 8 | 31,972.71 ns | 6.4087 | 0.1831 | - | 53824 B | +| Update_ExtHashMap | 1000 | 8 | 10,476.16 ns | 7.2021 | 0.2289 | - | 60272 B | +| UpdateSet_ImmDict | 1000 | 8 | 22,622.37 ns | 8.2703 | 0.3967 | - | 69280 B | +| UpdateSet_PersistentMap_Standard | 1000 | 8 | 22,274.40 ns | 23.9868 | 2.3499 | - | 200800 B | +| UpdateSet_PersistentMap_Unicode | 1000 | 8 | 32,897.61 ns | 27.0386 | 2.6855 | - | 226400 B | +| UpdateSet_TransientMap_Standard | 1000 | 8 | 8,218.75 ns | 4.5624 | 0.4272 | - | 38176 B | +| UpdateSet_TransientMap_Unicode | 1000 | 8 | 12,847.07 ns | 4.5929 | 0.4883 | - | 38432 B | +| UpdateSet_ImmSortedDict | 1000 | 8 | 37,683.95 ns | 6.3477 | 0.2441 | - | 53232 B | +| UpdateSet_ExtMap | 1000 | 8 | 38,195.16 ns | 7.6904 | 0.3052 | - | 64576 B | +| UpdateSet_ExtHashMap | 1000 | 8 | 14,327.00 ns | 7.7057 | 0.2747 | - | 64480 B | +| Iterate_ImmDict | 1000 | 8 | 12,971.77 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 1000 | 8 | 1,615.06 ns | - | - | - | - | +| Iterate_ImmSortedDict | 1000 | 8 | 4,905.20 ns | - | - | - | - | +| Iterate_ExtMap | 1000 | 8 | 3,282.20 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 1000 | 8 | 14,214.03 ns | 2.6093 | - | - | 21888 B | +| Iterate_PersistentMap_Unicode | 1000 | 8 | 1,644.30 ns | - | - | - | - | +| Remove_ImmDict | 1000 | 8 | 19,504.24 ns | 7.5989 | 0.3052 | - | 63648 B | +| Remove_PersistentMap_Standard | 1000 | 8 | 24,227.34 ns | 23.7122 | 2.0752 | - | 198400 B | +| Remove_PersistentMap_Unicode | 1000 | 8 | 29,622.70 ns | 26.7639 | 2.4414 | - | 224000 B | +| Remove_TransientMap_Standard | 1000 | 8 | 9,476.68 ns | 3.7689 | 0.3510 | - | 31592 B | +| Remove_TransientMap_Unicode | 1000 | 8 | 14,237.22 ns | 3.7994 | 0.3662 | - | 31848 B | +| Remove_ImmSortedDict | 1000 | 8 | 33,197.78 ns | 5.6763 | 0.1221 | - | 47616 B | +| Remove_ExtMap | 1000 | 8 | 39,241.19 ns | 6.8970 | 0.1831 | - | 57744 B | +| Remove_ExtHashMap | 1000 | 8 | 11,374.77 ns | 7.5378 | 0.2136 | - | 63056 B | +| **Build_TransientMap_Standard** | **1000** | **50** | **104,538.75 ns** | **5.4932** | **0.3662** | **-** | **46784 B** | +| Build_TransientMap_Unicode | 1000 | 50 | 175,661.70 ns | 7.0801 | 0.4883 | - | 59368 B | +| Build_ImmDict | 1000 | 50 | 278,290.76 ns | 79.5898 | 8.7891 | - | 669696 B | +| Build_ImmSortedDict | 1000 | 50 | 417,827.13 ns | 61.0352 | 5.3711 | - | 511344 B | +| Build_ExtMap | 1000 | 50 | 409,994.29 ns | 78.1250 | 7.3242 | - | 656288 B | +| Build_ExtHashMap | 1000 | 50 | 181,354.06 ns | 70.3125 | 4.6387 | - | 588328 B | +| Retrieve_ImmDict | 1000 | 50 | 3,132.97 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 1000 | 50 | 1,794.95 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 1000 | 50 | 2,065.82 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 1000 | 50 | 21,834.49 ns | - | - | - | - | +| Retrieve_ExtMap | 1000 | 50 | 23,236.99 ns | - | - | - | - | +| Retrieve_ExtHashMap | 1000 | 50 | 4,931.32 ns | - | - | - | - | +| Update_ImmDict | 1000 | 50 | 22,234.72 ns | 7.5073 | 0.3052 | - | 62816 B | +| Update_PersistentMap_Standard | 1000 | 50 | 20,313.66 ns | 23.9868 | 2.0142 | - | 200800 B | +| Update_PersistentMap_Unicode | 1000 | 50 | 27,829.75 ns | 27.0386 | 2.2888 | - | 226400 B | +| Update_TransientMap_Standard | 1000 | 50 | 6,827.30 ns | 4.0588 | 0.4044 | - | 33952 B | +| Update_TransientMap_Unicode | 1000 | 50 | 7,654.44 ns | 4.0894 | 0.3967 | - | 34208 B | +| Update_ImmSortedDict | 1000 | 50 | 32,637.97 ns | 5.6763 | 0.1221 | - | 47952 B | +| Update_ExtMap | 1000 | 50 | 32,523.61 ns | 6.5308 | 0.1831 | - | 54720 B | +| Update_ExtHashMap | 1000 | 50 | 12,993.79 ns | 7.2479 | 0.2441 | - | 60720 B | +| UpdateSet_ImmDict | 1000 | 50 | 25,835.15 ns | 8.3313 | 0.3967 | - | 69728 B | +| UpdateSet_PersistentMap_Standard | 1000 | 50 | 22,970.95 ns | 24.1089 | 2.2278 | - | 201704 B | +| UpdateSet_PersistentMap_Unicode | 1000 | 50 | 34,226.66 ns | 27.2217 | 2.6245 | - | 227840 B | +| UpdateSet_TransientMap_Standard | 1000 | 50 | 7,701.61 ns | 4.3335 | 0.4578 | - | 36264 B | +| UpdateSet_TransientMap_Unicode | 1000 | 50 | 13,115.82 ns | 4.4250 | 0.4578 | - | 37056 B | +| UpdateSet_ImmSortedDict | 1000 | 50 | 37,636.40 ns | 6.2866 | 0.1831 | - | 53088 B | +| UpdateSet_ExtMap | 1000 | 50 | 38,985.88 ns | 7.8735 | 0.3052 | - | 66200 B | +| UpdateSet_ExtHashMap | 1000 | 50 | 17,008.89 ns | 7.6294 | 0.2441 | - | 63936 B | +| Iterate_ImmDict | 1000 | 50 | 13,396.48 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 1000 | 50 | 1,626.01 ns | - | - | - | - | +| Iterate_ImmSortedDict | 1000 | 50 | 4,912.18 ns | - | - | - | - | +| Iterate_ExtMap | 1000 | 50 | 3,126.26 ns | 0.0038 | - | - | 32 B | +| Iterate_ExtHashMap | 1000 | 50 | 14,857.12 ns | 2.7924 | - | - | 23472 B | +| Iterate_PersistentMap_Unicode | 1000 | 50 | 1,654.97 ns | - | - | - | - | +| Remove_ImmDict | 1000 | 50 | 22,385.84 ns | 7.6904 | 0.3052 | - | 64352 B | +| Remove_PersistentMap_Standard | 1000 | 50 | 25,269.13 ns | 23.7122 | 1.9531 | - | 198400 B | +| Remove_PersistentMap_Unicode | 1000 | 50 | 30,307.99 ns | 26.7639 | 2.2583 | - | 224000 B | +| Remove_TransientMap_Standard | 1000 | 50 | 9,482.18 ns | 3.2654 | 0.2747 | - | 27368 B | +| Remove_TransientMap_Unicode | 1000 | 50 | 13,754.52 ns | 3.2959 | 0.2899 | - | 27624 B | +| Remove_ImmSortedDict | 1000 | 50 | 32,695.64 ns | 5.6763 | 0.1221 | - | 47664 B | +| Remove_ExtMap | 1000 | 50 | 37,495.23 ns | 6.9580 | 0.2441 | - | 58640 B | +| Remove_ExtHashMap | 1000 | 50 | 14,713.28 ns | 7.9193 | 0.2594 | - | 66264 B | +| **Build_TransientMap_Standard** | **10000** | **8** | **1,680,964.28 ns** | **52.7344** | **15.6250** | **-** | **452352 B** | +| Build_TransientMap_Unicode | 10000 | 8 | 2,275,404.91 ns | 66.4063 | 19.5313 | - | 576584 B | +| Build_ImmDict | 10000 | 8 | 4,364,880.86 ns | 1046.8750 | 507.8125 | - | 8766016 B | +| Build_ImmSortedDict | 10000 | 8 | 6,551,472.85 ns | 804.6875 | 281.2500 | - | 6767232 B | +| Build_ExtMap | 10000 | 8 | 6,411,766.35 ns | 1015.6250 | 437.5000 | - | 8542480 B | +| Build_ExtHashMap | 10000 | 8 | 1,913,707.20 ns | 945.3125 | 320.3125 | - | 7912992 B | +| Retrieve_ImmDict | 10000 | 8 | 23,538.66 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 10000 | 8 | 44,522.29 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 10000 | 8 | 33,878.51 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 10000 | 8 | 385,003.57 ns | - | - | - | - | +| Retrieve_ExtMap | 10000 | 8 | 415,526.12 ns | - | - | - | - | +| Retrieve_ExtHashMap | 10000 | 8 | 38,484.21 ns | - | - | - | - | +| Update_ImmDict | 10000 | 8 | 395,683.89 ns | 101.5625 | 26.3672 | - | 849792 B | +| Update_PersistentMap_Standard | 10000 | 8 | 348,111.33 ns | 304.6875 | 131.3477 | - | 2552000 B | +| Update_PersistentMap_Unicode | 10000 | 8 | 415,082.82 ns | 366.2109 | 160.1563 | - | 3064000 B | +| Update_TransientMap_Standard | 10000 | 8 | 138,925.69 ns | 40.5273 | 13.6719 | - | 339232 B | +| Update_TransientMap_Unicode | 10000 | 8 | 155,075.00 ns | 40.7715 | 16.6016 | - | 341792 B | +| Update_ImmSortedDict | 10000 | 8 | 576,796.03 ns | 76.1719 | 15.6250 | - | 640368 B | +| Update_ExtMap | 10000 | 8 | 566,570.69 ns | 87.8906 | 22.4609 | - | 735640 B | +| Update_ExtHashMap | 10000 | 8 | 169,597.22 ns | 103.0273 | 22.7051 | - | 862144 B | +| UpdateSet_ImmDict | 10000 | 8 | 430,876.44 ns | 108.3984 | 28.3203 | - | 907136 B | +| UpdateSet_PersistentMap_Standard | 10000 | 8 | 390,932.56 ns | 306.6406 | 136.7188 | - | 2566136 B | +| UpdateSet_PersistentMap_Unicode | 10000 | 8 | 485,184.77 ns | 368.6523 | 154.2969 | - | 3086432 B | +| UpdateSet_TransientMap_Standard | 10000 | 8 | 183,267.74 ns | 42.2363 | 16.3574 | - | 354776 B | +| UpdateSet_TransientMap_Unicode | 10000 | 8 | 222,767.01 ns | 43.7012 | 16.3574 | - | 365632 B | +| UpdateSet_ImmSortedDict | 10000 | 8 | 648,381.31 ns | 82.0313 | 19.5313 | - | 687648 B | +| UpdateSet_ExtMap | 10000 | 8 | 637,483.97 ns | 99.6094 | 25.3906 | - | 833752 B | +| UpdateSet_ExtHashMap | 10000 | 8 | 189,210.57 ns | 104.9805 | 23.6816 | - | 878600 B | +| Iterate_ImmDict | 10000 | 8 | 176,031.53 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 10000 | 8 | 17,218.51 ns | - | - | - | - | +| Iterate_ImmSortedDict | 10000 | 8 | 56,553.28 ns | - | - | - | - | +| Iterate_ExtMap | 10000 | 8 | 66,099.89 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 10000 | 8 | 175,854.49 ns | 20.0195 | - | - | 168696 B | +| Iterate_PersistentMap_Unicode | 10000 | 8 | 17,469.24 ns | - | - | - | - | +| Remove_ImmDict | 10000 | 8 | 395,433.28 ns | 102.5391 | 26.3672 | - | 858560 B | +| Remove_PersistentMap_Standard | 10000 | 8 | 417,267.98 ns | 302.2461 | 112.7930 | - | 2529408 B | +| Remove_PersistentMap_Unicode | 10000 | 8 | 489,859.89 ns | 363.2813 | 180.6641 | - | 3041408 B | +| Remove_TransientMap_Standard | 10000 | 8 | 198,908.02 ns | 37.8418 | 13.6719 | - | 316680 B | +| Remove_TransientMap_Unicode | 10000 | 8 | 223,427.67 ns | 38.0859 | 15.1367 | - | 319240 B | +| Remove_ImmSortedDict | 10000 | 8 | 602,358.94 ns | 77.1484 | 15.6250 | - | 652944 B | +| Remove_ExtMap | 10000 | 8 | 614,493.62 ns | 91.7969 | 20.5078 | - | 774000 B | +| Remove_ExtHashMap | 10000 | 8 | 179,222.20 ns | 104.0039 | 20.2637 | - | 870432 B | +| **Build_TransientMap_Standard** | **10000** | **50** | **1,799,942.31 ns** | **52.7344** | **13.6719** | **-** | **446352 B** | +| Build_TransientMap_Unicode | 10000 | 50 | 2,336,360.10 ns | 66.4063 | 23.4375 | - | 567112 B | +| Build_ImmDict | 10000 | 50 | 4,618,106.24 ns | 1046.8750 | 515.6250 | - | 8772288 B | +| Build_ImmSortedDict | 10000 | 50 | 6,614,766.71 ns | 804.6875 | 265.6250 | - | 6751584 B | +| Build_ExtMap | 10000 | 50 | 6,524,244.50 ns | 1015.6250 | 429.6875 | - | 8544720 B | +| Build_ExtHashMap | 10000 | 50 | 2,195,902.51 ns | 945.3125 | 312.5000 | - | 7921128 B | +| Retrieve_ImmDict | 10000 | 50 | 53,644.62 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 10000 | 50 | 51,772.04 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 10000 | 50 | 35,183.58 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 10000 | 50 | 384,954.19 ns | - | - | - | - | +| Retrieve_ExtMap | 10000 | 50 | 419,295.20 ns | - | - | - | - | +| Retrieve_ExtHashMap | 10000 | 50 | 68,364.13 ns | - | - | - | - | +| Update_ImmDict | 10000 | 50 | 416,499.71 ns | 100.5859 | 24.9023 | - | 842880 B | +| Update_PersistentMap_Standard | 10000 | 50 | 352,007.58 ns | 304.6875 | 131.3477 | - | 2552000 B | +| Update_PersistentMap_Unicode | 10000 | 50 | 419,485.01 ns | 366.2109 | 158.2031 | - | 3064000 B | +| Update_TransientMap_Standard | 10000 | 50 | 145,767.49 ns | 39.3066 | 13.6719 | - | 328832 B | +| Update_TransientMap_Unicode | 10000 | 50 | 152,884.18 ns | 39.5508 | 13.9160 | - | 331136 B | +| Update_ImmSortedDict | 10000 | 50 | 581,461.95 ns | 76.1719 | 14.6484 | - | 641616 B | +| Update_ExtMap | 10000 | 50 | 562,336.86 ns | 86.9141 | 20.5078 | - | 732896 B | +| Update_ExtHashMap | 10000 | 50 | 189,986.29 ns | 103.0273 | 22.2168 | - | 863280 B | +| UpdateSet_ImmDict | 10000 | 50 | 457,611.78 ns | 108.8867 | 30.7617 | - | 912128 B | +| UpdateSet_PersistentMap_Standard | 10000 | 50 | 400,214.07 ns | 306.6406 | 129.8828 | - | 2565560 B | +| UpdateSet_PersistentMap_Unicode | 10000 | 50 | 497,552.64 ns | 368.1641 | 152.3438 | - | 3085600 B | +| UpdateSet_TransientMap_Standard | 10000 | 50 | 191,918.16 ns | 41.0156 | 13.6719 | - | 343800 B | +| UpdateSet_TransientMap_Unicode | 10000 | 50 | 222,651.45 ns | 42.2363 | 15.8691 | - | 354144 B | +| UpdateSet_ImmSortedDict | 10000 | 50 | 656,130.18 ns | 82.0313 | 19.5313 | - | 691728 B | +| UpdateSet_ExtMap | 10000 | 50 | 644,220.45 ns | 98.6328 | 23.4375 | - | 829552 B | +| UpdateSet_ExtHashMap | 10000 | 50 | 213,717.48 ns | 104.7363 | 23.6816 | - | 876664 B | +| Iterate_ImmDict | 10000 | 50 | 172,329.11 ns | - | - | - | - | +| Iterate_PersistentMap_Standard | 10000 | 50 | 17,538.70 ns | - | - | - | - | +| Iterate_ImmSortedDict | 10000 | 50 | 52,626.12 ns | - | - | - | - | +| Iterate_ExtMap | 10000 | 50 | 75,440.14 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 10000 | 50 | 176,467.34 ns | 20.0195 | - | - | 168192 B | +| Iterate_PersistentMap_Unicode | 10000 | 50 | 17,931.81 ns | - | - | - | - | +| Remove_ImmDict | 10000 | 50 | 420,385.25 ns | 102.5391 | 26.3672 | - | 858432 B | +| Remove_PersistentMap_Standard | 10000 | 50 | 417,831.54 ns | 302.2461 | 113.7695 | - | 2530816 B | +| Remove_PersistentMap_Unicode | 10000 | 50 | 488,933.92 ns | 363.2813 | 158.2031 | - | 3042816 B | +| Remove_TransientMap_Standard | 10000 | 50 | 215,079.21 ns | 37.1094 | 13.4277 | - | 310504 B | +| Remove_TransientMap_Unicode | 10000 | 50 | 221,947.80 ns | 37.3535 | 13.1836 | - | 312808 B | +| Remove_ImmSortedDict | 10000 | 50 | 607,856.65 ns | 77.1484 | 15.6250 | - | 651840 B | +| Remove_ExtMap | 10000 | 50 | 628,754.81 ns | 91.7969 | 21.4844 | - | 771256 B | +| Remove_ExtHashMap | 10000 | 50 | 203,488.31 ns | 104.2480 | 21.4844 | - | 873560 B | +| **Build_TransientMap_Standard** | **100000** | **8** | **27,358,697.21 ns** | **531.2500** | **406.2500** | **-** | **4540216 B** | +| Build_TransientMap_Unicode | 100000 | 8 | 30,530,320.71 ns | 687.5000 | 593.7500 | - | 5798728 B | +| Build_ImmDict | 100000 | 8 | 90,877,970.56 ns | 13166.6667 | 4500.0000 | 166.6667 | 109369973 B | +| Build_ImmSortedDict | 100000 | 8 | 116,689,394.37 ns | 10000.0000 | 3800.0000 | - | 83946432 B | +| Build_ExtMap | 100000 | 8 | 117,813,711.80 ns | 12400.0000 | 5600.0000 | - | 104660688 B | +| Build_ExtHashMap | 100000 | 8 | 44,164,471.41 ns | 12000.0000 | 2583.3333 | 83.3333 | 99774201 B | +| Retrieve_ImmDict | 100000 | 8 | 1,475,338.26 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100000 | 8 | 1,774,506.52 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 100000 | 8 | 1,336,091.68 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 100000 | 8 | 6,136,773.68 ns | - | - | - | - | +| Retrieve_ExtMap | 100000 | 8 | 6,552,400.29 ns | - | - | - | - | +| Retrieve_ExtHashMap | 100000 | 8 | 1,008,726.75 ns | - | - | - | - | +| Update_ImmDict | 100000 | 8 | 7,725,041.14 ns | 1265.6250 | 1070.3125 | - | 10622656 B | +| Update_PersistentMap_Standard | 100000 | 8 | 9,751,403.12 ns | 3734.3750 | 2984.3750 | 46.8750 | 30960042 B | +| Update_PersistentMap_Unicode | 100000 | 8 | 11,106,373.87 ns | 4671.8750 | 2468.7500 | 62.5000 | 38640073 B | +| Update_TransientMap_Standard | 100000 | 8 | 3,072,594.67 ns | 410.1563 | 347.6563 | - | 3458240 B | +| Update_TransientMap_Unicode | 100000 | 8 | 2,678,497.53 ns | 414.0625 | 351.5625 | - | 3487168 B | +| Update_ImmSortedDict | 100000 | 8 | 9,958,696.31 ns | 953.1250 | 781.2500 | - | 8020080 B | +| Update_ExtMap | 100000 | 8 | 10,091,372.46 ns | 1093.7500 | 937.5000 | - | 9246400 B | +| Update_ExtHashMap | 100000 | 8 | 3,713,620.94 ns | 1269.5313 | 996.0938 | - | 10625016 B | +| UpdateSet_ImmDict | 100000 | 8 | 8,447,925.20 ns | 1343.7500 | 1062.5000 | - | 11239232 B | +| UpdateSet_PersistentMap_Standard | 100000 | 8 | 10,256,666.84 ns | 3750.0000 | 2609.3750 | 46.8750 | 31066796 B | +| UpdateSet_PersistentMap_Unicode | 100000 | 8 | 12,170,123.49 ns | 4687.5000 | 2250.0000 | 62.5000 | 38809425 B | +| UpdateSet_TransientMap_Standard | 100000 | 8 | 3,437,362.03 ns | 421.8750 | 351.5625 | - | 3545288 B | +| UpdateSet_TransientMap_Unicode | 100000 | 8 | 3,401,903.92 ns | 433.5938 | 386.7188 | - | 3636832 B | +| UpdateSet_ImmSortedDict | 100000 | 8 | 11,287,098.80 ns | 1015.6250 | 875.0000 | - | 8495856 B | +| UpdateSet_ExtMap | 100000 | 8 | 11,577,275.47 ns | 1218.7500 | 1015.6250 | - | 10287328 B | +| UpdateSet_ExtHashMap | 100000 | 8 | 3,795,803.13 ns | 1285.1563 | 996.0938 | - | 10752160 B | +| Iterate_ImmDict | 100000 | 8 | 2,176,936.15 ns | - | - | - | 192 B | +| Iterate_PersistentMap_Standard | 100000 | 8 | 240,376.55 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100000 | 8 | 713,292.36 ns | - | - | - | - | +| Iterate_ExtMap | 100000 | 8 | 1,041,016.32 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 100000 | 8 | 2,609,178.31 ns | 277.3438 | - | - | 2321200 B | +| Iterate_PersistentMap_Unicode | 100000 | 8 | 228,758.17 ns | - | - | - | - | +| Remove_ImmDict | 100000 | 8 | 7,633,500.34 ns | 1281.2500 | 1070.3125 | - | 10733056 B | +| Remove_PersistentMap_Standard | 100000 | 8 | 10,634,178.02 ns | 3718.7500 | 3093.7500 | 46.8750 | 30732714 B | +| Remove_PersistentMap_Unicode | 100000 | 8 | 11,815,412.68 ns | 4640.6250 | 2875.0000 | 62.5000 | 38412734 B | +| Remove_TransientMap_Standard | 100000 | 8 | 3,501,181.09 ns | 378.9063 | 312.5000 | - | 3197160 B | +| Remove_TransientMap_Unicode | 100000 | 8 | 3,113,607.62 ns | 382.8125 | 324.2188 | - | 3226088 B | +| Remove_ImmSortedDict | 100000 | 8 | 10,204,663.55 ns | 953.1250 | 781.2500 | - | 8031888 B | +| Remove_ExtMap | 100000 | 8 | 11,026,369.80 ns | 1140.6250 | 937.5000 | - | 9630448 B | +| Remove_ExtHashMap | 100000 | 8 | 3,801,747.29 ns | 1265.6250 | 937.5000 | - | 10608872 B | +| **Build_TransientMap_Standard** | **100000** | **50** | **31,414,873.58 ns** | **500.0000** | **312.5000** | **-** | **4518440 B** | +| Build_TransientMap_Unicode | 100000 | 50 | 34,272,872.95 ns | 666.6667 | 466.6667 | - | 5763784 B | +| Build_ImmDict | 100000 | 50 | 89,991,315.99 ns | 13166.6667 | 4833.3333 | 166.6667 | 109418176 B | +| Build_ImmSortedDict | 100000 | 50 | 131,306,574.64 ns | 10000.0000 | 4000.0000 | - | 83731632 B | +| Build_ExtMap | 100000 | 50 | 128,889,148.68 ns | 12250.0000 | 5250.0000 | - | 104371560 B | +| Build_ExtHashMap | 100000 | 50 | 48,924,428.69 ns | 11909.0909 | 2272.7273 | - | 99798944 B | +| Retrieve_ImmDict | 100000 | 50 | 1,789,773.89 ns | - | - | - | - | +| Retrieve_PersistentMap_Standard | 100000 | 50 | 2,280,540.33 ns | - | - | - | - | +| Retrieve_PersistentMap_Unicode | 100000 | 50 | 1,447,748.76 ns | - | - | - | - | +| Retrieve_ImmSortedDict | 100000 | 50 | 7,097,150.11 ns | - | - | - | - | +| Retrieve_ExtMap | 100000 | 50 | 7,013,962.09 ns | - | - | - | - | +| Retrieve_ExtHashMap | 100000 | 50 | 1,327,343.45 ns | - | - | - | - | +| Update_ImmDict | 100000 | 50 | 8,075,323.36 ns | 1265.6250 | 1062.5000 | - | 10593088 B | +| Update_PersistentMap_Standard | 100000 | 50 | 11,441,201.31 ns | 3734.3750 | 3421.8750 | 46.8750 | 30960051 B | +| Update_PersistentMap_Unicode | 100000 | 50 | 11,646,138.25 ns | 4656.2500 | 2765.6250 | 46.8750 | 38640052 B | +| Update_TransientMap_Standard | 100000 | 50 | 3,785,331.39 ns | 406.2500 | 347.6563 | - | 3410592 B | +| Update_TransientMap_Unicode | 100000 | 50 | 2,931,528.16 ns | 410.1563 | 343.7500 | - | 3440288 B | +| Update_ImmSortedDict | 100000 | 50 | 11,055,325.40 ns | 953.1250 | 781.2500 | - | 8012928 B | +| Update_ExtMap | 100000 | 50 | 10,703,171.07 ns | 1093.7500 | 906.2500 | - | 9233632 B | +| Update_ExtHashMap | 100000 | 50 | 4,100,682.76 ns | 1265.6250 | 976.5625 | - | 10623184 B | +| UpdateSet_ImmDict | 100000 | 50 | 8,848,378.05 ns | 1343.7500 | 1046.8750 | - | 11255552 B | +| UpdateSet_PersistentMap_Standard | 100000 | 50 | 12,223,495.13 ns | 3734.3750 | 2640.6250 | 31.2500 | 31083384 B | +| UpdateSet_PersistentMap_Unicode | 100000 | 50 | 12,917,217.90 ns | 4687.5000 | 2437.5000 | 46.8750 | 38835954 B | +| UpdateSet_TransientMap_Standard | 100000 | 50 | 4,490,436.88 ns | 414.0625 | 343.7500 | - | 3528320 B | +| UpdateSet_TransientMap_Unicode | 100000 | 50 | 3,756,285.03 ns | 433.5938 | 367.1875 | - | 3630560 B | +| UpdateSet_ImmSortedDict | 100000 | 50 | 12,490,770.48 ns | 1015.6250 | 890.6250 | - | 8501136 B | +| UpdateSet_ExtMap | 100000 | 50 | 12,674,840.15 ns | 1218.7500 | 1015.6250 | - | 10265544 B | +| UpdateSet_ExtHashMap | 100000 | 50 | 4,395,939.08 ns | 1281.2500 | 992.1875 | - | 10756792 B | +| Iterate_ImmDict | 100000 | 50 | 2,416,834.88 ns | - | - | - | 96 B | +| Iterate_PersistentMap_Standard | 100000 | 50 | 214,203.41 ns | - | - | - | - | +| Iterate_ImmSortedDict | 100000 | 50 | 712,519.08 ns | - | - | - | - | +| Iterate_ExtMap | 100000 | 50 | 1,091,987.30 ns | - | - | - | 32 B | +| Iterate_ExtHashMap | 100000 | 50 | 2,669,431.61 ns | 273.4375 | - | - | 2314072 B | +| Iterate_PersistentMap_Unicode | 100000 | 50 | 211,247.16 ns | - | - | - | - | +| Remove_ImmDict | 100000 | 50 | 8,089,556.02 ns | 1281.2500 | 1031.2500 | - | 10743040 B | +| Remove_PersistentMap_Standard | 100000 | 50 | 11,874,296.78 ns | 3703.1250 | 2750.0000 | 31.2500 | 30722852 B | +| Remove_PersistentMap_Unicode | 100000 | 50 | 11,948,106.52 ns | 4625.0000 | 2859.3750 | 46.8750 | 38402877 B | +| Remove_TransientMap_Standard | 100000 | 50 | 4,337,489.41 ns | 375.0000 | 304.6875 | - | 3176264 B | +| Remove_TransientMap_Unicode | 100000 | 50 | 3,278,674.84 ns | 382.8125 | 312.5000 | - | 3205960 B | +| Remove_ImmSortedDict | 100000 | 50 | 11,338,022.46 ns | 953.1250 | 781.2500 | - | 8032656 B | +| Remove_ExtMap | 100000 | 50 | 11,912,777.70 ns | 1140.6250 | 921.8750 | - | 9617232 B | +| Remove_ExtHashMap | 100000 | 50 | 4,223,945.68 ns | 1265.6250 | 937.5000 | - | 10621824 B |#+end_src Architecture Notes: Key Strategies From a6e8ced7f7402c43e3bf79881ab7392b58176619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Tue, 28 Apr 2026 21:05:43 +0200 Subject: [PATCH 17/22] Added int avx dispatch in internal nodes --- PersistentMap/BTreeFunctions.cs | 12 +++- PersistentMap/KeyStrategies/IntScanner.cs | 78 +++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/PersistentMap/BTreeFunctions.cs b/PersistentMap/BTreeFunctions.cs index 51e4b08..ca199fc 100644 --- a/PersistentMap/BTreeFunctions.cs +++ b/PersistentMap/BTreeFunctions.cs @@ -210,7 +210,17 @@ namespace PersistentMap [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static int FindRoutingIndex(InternalNode node, K key, long keyPrefix, TStrategy strategy) where TStrategy : IKeyStrategy - { + { + +if (typeof(K) == typeof(int)) + { + Span keys = node.GetKeys(); + ref K firstKeyRef = ref MemoryMarshal.GetReference(keys); + ref int firstIntRef = ref Unsafe.As(ref firstKeyRef); + ReadOnlySpan intKeys = MemoryMarshal.CreateReadOnlySpan(ref firstIntRef, keys.Length); + int intKey = Unsafe.As(ref key); + return IntScanner.FindFirstGreater(intKeys, intKey); + } if (!strategy.UsesPrefixes) { return FallbackRoutingKeys(node.GetKeys(), key, strategy); diff --git a/PersistentMap/KeyStrategies/IntScanner.cs b/PersistentMap/KeyStrategies/IntScanner.cs index de7b691..3c19856 100644 --- a/PersistentMap/KeyStrategies/IntScanner.cs +++ b/PersistentMap/KeyStrategies/IntScanner.cs @@ -87,4 +87,82 @@ public static class IntScanner return LinearScan(keys.Slice(i), target) + i; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FindFirstGreater(ReadOnlySpan keys, int target) + { + if (!Avx2.IsSupported || keys.Length < 8) + return LinearScanGreater(keys, target); + + return Avx512F.IsSupported + ? ScanAvx512Greater(keys, target) + : ScanAvx2Greater(keys, target); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int LinearScanGreater(ReadOnlySpan keys, int target) + { + for (var i = 0; i < keys.Length; i++) + if (keys[i] > target) + return i; + return keys.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe int ScanAvx2Greater(ReadOnlySpan keys, int target) + { + // For > target, AVX2 CompareGreaterThan works directly without the (target - 1) offset + var vTarget = Vector256.Create(target); + var i = 0; + var len = keys.Length; + + for (; i <= len - 8; i += 8) + { + fixed (int* ptr = keys) + { + var vData = Avx2.LoadVector256(ptr + i); + var vResult = Avx2.CompareGreaterThan(vData, vTarget); + + var mask = (uint)Avx2.MoveMask(vResult.AsByte()); + + if (mask != 0) + { + return i + (BitOperations.TrailingZeroCount(mask) / 4); + } + } + } + + return LinearScanGreater(keys.Slice(i), target) + i; + } + +[MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe int ScanAvx512Greater(ReadOnlySpan keys, int target) + { + var vTarget = Vector512.Create(target); + var i = 0; + var len = keys.Length; + + for (; i <= len - 16; i += 16) + { + fixed (int* ptr = keys) + { + var vData = Avx512F.LoadVector512(ptr + i); + + // Use GreaterThan instead of GreaterThanOrEqual + var mask = Vector512.GreaterThan(vData, vTarget); + + if (mask != Vector512.Zero) + { + uint m = (uint)mask.ExtractMostSignificantBits(); + return i + BitOperations.TrailingZeroCount(m); + } + } + } + + return LinearScanGreater(keys.Slice(i), target) + i; + } } + + + + From b5b363ae9fbcdf6d6e6ac8907403803ca9235370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?linus=20bj=C3=B6rnstam?= Date: Tue, 28 Apr 2026 21:48:45 +0200 Subject: [PATCH 18/22] Changed some formatting --- PersistentMap/BTreeFunctions.cs | 20 ++++++++++++-------- PersistentMap/KeyStrategies/IntScanner.cs | 14 +++++++------- PersistentMap/Nodes.cs | 8 ++++---- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/PersistentMap/BTreeFunctions.cs b/PersistentMap/BTreeFunctions.cs index ca199fc..4a7a41e 100644 --- a/PersistentMap/BTreeFunctions.cs +++ b/PersistentMap/BTreeFunctions.cs @@ -10,9 +10,12 @@ namespace PersistentMap // Public API // --------------------------------------------------------- + /// TryGetValue tries to get the value at mapping key. If it finds the key it sets th + /// out var to value and returns true. public static bool TryGetValue(Node root, K key, TStrategy strategy, out V value) where TStrategy : IKeyStrategy { + // We always get a strategy to avoid branching already here long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; Node current = root; @@ -43,6 +46,7 @@ namespace PersistentMap { root = root.EnsureEditable(owner); + // Todo, this should really be made a tuple return value to not stress the GC var splitResult = InsertRecursive(root, key, value, strategy, owner, out countChanged); if (splitResult != null) @@ -212,7 +216,7 @@ namespace PersistentMap where TStrategy : IKeyStrategy { -if (typeof(K) == typeof(int)) + if (typeof(K) == typeof(int)) { Span keys = node.GetKeys(); ref K firstKeyRef = ref MemoryMarshal.GetReference(keys); @@ -391,13 +395,13 @@ if (typeof(K) == typeof(int)) right.SetCount(moveCount); if (insertIndex < splitPoint || (splitPoint == 0 && insertIndex == 0)) -{ - InsertIntoLeaf(left, insertIndex, key, value, strategy); -} -else -{ - InsertIntoLeaf(right, insertIndex - splitPoint, key, value, strategy); -} + { + InsertIntoLeaf(left, insertIndex, key, value, strategy); + } + else + { + InsertIntoLeaf(right, insertIndex - splitPoint, key, value, strategy); + } return new SplitResult(right, right.Keys[0]); } diff --git a/PersistentMap/KeyStrategies/IntScanner.cs b/PersistentMap/KeyStrategies/IntScanner.cs index 3c19856..325436d 100644 --- a/PersistentMap/KeyStrategies/IntScanner.cs +++ b/PersistentMap/KeyStrategies/IntScanner.cs @@ -44,7 +44,7 @@ public static class IntScanner { var vData = Avx2.LoadVector256(ptr + i); var vResult = Avx2.CompareGreaterThan(vData, vTarget); - + // MoveMask creates a 32-bit integer from the most significant bit of each byte. var mask = (uint)Avx2.MoveMask(vResult.AsByte()); @@ -73,7 +73,7 @@ public static class IntScanner fixed (int* ptr = keys) { var vData = Avx512F.LoadVector512(ptr + i); - + // Vector512 API is used directly here to cleanly get the mask var mask = Vector512.GreaterThanOrEqual(vData, vTarget); @@ -88,7 +88,7 @@ public static class IntScanner return LinearScan(keys.Slice(i), target) + i; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int FindFirstGreater(ReadOnlySpan keys, int target) { if (!Avx2.IsSupported || keys.Length < 8) @@ -107,7 +107,7 @@ public static class IntScanner return i; return keys.Length; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe int ScanAvx2Greater(ReadOnlySpan keys, int target) { @@ -122,7 +122,7 @@ public static class IntScanner { var vData = Avx2.LoadVector256(ptr + i); var vResult = Avx2.CompareGreaterThan(vData, vTarget); - + var mask = (uint)Avx2.MoveMask(vResult.AsByte()); if (mask != 0) @@ -135,7 +135,7 @@ public static class IntScanner return LinearScanGreater(keys.Slice(i), target) + i; } -[MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe int ScanAvx512Greater(ReadOnlySpan keys, int target) { var vTarget = Vector512.Create(target); @@ -147,7 +147,7 @@ public static class IntScanner fixed (int* ptr = keys) { var vData = Avx512F.LoadVector512(ptr + i); - + // Use GreaterThan instead of GreaterThanOrEqual var mask = Vector512.GreaterThan(vData, vTarget); diff --git a/PersistentMap/Nodes.cs b/PersistentMap/Nodes.cs index 1e1d6ba..59075c4 100644 --- a/PersistentMap/Nodes.cs +++ b/PersistentMap/Nodes.cs @@ -90,10 +90,10 @@ public abstract class Node } [MethodImpl(MethodImplOptions.AggressiveInlining)] -public PrefixInternalNode AsPrefixInternal() -{ - return Unsafe.As>(this); -} + public PrefixInternalNode AsPrefixInternal() + { + return Unsafe.As>(this); + } } public sealed class LeafNode : Node From bf8298658cffd14038547824ea8ed3bd09b3be2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?linus=20bj=C3=B6rnstam?= Date: Tue, 28 Apr 2026 21:52:24 +0200 Subject: [PATCH 19/22] started changing to valuetuple --- PersistentMap/BTreeFunctions.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/PersistentMap/BTreeFunctions.cs b/PersistentMap/BTreeFunctions.cs index 4a7a41e..4596c0a 100644 --- a/PersistentMap/BTreeFunctions.cs +++ b/PersistentMap/BTreeFunctions.cs @@ -333,11 +333,11 @@ namespace PersistentMap // Insertion Logic // --------------------------------------------------------- - private class SplitResult + private class SeplitResult { public Node NewNode; public K Separator; - public SplitResult(Node newNode, K separator) + public SeplitResult(Node newNode, K separator) { NewNode = newNode; Separator = separator; @@ -371,7 +371,7 @@ namespace PersistentMap leaf.SetCount(count + 1); } - private static SplitResult SplitLeaf(LeafNode left, int insertIndex, K key, V value, TStrategy strategy, OwnerId owner) + private static (Node, K) SplitLeaf(LeafNode left, int insertIndex, K key, V value, TStrategy strategy, OwnerId owner) where TStrategy : IKeyStrategy { var right = new LeafNode(owner, strategy.UsesPrefixes); @@ -403,7 +403,7 @@ namespace PersistentMap InsertIntoLeaf(right, insertIndex - splitPoint, key, value, strategy); } - return new SplitResult(right, right.Keys[0]); + return (right, right.Keys[0]); } private static void InsertIntoInternal(InternalNode node, int index, K separator, Node newChild, TStrategy strategy) @@ -437,7 +437,7 @@ namespace PersistentMap node.SetCount(count + 1); } - private static SplitResult SplitInternal(InternalNode left, int insertIndex, K separator, Node newChild, TStrategy strategy, OwnerId owner) + private static (Node, K) SplitInternal(InternalNode left, int insertIndex, K separator, Node newChild, TStrategy strategy, OwnerId owner) where TStrategy : IKeyStrategy { var right = strategy.UsesPrefixes @@ -477,7 +477,7 @@ namespace PersistentMap InsertIntoInternal(right, insertIndex - (splitPoint + 1), separator, newChild, strategy); } - return new SplitResult(right, upKey); + return (right, upKey); } // --------------------------------------------------------- From e3cec3423b64fa032a264b034544303d70b1166a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Thu, 7 May 2026 07:44:55 +0200 Subject: [PATCH 20/22] Rename because it is ordered --- NiceBtree.sln => PersistentOrderedMap.sln | 2 +- .../BTreeFunctions.cs | 2 +- .../BaseOrderedMap.cs | 10 +-- .../Iterator.cs | 2 +- .../KeyStrategies.cs | 2 +- .../KeyStrategies/ComparableStrategy.cs | 2 +- .../KeyStrategies/DoubleStrategy.cs | 2 +- .../KeyStrategies/IntScanner.cs | 2 +- .../KeyStrategies/IntStrategy.cs | 2 +- .../KeyStrategies/PrefixScanner.cs | 2 +- .../KeyStrategies/StandardStrategy.cs | 2 +- .../Nodes.cs | 2 +- .../PersistentOrderedMap.cs | 22 +++--- .../PersistentOrderedMap.csproj | 0 .../Readme.org | 0 .../TransientOrderedMap.cs | 12 ++-- TestProject1/FuzzTest.cs | 4 +- TestProject1/FuzzTestStandardStrategy.cs | 4 +- TestProject1/IteratorTests.cs | 8 +-- TestProject1/OrderedQueriesTest.cs | 4 +- TestProject1/PersistenceTests.cs | 2 +- TestProject1/StandardStrategy.cs | 2 +- TestProject1/StressTest.cs | 2 +- TestProject1/TestProject1.csproj | 2 +- TestProject1/UnitTest1.cs | 2 +- benchmarks/MyBenchMarks/MyBenchMarks.csproj | 2 +- benchmarks/MyBenchMarks/Program.cs | 40 +++++------ benchmarks/MyBenchMarks/StringBenchmarks.cs | 70 +++++++++---------- 28 files changed, 104 insertions(+), 104 deletions(-) rename NiceBtree.sln => PersistentOrderedMap.sln (95%) rename {PersistentMap => PersistentOrderedMap}/BTreeFunctions.cs (99%) rename {PersistentMap => PersistentOrderedMap}/BaseOrderedMap.cs (93%) rename {PersistentMap => PersistentOrderedMap}/Iterator.cs (99%) rename {PersistentMap => PersistentOrderedMap}/KeyStrategies.cs (98%) rename {PersistentMap => PersistentOrderedMap}/KeyStrategies/ComparableStrategy.cs (94%) rename {PersistentMap => PersistentOrderedMap}/KeyStrategies/DoubleStrategy.cs (97%) rename {PersistentMap => PersistentOrderedMap}/KeyStrategies/IntScanner.cs (99%) rename {PersistentMap => PersistentOrderedMap}/KeyStrategies/IntStrategy.cs (93%) rename {PersistentMap => PersistentOrderedMap}/KeyStrategies/PrefixScanner.cs (99%) rename {PersistentMap => PersistentOrderedMap}/KeyStrategies/StandardStrategy.cs (98%) rename {PersistentMap => PersistentOrderedMap}/Nodes.cs (99%) rename PersistentMap/PersistentMap.cs => PersistentOrderedMap/PersistentOrderedMap.cs (53%) rename PersistentMap/PersistentMap.csproj => PersistentOrderedMap/PersistentOrderedMap.csproj (100%) rename {PersistentMap => PersistentOrderedMap}/Readme.org (100%) rename PersistentMap/TransientMap.cs => PersistentOrderedMap/TransientOrderedMap.cs (63%) diff --git a/NiceBtree.sln b/PersistentOrderedMap.sln similarity index 95% rename from NiceBtree.sln rename to PersistentOrderedMap.sln index 4da8777..d13d62d 100644 --- a/NiceBtree.sln +++ b/PersistentOrderedMap.sln @@ -2,7 +2,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B0432C7A-80E2-4EA6-8FAB-B8F23A8C39DE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PersistentMap", "PersistentMap\PersistentMap.csproj", "{CA49AA3C-0CE6-4735-887F-FB3631D63CEE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PersistentOrderedMap", "PersistentOrderedMap\PersistentOrderedMap.csproj", "{CA49AA3C-0CE6-4735-887F-FB3631D63CEE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject1", "TestProject1\TestProject1.csproj", "{9E499000-5E37-42F8-89D2-E18A53F0EF0C}" EndProject diff --git a/PersistentMap/BTreeFunctions.cs b/PersistentOrderedMap/BTreeFunctions.cs similarity index 99% rename from PersistentMap/BTreeFunctions.cs rename to PersistentOrderedMap/BTreeFunctions.cs index 4a7a41e..2831170 100644 --- a/PersistentMap/BTreeFunctions.cs +++ b/PersistentOrderedMap/BTreeFunctions.cs @@ -2,7 +2,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace PersistentMap +namespace PersistentOrderedMap { public static class BTreeFunctions { diff --git a/PersistentMap/BaseOrderedMap.cs b/PersistentOrderedMap/BaseOrderedMap.cs similarity index 93% rename from PersistentMap/BaseOrderedMap.cs rename to PersistentOrderedMap/BaseOrderedMap.cs index 613bca0..c3838eb 100644 --- a/PersistentMap/BaseOrderedMap.cs +++ b/PersistentOrderedMap/BaseOrderedMap.cs @@ -1,6 +1,6 @@ using System.Collections; -namespace PersistentMap; +namespace PersistentOrderedMap; public abstract class BaseOrderedMap : IEnumerable> where TStrategy : IKeyStrategy { @@ -37,17 +37,17 @@ public abstract class BaseOrderedMap : IEnumerable Create(TStrategy strategy) + public static PersistentOrderedMap Create(TStrategy strategy) { // Start with an empty leaf owned by None so the first write triggers CoW. var emptyRoot = new LeafNode(OwnerId.None, strategy.UsesPrefixes); - return new PersistentMap(emptyRoot, strategy, 0); + return new PersistentOrderedMap(emptyRoot, strategy, 0); } - public static TransientMap CreateTransient(TStrategy strategy) + public static TransientOrderedMap CreateTransient(TStrategy strategy) { var emptyRoot = new LeafNode(OwnerId.None, strategy.UsesPrefixes); - return new TransientMap(emptyRoot, strategy,0); + return new TransientOrderedMap(emptyRoot, strategy,0); } diff --git a/PersistentMap/Iterator.cs b/PersistentOrderedMap/Iterator.cs similarity index 99% rename from PersistentMap/Iterator.cs rename to PersistentOrderedMap/Iterator.cs index f6ace28..4adaebc 100644 --- a/PersistentMap/Iterator.cs +++ b/PersistentOrderedMap/Iterator.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Runtime.CompilerServices; -namespace PersistentMap; +namespace PersistentOrderedMap; diff --git a/PersistentMap/KeyStrategies.cs b/PersistentOrderedMap/KeyStrategies.cs similarity index 98% rename from PersistentMap/KeyStrategies.cs rename to PersistentOrderedMap/KeyStrategies.cs index f355921..f960d1d 100644 --- a/PersistentMap/KeyStrategies.cs +++ b/PersistentOrderedMap/KeyStrategies.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace PersistentMap; +namespace PersistentOrderedMap; using System; using System.Buffers.Binary; diff --git a/PersistentMap/KeyStrategies/ComparableStrategy.cs b/PersistentOrderedMap/KeyStrategies/ComparableStrategy.cs similarity index 94% rename from PersistentMap/KeyStrategies/ComparableStrategy.cs rename to PersistentOrderedMap/KeyStrategies/ComparableStrategy.cs index d0212f6..a9aa726 100644 --- a/PersistentMap/KeyStrategies/ComparableStrategy.cs +++ b/PersistentOrderedMap/KeyStrategies/ComparableStrategy.cs @@ -1,4 +1,4 @@ -namespace PersistentMap; +namespace PersistentOrderedMap; using System.Runtime.CompilerServices; diff --git a/PersistentMap/KeyStrategies/DoubleStrategy.cs b/PersistentOrderedMap/KeyStrategies/DoubleStrategy.cs similarity index 97% rename from PersistentMap/KeyStrategies/DoubleStrategy.cs rename to PersistentOrderedMap/KeyStrategies/DoubleStrategy.cs index b62090e..627c9a8 100644 --- a/PersistentMap/KeyStrategies/DoubleStrategy.cs +++ b/PersistentOrderedMap/KeyStrategies/DoubleStrategy.cs @@ -1,4 +1,4 @@ -namespace PersistentMap; +namespace PersistentOrderedMap; using System.Runtime.CompilerServices; public struct DoubleStrategy : IKeyStrategy diff --git a/PersistentMap/KeyStrategies/IntScanner.cs b/PersistentOrderedMap/KeyStrategies/IntScanner.cs similarity index 99% rename from PersistentMap/KeyStrategies/IntScanner.cs rename to PersistentOrderedMap/KeyStrategies/IntScanner.cs index 325436d..97afa3c 100644 --- a/PersistentMap/KeyStrategies/IntScanner.cs +++ b/PersistentOrderedMap/KeyStrategies/IntScanner.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace PersistentMap; +namespace PersistentOrderedMap; public static class IntScanner { diff --git a/PersistentMap/KeyStrategies/IntStrategy.cs b/PersistentOrderedMap/KeyStrategies/IntStrategy.cs similarity index 93% rename from PersistentMap/KeyStrategies/IntStrategy.cs rename to PersistentOrderedMap/KeyStrategies/IntStrategy.cs index f6133e2..5e29db0 100644 --- a/PersistentMap/KeyStrategies/IntStrategy.cs +++ b/PersistentOrderedMap/KeyStrategies/IntStrategy.cs @@ -1,4 +1,4 @@ -namespace PersistentMap; +namespace PersistentOrderedMap; using System.Runtime.CompilerServices; diff --git a/PersistentMap/KeyStrategies/PrefixScanner.cs b/PersistentOrderedMap/KeyStrategies/PrefixScanner.cs similarity index 99% rename from PersistentMap/KeyStrategies/PrefixScanner.cs rename to PersistentOrderedMap/KeyStrategies/PrefixScanner.cs index 9f6d907..9364998 100644 --- a/PersistentMap/KeyStrategies/PrefixScanner.cs +++ b/PersistentOrderedMap/KeyStrategies/PrefixScanner.cs @@ -1,4 +1,4 @@ -namespace PersistentMap; +namespace PersistentOrderedMap; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; diff --git a/PersistentMap/KeyStrategies/StandardStrategy.cs b/PersistentOrderedMap/KeyStrategies/StandardStrategy.cs similarity index 98% rename from PersistentMap/KeyStrategies/StandardStrategy.cs rename to PersistentOrderedMap/KeyStrategies/StandardStrategy.cs index 94cc05c..aefca25 100644 --- a/PersistentMap/KeyStrategies/StandardStrategy.cs +++ b/PersistentOrderedMap/KeyStrategies/StandardStrategy.cs @@ -1,4 +1,4 @@ -namespace PersistentMap; +namespace PersistentOrderedMap; using System.Runtime.CompilerServices; /// diff --git a/PersistentMap/Nodes.cs b/PersistentOrderedMap/Nodes.cs similarity index 99% rename from PersistentMap/Nodes.cs rename to PersistentOrderedMap/Nodes.cs index 59075c4..8e1cc76 100644 --- a/PersistentMap/Nodes.cs +++ b/PersistentOrderedMap/Nodes.cs @@ -1,7 +1,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace PersistentMap; +namespace PersistentOrderedMap; [Flags] public enum NodeFlags : byte diff --git a/PersistentMap/PersistentMap.cs b/PersistentOrderedMap/PersistentOrderedMap.cs similarity index 53% rename from PersistentMap/PersistentMap.cs rename to PersistentOrderedMap/PersistentOrderedMap.cs index c691918..5d45114 100644 --- a/PersistentMap/PersistentMap.cs +++ b/PersistentOrderedMap/PersistentOrderedMap.cs @@ -1,25 +1,25 @@ using System.Collections; -namespace PersistentMap; +namespace PersistentOrderedMap; -public sealed class PersistentMap : BaseOrderedMap, IEnumerable, IEnumerable> where TStrategy : IKeyStrategy +public sealed class PersistentOrderedMap : BaseOrderedMap, IEnumerable, IEnumerable> where TStrategy : IKeyStrategy { - internal PersistentMap(Node root, TStrategy strategy, int count) + internal PersistentOrderedMap(Node root, TStrategy strategy, int count) : base(root, strategy, count) { } // --------------------------------------------------------- // Immutable Write API (Returns new Map) // --------------------------------------------------------- - public PersistentMap Set(K key, V value) + public PersistentOrderedMap Set(K key, V value) { // OPTIMIZATION: Use OwnerId.None (0). // This signals EnsureEditable to always copy the root path, // producing a new tree of nodes that also have OwnerId.None. var newRoot = BTreeFunctions.Set(_root, key, value, _strategy, OwnerId.None, out bool countChanged); - return new PersistentMap(newRoot, _strategy, countChanged ? Count + 1 : Count); + return new PersistentOrderedMap(newRoot, _strategy, countChanged ? Count + 1 : Count); } - public static PersistentMap Empty(TStrategy strategy) + public static PersistentOrderedMap Empty(TStrategy strategy) { // Create an empty Leaf Node. // 'default(OwnerId)' (usually 0) marks this node as Immutable/Persistent. @@ -27,18 +27,18 @@ public sealed class PersistentMap : BaseOrderedMap(default(OwnerId), strategy.UsesPrefixes); - return new PersistentMap(emptyRoot, strategy, 0); + return new PersistentOrderedMap(emptyRoot, strategy, 0); } - public PersistentMap Remove(K key) + public PersistentOrderedMap Remove(K key) { var newRoot = BTreeFunctions.Remove(_root, key, _strategy, OwnerId.None, out bool removed); if (!removed) return this; - return new PersistentMap(newRoot, _strategy, Count - 1); + return new PersistentOrderedMap(newRoot, _strategy, Count - 1); } - public TransientMap ToTransient() + public TransientOrderedMap ToTransient() { - return new TransientMap(_root, _strategy, Count); + return new TransientOrderedMap(_root, _strategy, Count); } } diff --git a/PersistentMap/PersistentMap.csproj b/PersistentOrderedMap/PersistentOrderedMap.csproj similarity index 100% rename from PersistentMap/PersistentMap.csproj rename to PersistentOrderedMap/PersistentOrderedMap.csproj diff --git a/PersistentMap/Readme.org b/PersistentOrderedMap/Readme.org similarity index 100% rename from PersistentMap/Readme.org rename to PersistentOrderedMap/Readme.org diff --git a/PersistentMap/TransientMap.cs b/PersistentOrderedMap/TransientOrderedMap.cs similarity index 63% rename from PersistentMap/TransientMap.cs rename to PersistentOrderedMap/TransientOrderedMap.cs index 568b20d..48d4248 100644 --- a/PersistentMap/TransientMap.cs +++ b/PersistentOrderedMap/TransientOrderedMap.cs @@ -1,13 +1,13 @@ using System.Collections; -namespace PersistentMap; +namespace PersistentOrderedMap; -public sealed class TransientMap : BaseOrderedMap where TStrategy : IKeyStrategy +public sealed class TransientOrderedMap : BaseOrderedMap where TStrategy : IKeyStrategy { // This is mutable, but we treat it as readonly for the ID generation logic usually. private OwnerId _transactionId; - public TransientMap(Node root, TStrategy strategy, int count) + public TransientOrderedMap(Node root, TStrategy strategy, int count) : base(root, strategy, count) { _transactionId = OwnerId.Next(); @@ -25,13 +25,13 @@ public sealed class TransientMap : BaseOrderedMap ToPersistent() + public PersistentOrderedMap ToPersistent() { // 1. Create the snapshot by copying all relevant information - var snapshot = new PersistentMap(_root, _strategy, Count); + var snapshot = new PersistentOrderedMap(_root, _strategy, Count); - // 2. Protect the snapshot from THIS TransientMap by getting a new ownerId + // 2. Protect the snapshot from THIS TransientOrderedMap by getting a new ownerId // so that future edits will be done by CoW _transactionId = OwnerId.Next(); diff --git a/TestProject1/FuzzTest.cs b/TestProject1/FuzzTest.cs index cded93b..643db48 100644 --- a/TestProject1/FuzzTest.cs +++ b/TestProject1/FuzzTest.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using Xunit; using Xunit.Abstractions; -using PersistentMap; +using PersistentOrderedMap; public class BTreeFuzzTests { @@ -137,7 +137,7 @@ public class BTreeFuzzTests } } - private void AssertConsistency(SortedDictionary expected, TransientMap actual) + private void AssertConsistency(SortedDictionary expected, TransientOrderedMap actual) { // 1. Count if (expected.Count != actual.Count) diff --git a/TestProject1/FuzzTestStandardStrategy.cs b/TestProject1/FuzzTestStandardStrategy.cs index 69e598f..958498a 100644 --- a/TestProject1/FuzzTestStandardStrategy.cs +++ b/TestProject1/FuzzTestStandardStrategy.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using Xunit; using Xunit.Abstractions; -using PersistentMap; +using PersistentOrderedMap; public class BTreeFuzzTestStandardStrategy { @@ -137,7 +137,7 @@ public class BTreeFuzzTestStandardStrategy } } - private void AssertConsistency(SortedDictionary expected, TransientMap> actual) + private void AssertConsistency(SortedDictionary expected, TransientOrderedMap> actual) { // 1. Count if (expected.Count != actual.Count) diff --git a/TestProject1/IteratorTests.cs b/TestProject1/IteratorTests.cs index acd07e3..453e1af 100644 --- a/TestProject1/IteratorTests.cs +++ b/TestProject1/IteratorTests.cs @@ -1,5 +1,5 @@ using Xunit; -using PersistentMap; +using PersistentOrderedMap; using System.Linq; using System.Collections.Generic; @@ -8,8 +8,8 @@ public class EnumeratorTests // Use IntStrategy for simple numeric testing private readonly IntStrategy _strategy = new(); - // Helper to create a populated PersistentMap quickly - private PersistentMap CreateMap(params int[] keys) + // Helper to create a populated PersistentOrderedMap quickly + private PersistentOrderedMap CreateMap(params int[] keys) { var map = BaseOrderedMap.CreateTransient(_strategy); foreach (var k in keys) @@ -22,7 +22,7 @@ public class EnumeratorTests [Fact] public void EmptyMap_EnumeratesNothing() { - var map = PersistentMap.Empty(_strategy); + var map = PersistentOrderedMap.Empty(_strategy); var list = new List(); foreach(var kv in map) list.Add(kv.Key); diff --git a/TestProject1/OrderedQueriesTest.cs b/TestProject1/OrderedQueriesTest.cs index b9b996a..ac24d20 100644 --- a/TestProject1/OrderedQueriesTest.cs +++ b/TestProject1/OrderedQueriesTest.cs @@ -1,13 +1,13 @@ using System.Linq; using Xunit; -using PersistentMap; +using PersistentOrderedMap; namespace PersistentMap.Tests { public class BTreeExtendedOperationsTests { // Helper to quickly spin up a populated map - private TransientMap CreateMap(params int[] keys) + private TransientOrderedMap CreateMap(params int[] keys) { var map = BaseOrderedMap.CreateTransient(new IntStrategy()); foreach (var key in keys) diff --git a/TestProject1/PersistenceTests.cs b/TestProject1/PersistenceTests.cs index 713df90..78a163e 100644 --- a/TestProject1/PersistenceTests.cs +++ b/TestProject1/PersistenceTests.cs @@ -1,5 +1,5 @@ namespace TestProject1; -using PersistentMap; +using PersistentOrderedMap; public class PersistenceTests { private readonly UnicodeStrategy _strategy = new UnicodeStrategy(); diff --git a/TestProject1/StandardStrategy.cs b/TestProject1/StandardStrategy.cs index 912c7a7..b5467b0 100644 --- a/TestProject1/StandardStrategy.cs +++ b/TestProject1/StandardStrategy.cs @@ -1,5 +1,5 @@ namespace PersistentMap.Tests; -using PersistentMap; +using PersistentOrderedMap; using System.Linq; using Xunit; public class StandardStrategy diff --git a/TestProject1/StressTest.cs b/TestProject1/StressTest.cs index ef086c3..adcd6a7 100644 --- a/TestProject1/StressTest.cs +++ b/TestProject1/StressTest.cs @@ -1,6 +1,6 @@ namespace TestProject1; -using PersistentMap; +using PersistentOrderedMap; public class StressTests { diff --git a/TestProject1/TestProject1.csproj b/TestProject1/TestProject1.csproj index e36bb42..0aea003 100644 --- a/TestProject1/TestProject1.csproj +++ b/TestProject1/TestProject1.csproj @@ -19,7 +19,7 @@ - + \ No newline at end of file diff --git a/TestProject1/UnitTest1.cs b/TestProject1/UnitTest1.cs index a9a9d77..25f698a 100644 --- a/TestProject1/UnitTest1.cs +++ b/TestProject1/UnitTest1.cs @@ -1,7 +1,7 @@ namespace TestProject1; using Xunit; -using PersistentMap; +using PersistentOrderedMap; public class BasicTests { diff --git a/benchmarks/MyBenchMarks/MyBenchMarks.csproj b/benchmarks/MyBenchMarks/MyBenchMarks.csproj index a55ce4e..3e30a8f 100644 --- a/benchmarks/MyBenchMarks/MyBenchMarks.csproj +++ b/benchmarks/MyBenchMarks/MyBenchMarks.csproj @@ -13,7 +13,7 @@ - + diff --git a/benchmarks/MyBenchMarks/Program.cs b/benchmarks/MyBenchMarks/Program.cs index dcad462..a7acb3c 100644 --- a/benchmarks/MyBenchMarks/Program.cs +++ b/benchmarks/MyBenchMarks/Program.cs @@ -5,7 +5,7 @@ using System.Linq; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using LanguageExt; -using PersistentMap; +using PersistentOrderedMap; namespace MapBenchmarks; @@ -26,7 +26,7 @@ public class IntMapBenchmarks private ImmutableSortedDictionary _immSortedDict; private LanguageExt.Map _extMap; private LanguageExt.HashMap _extHashMap; - private PersistentMap _persistentMap; + private PersistentOrderedMap _persistentOrderedMap; private readonly IntStrategy _intStrategy = new IntStrategy(); @@ -65,7 +65,7 @@ public class IntMapBenchmarks var transient = BaseOrderedMap.CreateTransient(_intStrategy); foreach (var k in _allKeys) transient.Set(k, k); - _persistentMap = transient.ToPersistent(); + _persistentOrderedMap = transient.ToPersistent(); } // --- 1. BUILD --- @@ -103,15 +103,15 @@ public class IntMapBenchmarks } [Benchmark] - public PersistentMap Build_PersistentMap() + public PersistentOrderedMap Build_PersistentMap() { - var map = PersistentMap.Empty(_intStrategy); + var map = PersistentOrderedMap.Empty(_intStrategy); foreach (var k in _allKeys) map = map.Set(k, k); return map; } [Benchmark] - public PersistentMap Build_TransientMap() + public PersistentOrderedMap Build_TransientMap() { var map = BaseOrderedMap.CreateTransient(_intStrategy); foreach (var k in _allKeys) map.Set(k, k); @@ -161,7 +161,7 @@ public class IntMapBenchmarks { int count = 0; foreach (var k in _retrieveKeys) - if (_persistentMap.TryGetValue(k, out _)) count++; + if (_persistentOrderedMap.TryGetValue(k, out _)) count++; return count; } @@ -176,17 +176,17 @@ public class IntMapBenchmarks } [Benchmark] - public PersistentMap Update_PersistentMap() + public PersistentOrderedMap Update_PersistentMap() { - var map = _persistentMap; + var map = _persistentOrderedMap; foreach (var k in _updateKeys) map = map.Set(k, 999); return map; } [Benchmark] - public PersistentMap Update_TransientMap() + public PersistentOrderedMap Update_TransientMap() { - var transient = _persistentMap.ToTransient(); + var transient = _persistentOrderedMap.ToTransient(); foreach (var k in _updateKeys) transient.Set(k, 999); return transient.ToPersistent(); } @@ -226,17 +226,17 @@ public class IntMapBenchmarks } [Benchmark] - public PersistentMap UpdateSet_PersistentMap() + public PersistentOrderedMap UpdateSet_PersistentMap() { - var map = _persistentMap; + var map = _persistentOrderedMap; foreach (var k in _mixedKeys) map = map.Set(k, 999); return map; } [Benchmark] - public PersistentMap UpdateSet_TransientMap() + public PersistentOrderedMap UpdateSet_TransientMap() { - var transient = _persistentMap.ToTransient(); + var transient = _persistentOrderedMap.ToTransient(); foreach (var k in _mixedKeys) transient.Set(k, 999); return transient.ToPersistent(); } @@ -279,7 +279,7 @@ public class IntMapBenchmarks public int Iterate_PersistentMap() { int sum = 0; - foreach (var kvp in _persistentMap) sum += kvp.Value; + foreach (var kvp in _persistentOrderedMap) sum += kvp.Value; return sum; } @@ -319,16 +319,16 @@ public class IntMapBenchmarks } [Benchmark] - public PersistentMap Remove_PersistentMap() + public PersistentOrderedMap Remove_PersistentMap() { - var map = _persistentMap; + var map = _persistentOrderedMap; foreach (var k in _removeKeys) map = map.Remove(k); return map; } [Benchmark] - public PersistentMap Remove_TransientMap() + public PersistentOrderedMap Remove_TransientMap() { - var transient = _persistentMap.ToTransient(); + var transient = _persistentOrderedMap.ToTransient(); foreach (var k in _removeKeys) transient.Remove(k); return transient.ToPersistent(); } diff --git a/benchmarks/MyBenchMarks/StringBenchmarks.cs b/benchmarks/MyBenchMarks/StringBenchmarks.cs index 6f1dc30..f7caa42 100644 --- a/benchmarks/MyBenchMarks/StringBenchmarks.cs +++ b/benchmarks/MyBenchMarks/StringBenchmarks.cs @@ -4,7 +4,7 @@ using System.Collections.Immutable; using System.Linq; using BenchmarkDotNet.Attributes; using LanguageExt; -using PersistentMap; +using PersistentOrderedMap; using System.Runtime.CompilerServices; namespace MapBenchmarks; @@ -34,8 +34,8 @@ public class StringMapBenchmarks private LanguageExt.Map _extMap; private LanguageExt.HashMap _extHashMap; - private PersistentMap> _persistentMapStandard; - private PersistentMap _persistentMapUnicode; + private PersistentOrderedMap> _persistentOrderedMapStandard; + private PersistentOrderedMap _persistentOrderedMapUnicode; private readonly StandardStrategy2 _stdStrategy = new StandardStrategy2(new OrdinalComparer()); private readonly UnicodeStrategy _uniStrategy = new UnicodeStrategy(); @@ -84,8 +84,8 @@ public class StringMapBenchmarks transStd.Set(_allKeys[i], i); transUni.Set(_allKeys[i], i); } - _persistentMapStandard = transStd.ToPersistent(); - _persistentMapUnicode = transUni.ToPersistent(); + _persistentOrderedMapStandard = transStd.ToPersistent(); + _persistentOrderedMapUnicode = transUni.ToPersistent(); } private static string GenerateRandomString(int length, Random rnd) @@ -97,7 +97,7 @@ public class StringMapBenchmarks // --- 1. BUILD --- [Benchmark] - public PersistentMap> Build_TransientMap_Standard() + public PersistentOrderedMap> Build_TransientMap_Standard() { var map = BaseOrderedMap>.CreateTransient(_stdStrategy); for (int i = 0; i < _allKeys.Length; i++) map.Set(_allKeys[i], i); @@ -105,7 +105,7 @@ public class StringMapBenchmarks } [Benchmark] - public PersistentMap Build_TransientMap_Unicode() + public PersistentOrderedMap Build_TransientMap_Unicode() { var map = BaseOrderedMap.CreateTransient(_uniStrategy); for (int i = 0; i < _allKeys.Length; i++) map.Set(_allKeys[i], i); @@ -162,7 +162,7 @@ public class StringMapBenchmarks { int count = 0; foreach (var k in _retrieveKeys) - if (_persistentMapStandard.TryGetValue(k, out _)) count++; + if (_persistentOrderedMapStandard.TryGetValue(k, out _)) count++; return count; } @@ -171,7 +171,7 @@ public class StringMapBenchmarks { int count = 0; foreach (var k in _retrieveKeys) - if (_persistentMapUnicode.TryGetValue(k, out _)) count++; + if (_persistentOrderedMapUnicode.TryGetValue(k, out _)) count++; return count; } [Benchmark] @@ -213,32 +213,32 @@ public class StringMapBenchmarks } [Benchmark] - public PersistentMap> Update_PersistentMap_Standard() + public PersistentOrderedMap> Update_PersistentMap_Standard() { - var map = _persistentMapStandard; + var map = _persistentOrderedMapStandard; foreach (var k in _updateKeys) map = map.Set(k, 999); return map; } [Benchmark] - public PersistentMap Update_PersistentMap_Unicode() + public PersistentOrderedMap Update_PersistentMap_Unicode() { - var map = _persistentMapUnicode; + var map = _persistentOrderedMapUnicode; foreach (var k in _updateKeys) map = map.Set(k, 999); return map; } [Benchmark] - public PersistentMap> Update_TransientMap_Standard() + public PersistentOrderedMap> Update_TransientMap_Standard() { - var transient = _persistentMapStandard.ToTransient(); + var transient = _persistentOrderedMapStandard.ToTransient(); foreach (var k in _updateKeys) transient.Set(k, 999); return transient.ToPersistent(); } [Benchmark] - public PersistentMap Update_TransientMap_Unicode() + public PersistentOrderedMap Update_TransientMap_Unicode() { - var transient = _persistentMapUnicode.ToTransient(); + var transient = _persistentOrderedMapUnicode.ToTransient(); foreach (var k in _updateKeys) transient.Set(k, 999); return transient.ToPersistent(); } @@ -278,33 +278,33 @@ public class StringMapBenchmarks } [Benchmark] - public PersistentMap> UpdateSet_PersistentMap_Standard() + public PersistentOrderedMap> UpdateSet_PersistentMap_Standard() { - var map = _persistentMapStandard; + var map = _persistentOrderedMapStandard; foreach (var k in _mixedKeys) map = map.Set(k, 999); return map; } [Benchmark] - public PersistentMap UpdateSet_PersistentMap_Unicode() + public PersistentOrderedMap UpdateSet_PersistentMap_Unicode() { - var map = _persistentMapUnicode; + var map = _persistentOrderedMapUnicode; foreach (var k in _mixedKeys) map = map.Set(k, 999); return map; } [Benchmark] - public PersistentMap> UpdateSet_TransientMap_Standard() + public PersistentOrderedMap> UpdateSet_TransientMap_Standard() { - var transient = _persistentMapStandard.ToTransient(); + var transient = _persistentOrderedMapStandard.ToTransient(); foreach (var k in _mixedKeys) transient.Set(k, 999); return transient.ToPersistent(); } [Benchmark] - public PersistentMap UpdateSet_TransientMap_Unicode() + public PersistentOrderedMap UpdateSet_TransientMap_Unicode() { - var transient = _persistentMapUnicode.ToTransient(); + var transient = _persistentOrderedMapUnicode.ToTransient(); foreach (var k in _mixedKeys) transient.Set(k, 999); return transient.ToPersistent(); } @@ -347,7 +347,7 @@ public class StringMapBenchmarks public int Iterate_PersistentMap_Standard() { int sum = 0; - foreach (var kvp in _persistentMapStandard) sum += kvp.Value; + foreach (var kvp in _persistentOrderedMapStandard) sum += kvp.Value; return sum; } @@ -379,7 +379,7 @@ public class StringMapBenchmarks public int Iterate_PersistentMap_Unicode() { int sum = 0; - foreach (var kvp in _persistentMapUnicode) sum += kvp.Value; + foreach (var kvp in _persistentOrderedMapUnicode) sum += kvp.Value; return sum; } @@ -394,33 +394,33 @@ public class StringMapBenchmarks } [Benchmark] - public PersistentMap> Remove_PersistentMap_Standard() + public PersistentOrderedMap> Remove_PersistentMap_Standard() { - var map = _persistentMapStandard; + var map = _persistentOrderedMapStandard; foreach (var k in _removeKeys) map = map.Remove(k); return map; } [Benchmark] - public PersistentMap Remove_PersistentMap_Unicode() + public PersistentOrderedMap Remove_PersistentMap_Unicode() { - var map = _persistentMapUnicode; + var map = _persistentOrderedMapUnicode; foreach (var k in _removeKeys) map = map.Remove(k); return map; } [Benchmark] - public PersistentMap> Remove_TransientMap_Standard() + public PersistentOrderedMap> Remove_TransientMap_Standard() { - var transient = _persistentMapStandard.ToTransient(); + var transient = _persistentOrderedMapStandard.ToTransient(); foreach (var k in _removeKeys) transient.Remove(k); return transient.ToPersistent(); } [Benchmark] - public PersistentMap Remove_TransientMap_Unicode() + public PersistentOrderedMap Remove_TransientMap_Unicode() { - var transient = _persistentMapUnicode.ToTransient(); + var transient = _persistentOrderedMapUnicode.ToTransient(); foreach (var k in _removeKeys) transient.Remove(k); return transient.ToPersistent(); } From 7ee2238248e3a961a804f7e49f6e26c47573508c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Thu, 21 May 2026 12:29:20 +0200 Subject: [PATCH 21/22] speedups maybe changed Set function and Find(routing)Index) to specialize on class (no virtual dispatch) or using generics. --- PersistentOrderedMap/BTreeFunctions.cs | 34 +++++++++++++------------- PersistentOrderedMap/Iterator.cs | 5 +++- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/PersistentOrderedMap/BTreeFunctions.cs b/PersistentOrderedMap/BTreeFunctions.cs index 2f7617a..34c2222 100644 --- a/PersistentOrderedMap/BTreeFunctions.cs +++ b/PersistentOrderedMap/BTreeFunctions.cs @@ -1,4 +1,3 @@ -using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -42,27 +41,28 @@ namespace PersistentOrderedMap } } - public static Node Set(Node root, K key, V value, IKeyStrategy strategy, OwnerId owner, out bool countChanged) + public static Node Set(Node root, K key, V value, TStrategy strategy, OwnerId owner, out bool countChanged) + where TStrategy : IKeyStrategy { root = root.EnsureEditable(owner); // Todo, this should really be made a tuple return value to not stress the GC - var splitResult = InsertRecursive(root, key, value, strategy, owner, out countChanged); + var (newNode, sep) = InsertRecursive(root, key, value, strategy, owner, out countChanged); - if (splitResult != null) + if (newNode != null) { var newRoot = strategy.UsesPrefixes ? new PrefixInternalNode(owner) : new InternalNode(owner); - newRoot.Keys[0] = splitResult.Separator; + newRoot.Keys[0] = sep; newRoot.Children[0] = root; - newRoot.Children[1] = splitResult.NewNode; + newRoot.Children[1] = newNode; newRoot.SetCount(1); if (strategy.UsesPrefixes) { - newRoot.AllPrefixes[0] = strategy.GetPrefix(splitResult.Separator); + newRoot.AllPrefixes[0] = strategy.GetPrefix(sep); } return newRoot; @@ -71,7 +71,7 @@ namespace PersistentOrderedMap return root; } - private static SplitResult? InsertRecursive(Node node, K key, V value, IKeyStrategy strategy, OwnerId owner, out bool added) + private static (Node? newNode, K separator ) InsertRecursive(Node node, K key, V value, IKeyStrategy strategy, OwnerId owner, out bool added) { long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; @@ -84,14 +84,14 @@ namespace PersistentOrderedMap { leaf.Values[index] = value; added = false; - return null; + return (null, default); } added = true; if (leaf.Header.Count < LeafNode.Capacity) { InsertIntoLeaf(leaf, index, key, value, strategy); - return null; + return (null, default); } else { @@ -106,21 +106,21 @@ namespace PersistentOrderedMap var child = internalNode.Children[index]!.EnsureEditable(owner); internalNode.Children[index] = child; - var split = InsertRecursive(child, key, value, strategy, owner, out added); + var (newNode, sep) = InsertRecursive(child, key, value, strategy, owner, out added); - if (split != null) + if (newNode != null) { if (internalNode.Header.Count < InternalNode.Capacity - 1) { - InsertIntoInternal(internalNode, index, split.Separator, split.NewNode, strategy); - return null; + InsertIntoInternal(internalNode, index, sep, newNode, strategy); + return (null, default); } else { - return SplitInternal(internalNode, index, split.Separator, split.NewNode, strategy, owner); + return SplitInternal(internalNode, index, sep, newNode, strategy, owner); } } - return null; + return (null, default); } } @@ -189,7 +189,7 @@ namespace PersistentOrderedMap // --------------------------------------------------------- [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int FindIndex(Node node, K key, long keyPrefix, TStrategy strategy) + internal static int FindIndex(LeafNode node, K key, long keyPrefix, TStrategy strategy) where TStrategy : IKeyStrategy { if (typeof(K) == typeof(int)) diff --git a/PersistentOrderedMap/Iterator.cs b/PersistentOrderedMap/Iterator.cs index 4adaebc..d8e776b 100644 --- a/PersistentOrderedMap/Iterator.cs +++ b/PersistentOrderedMap/Iterator.cs @@ -31,6 +31,9 @@ where TStrategy : IKeyStrategy // Fixed-size buffer for the path. // Depth 16 * 32 (branching factor) = Exabytes of capacity. +// The B tree is, theoretically, not limited by the size of an int, like +// all int-indexed data structures (or bit partitioned ones). +// This should be enough for anyone. [InlineArray(16)] internal struct IterNodeBuffer { @@ -138,7 +141,7 @@ public struct BTreeEnumerator : IEnumerator> // Find index in Leaf _currentLeaf = node.AsLeaf(); - int index = BTreeFunctions.FindIndex(_currentLeaf, key, keyPrefix, _strategy); + int index = BTreeFunctions.FindIndex(_currentLeaf, key, keyPrefix, _strategy); // Set position to (index - 1) so that the first MoveNext() lands on 'index' _currentLeafIndex = index - 1; From 23c4ac299e4a0bf9ba5935344b24f1e338701d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Bj=C3=B6rnstam?= Date: Thu, 21 May 2026 13:13:22 +0200 Subject: [PATCH 22/22] surpressed all style warnings from the LSP server. --- PersistentOrderedMap.sln | 7 - PersistentOrderedMap/BTreeFunctions.cs | 298 +++++++++--------- PersistentOrderedMap/BaseOrderedMap.cs | 70 ++-- PersistentOrderedMap/Iterator.cs | 76 ++--- PersistentOrderedMap/KeyStrategies.cs | 10 +- .../KeyStrategies/ComparableStrategy.cs | 6 +- .../KeyStrategies/StandardStrategy.cs | 22 +- PersistentOrderedMap/Nodes.cs | 92 +++--- PersistentOrderedMap/PersistentOrderedMap.cs | 27 +- PersistentOrderedMap/TransientOrderedMap.cs | 17 +- TestProject1/FuzzTest.cs | 34 +- TestProject1/FuzzTestStandardStrategy.cs | 34 +- TestProject1/StandardStrategy.cs | 24 +- 13 files changed, 346 insertions(+), 371 deletions(-) diff --git a/PersistentOrderedMap.sln b/PersistentOrderedMap.sln index d13d62d..f313444 100644 --- a/PersistentOrderedMap.sln +++ b/PersistentOrderedMap.sln @@ -10,8 +10,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgainstImmutableDict", "benchmarks\AgainstImmutableDict\AgainstImmutableDict.csproj", "{13304F19-7ED3-4C40-9A08-46D539667D50}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AgainstLanguageExt", "benchmarks\AgainstLanguageExt\AgainstLanguageExt.csproj", "{6C16526B-5139-4EA3-BF74-E6320F467198}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyBenchMarks", "benchmarks\MyBenchMarks\MyBenchMarks.csproj", "{769E1CEA-7E01-405B-80A2-95CBF432A2BA}" EndProject Global @@ -22,7 +20,6 @@ Global GlobalSection(NestedProjects) = preSolution {CA49AA3C-0CE6-4735-887F-FB3631D63CEE} = {B0432C7A-80E2-4EA6-8FAB-B8F23A8C39DE} {13304F19-7ED3-4C40-9A08-46D539667D50} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53} - {6C16526B-5139-4EA3-BF74-E6320F467198} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53} {769E1CEA-7E01-405B-80A2-95CBF432A2BA} = {E38B3FCB-0D4D-401D-A2FC-EDF41B755E53} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution @@ -38,10 +35,6 @@ Global {13304F19-7ED3-4C40-9A08-46D539667D50}.Debug|Any CPU.Build.0 = Debug|Any CPU {13304F19-7ED3-4C40-9A08-46D539667D50}.Release|Any CPU.ActiveCfg = Release|Any CPU {13304F19-7ED3-4C40-9A08-46D539667D50}.Release|Any CPU.Build.0 = Release|Any CPU - {6C16526B-5139-4EA3-BF74-E6320F467198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6C16526B-5139-4EA3-BF74-E6320F467198}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6C16526B-5139-4EA3-BF74-E6320F467198}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6C16526B-5139-4EA3-BF74-E6320F467198}.Release|Any CPU.Build.0 = Release|Any CPU {769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Debug|Any CPU.Build.0 = Debug|Any CPU {769E1CEA-7E01-405B-80A2-95CBF432A2BA}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/PersistentOrderedMap/BTreeFunctions.cs b/PersistentOrderedMap/BTreeFunctions.cs index 34c2222..0d7a7af 100644 --- a/PersistentOrderedMap/BTreeFunctions.cs +++ b/PersistentOrderedMap/BTreeFunctions.cs @@ -11,20 +11,20 @@ namespace PersistentOrderedMap /// TryGetValue tries to get the value at mapping key. If it finds the key it sets th /// out var to value and returns true. - public static bool TryGetValue(Node root, K key, TStrategy strategy, out V value) - where TStrategy : IKeyStrategy + public static bool TryGetValue(Node root, TK key, TStrategy strategy, out TV value) + where TStrategy : IKeyStrategy { // We always get a strategy to avoid branching already here long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; - Node current = root; + Node current = root; while (true) { if (current.IsLeaf) { - var leaf = current.AsLeaf(); + var leaf = current.AsLeaf(); int index = FindIndex(leaf, key, keyPrefix, strategy); - if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0) + if (index < leaf.Header.Count && strategy.Compare(leaf.Keys![index], key) == 0) { value = leaf.Values[index]; return true; @@ -41,8 +41,8 @@ namespace PersistentOrderedMap } } - public static Node Set(Node root, K key, V value, TStrategy strategy, OwnerId owner, out bool countChanged) - where TStrategy : IKeyStrategy + public static Node Set(Node root, TK key, TV value, TStrategy strategy, OwnerId owner, out bool countChanged) + where TStrategy : IKeyStrategy { root = root.EnsureEditable(owner); @@ -52,8 +52,8 @@ namespace PersistentOrderedMap if (newNode != null) { var newRoot = strategy.UsesPrefixes - ? new PrefixInternalNode(owner) - : new InternalNode(owner); + ? new PrefixInternalNode(owner) + : new InternalNode(owner); newRoot.Keys[0] = sep; newRoot.Children[0] = root; @@ -71,27 +71,27 @@ namespace PersistentOrderedMap return root; } - private static (Node? newNode, K separator ) InsertRecursive(Node node, K key, V value, IKeyStrategy strategy, OwnerId owner, out bool added) + private static (Node? newNode, TK separator ) InsertRecursive(Node node, TK key, TV value, IKeyStrategy strategy, OwnerId owner, out bool added) { long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; if (node.IsLeaf) { - var leaf = node.AsLeaf(); + var leaf = node.AsLeaf(); int index = FindIndex(leaf, key, keyPrefix, strategy); - if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0) + if (index < leaf.Header.Count && strategy.Compare(leaf.Keys![index], key) == 0) { leaf.Values[index] = value; added = false; - return (null, default); + return (null, default)!; } added = true; - if (leaf.Header.Count < LeafNode.Capacity) + if (leaf.Header.Count < LeafNode.Capacity) { InsertIntoLeaf(leaf, index, key, value, strategy); - return (null, default); + return (null, default)!; } else { @@ -110,26 +110,25 @@ namespace PersistentOrderedMap if (newNode != null) { - if (internalNode.Header.Count < InternalNode.Capacity - 1) + if (internalNode.Header.Count < InternalNode.Capacity - 1) { InsertIntoInternal(internalNode, index, sep, newNode, strategy); - return (null, default); - } - else - { - return SplitInternal(internalNode, index, sep, newNode, strategy, owner); + return (null, default)!; } + + return SplitInternal(internalNode, index, sep, newNode, strategy, owner); + } - return (null, default); + return (null, default)!; } } - public static Node Remove(Node root, K key, TStrategy strategy, OwnerId owner, out bool countChanged) - where TStrategy : IKeyStrategy + public static Node Remove(Node root, TK key, TStrategy strategy, OwnerId owner, out bool countChanged) + where TStrategy : IKeyStrategy { root = root.EnsureEditable(owner); - bool rebalanceNeeded = RemoveRecursive(root, key, strategy, owner, out countChanged); + bool rebalanceNeeded = RemoveRecursive(root, key, strategy, owner, out countChanged); if (rebalanceNeeded) { @@ -146,21 +145,21 @@ namespace PersistentOrderedMap return root; } - private static bool RemoveRecursive(Node node, K key, TStrategy strategy, OwnerId owner, out bool removed) - where TStrategy : IKeyStrategy + private static bool RemoveRecursive(Node node, TK key, TStrategy strategy, OwnerId owner, out bool removed) + where TStrategy : IKeyStrategy { long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; if (node.IsLeaf) { - var leaf = node.AsLeaf(); + var leaf = node.AsLeaf(); int index = FindIndex(leaf, key, keyPrefix, strategy); - if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0) + if (index < leaf.Header.Count && strategy.Compare(leaf.Keys![index], key) == 0) { RemoveFromLeaf(leaf, index, strategy); removed = true; - return leaf.Header.Count < LeafNode.MergeThreshold; + return leaf.Header.Count < LeafNode.MergeThreshold; } removed = false; @@ -174,11 +173,11 @@ namespace PersistentOrderedMap var child = internalNode.Children[index]!.EnsureEditable(owner); internalNode.Children[index] = child; - bool childUnderflow = RemoveRecursive(child, key, strategy, owner, out removed); + bool childUnderflow = RemoveRecursive(child, key, strategy, owner, out removed); if (removed && childUnderflow) { - return HandleUnderflow(internalNode, index, strategy, owner); + return HandleUnderflow(internalNode, index, strategy, owner); } return false; } @@ -189,16 +188,16 @@ namespace PersistentOrderedMap // --------------------------------------------------------- [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int FindIndex(LeafNode node, K key, long keyPrefix, TStrategy strategy) - where TStrategy : IKeyStrategy + internal static int FindIndex(LeafNode node, TK key, long keyPrefix, TStrategy strategy) + where TStrategy : IKeyStrategy { - if (typeof(K) == typeof(int)) + if (typeof(TK) == typeof(int)) { - Span keys = node.GetKeys(); - ref K firstKeyRef = ref MemoryMarshal.GetReference(keys); - ref int firstIntRef = ref Unsafe.As(ref firstKeyRef); + Span keys = node.GetKeys(); + ref TK firstKeyRef = ref MemoryMarshal.GetReference(keys); + ref int firstIntRef = ref Unsafe.As(ref firstKeyRef); ReadOnlySpan intKeys = MemoryMarshal.CreateReadOnlySpan(ref firstIntRef, keys.Length); - int intKey = Unsafe.As(ref key); + int intKey = Unsafe.As(ref key); return IntScanner.FindFirstGreaterOrEqual(intKeys, intKey); } @@ -212,17 +211,17 @@ namespace PersistentOrderedMap } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int FindRoutingIndex(InternalNode node, K key, long keyPrefix, TStrategy strategy) - where TStrategy : IKeyStrategy + internal static int FindRoutingIndex(InternalNode node, TK key, long keyPrefix, TStrategy strategy) + where TStrategy : IKeyStrategy { - if (typeof(K) == typeof(int)) + if (typeof(TK) == typeof(int)) { - Span keys = node.GetKeys(); - ref K firstKeyRef = ref MemoryMarshal.GetReference(keys); - ref int firstIntRef = ref Unsafe.As(ref firstKeyRef); + Span keys = node.GetKeys(); + ref TK firstKeyRef = ref MemoryMarshal.GetReference(keys); + ref int firstIntRef = ref Unsafe.As(ref firstKeyRef); ReadOnlySpan intKeys = MemoryMarshal.CreateReadOnlySpan(ref firstIntRef, keys.Length); - int intKey = Unsafe.As(ref key); + int intKey = Unsafe.As(ref key); return IntScanner.FindFirstGreater(intKeys, intKey); } if (!strategy.UsesPrefixes) @@ -235,8 +234,8 @@ namespace PersistentOrderedMap } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int RefineSearch(int startIndex, ReadOnlySpan keys, K key, TStrategy strategy) - where TStrategy : IKeyStrategy + private static int RefineSearch(int startIndex, ReadOnlySpan keys, TK key, TStrategy strategy) + where TStrategy : IKeyStrategy { int i = startIndex; while (i < keys.Length && strategy.Compare(keys[i], key) < 0) i++; @@ -244,8 +243,8 @@ namespace PersistentOrderedMap } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int RefineRouting(int startIndex, ReadOnlySpan keys, K key, TStrategy strategy) - where TStrategy : IKeyStrategy + private static int RefineRouting(int startIndex, ReadOnlySpan keys, TK key, TStrategy strategy) + where TStrategy : IKeyStrategy { int i = startIndex; while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) i++; @@ -253,8 +252,8 @@ namespace PersistentOrderedMap } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int FallbackSearchKeys(ReadOnlySpan keys, K key, TStrategy strategy) - where TStrategy : IKeyStrategy + private static int FallbackSearchKeys(ReadOnlySpan keys, TK key, TStrategy strategy) + where TStrategy : IKeyStrategy { return strategy.UseBinarySearch ? BinarySearchKeys(keys, key, strategy) @@ -262,8 +261,8 @@ namespace PersistentOrderedMap } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int FallbackRoutingKeys(ReadOnlySpan keys, K key, TStrategy strategy) - where TStrategy : IKeyStrategy + private static int FallbackRoutingKeys(ReadOnlySpan keys, TK key, TStrategy strategy) + where TStrategy : IKeyStrategy { return strategy.UseBinarySearch ? BinaryRoutingKeys(keys, key, strategy) @@ -271,8 +270,8 @@ namespace PersistentOrderedMap } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LinearSearchKeys(ReadOnlySpan keys, K key, TStrategy strategy) - where TStrategy : IKeyStrategy + private static int LinearSearchKeys(ReadOnlySpan keys, TK key, TStrategy strategy) + where TStrategy : IKeyStrategy { int i = 0; while (i < keys.Length && strategy.Compare(keys[i], key) < 0) i++; @@ -280,8 +279,8 @@ namespace PersistentOrderedMap } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int LinearRoutingKeys(ReadOnlySpan keys, K key, TStrategy strategy) - where TStrategy : IKeyStrategy + private static int LinearRoutingKeys(ReadOnlySpan keys, TK key, TStrategy strategy) + where TStrategy : IKeyStrategy { int i = 0; while (i < keys.Length && strategy.Compare(keys[i], key) <= 0) i++; @@ -290,17 +289,17 @@ namespace PersistentOrderedMap [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int BinarySearchKeys(ReadOnlySpan keys, K key, TStrategy strategy) - where TStrategy : IKeyStrategy + private static int BinarySearchKeys(ReadOnlySpan keys, TK key, TStrategy strategy) + where TStrategy : IKeyStrategy { int low = 0; int high = keys.Length - 1; - ref K keysRef = ref MemoryMarshal.GetReference(keys); + ref TK keysRef = ref MemoryMarshal.GetReference(keys); while (low <= high) { int mid = low + ((high - low) >> 1); - K midKey = Unsafe.Add(ref keysRef, mid); + TK midKey = Unsafe.Add(ref keysRef, mid); int cmp = strategy.Compare(midKey, key); if (cmp == 0) return mid; @@ -310,17 +309,17 @@ namespace PersistentOrderedMap return low; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int BinaryRoutingKeys(ReadOnlySpan keys, K key, TStrategy strategy) - where TStrategy : IKeyStrategy + private static int BinaryRoutingKeys(ReadOnlySpan keys, TK key, TStrategy strategy) + where TStrategy : IKeyStrategy { int low = 0; int high = keys.Length - 1; - ref K keysRef = ref MemoryMarshal.GetReference(keys); + ref TK keysRef = ref MemoryMarshal.GetReference(keys); while (low <= high) { int mid = low + ((high - low) >> 1); - K midKey = Unsafe.Add(ref keysRef, mid); + TK midKey = Unsafe.Add(ref keysRef, mid); int cmp = strategy.Compare(midKey, key); if (cmp <= 0) low = mid + 1; @@ -333,19 +332,8 @@ namespace PersistentOrderedMap // Insertion Logic // --------------------------------------------------------- - private class SeplitResult - { - public Node NewNode; - public K Separator; - public SeplitResult(Node newNode, K separator) - { - NewNode = newNode; - Separator = separator; - } - } - - private static void InsertIntoLeaf(LeafNode leaf, int index, K key, V value, TStrategy strategy) - where TStrategy : IKeyStrategy + private static void InsertIntoLeaf(LeafNode leaf, int index, TK key, TV value, TStrategy strategy) + where TStrategy : IKeyStrategy { int count = leaf.Header.Count; if (index < count) @@ -360,7 +348,7 @@ namespace PersistentOrderedMap } } - leaf.Keys[index] = key; + leaf.Keys![index] = key; leaf.Values[index] = value; if (strategy.UsesPrefixes) @@ -371,10 +359,10 @@ namespace PersistentOrderedMap leaf.SetCount(count + 1); } - private static (Node, K) SplitLeaf(LeafNode left, int insertIndex, K key, V value, TStrategy strategy, OwnerId owner) - where TStrategy : IKeyStrategy + private static (Node, TK) SplitLeaf(LeafNode left, int insertIndex, TK key, TV value, TStrategy strategy, OwnerId owner) + where TStrategy : IKeyStrategy { - var right = new LeafNode(owner, strategy.UsesPrefixes); + var right = new LeafNode(owner, strategy.UsesPrefixes); int totalCount = left.Header.Count; int splitPoint = (insertIndex == totalCount) ? totalCount : (insertIndex == 0 ? 0 : totalCount / 2); @@ -403,21 +391,21 @@ namespace PersistentOrderedMap InsertIntoLeaf(right, insertIndex - splitPoint, key, value, strategy); } - return (right, right.Keys[0]); + return (right, right.Keys![0]); } - private static void InsertIntoInternal(InternalNode node, int index, K separator, Node newChild, TStrategy strategy) - where TStrategy : IKeyStrategy + private static void InsertIntoInternal(InternalNode node, int index, TK separator, Node newChild, TStrategy strategy) + where TStrategy : IKeyStrategy { int count = node.Header.Count; if (index < count) { int moveCount = count - index; - Span keysSpan = node.Keys; + Span keysSpan = node.Keys; keysSpan.Slice(index, moveCount).CopyTo(keysSpan.Slice(index + 1)); - Span> childrenSpan = node.Children; + Span> childrenSpan = node.Children!; childrenSpan.Slice(index + 1, moveCount).CopyTo(childrenSpan.Slice(index + 2)); if (strategy.UsesPrefixes) @@ -437,26 +425,26 @@ namespace PersistentOrderedMap node.SetCount(count + 1); } - private static (Node, K) SplitInternal(InternalNode left, int insertIndex, K separator, Node newChild, TStrategy strategy, OwnerId owner) - where TStrategy : IKeyStrategy + private static (Node, TK) SplitInternal(InternalNode left, int insertIndex, TK separator, Node newChild, TStrategy strategy, OwnerId owner) + where TStrategy : IKeyStrategy { var right = strategy.UsesPrefixes - ? new PrefixInternalNode(owner) - : new InternalNode(owner); + ? new PrefixInternalNode(owner) + : new InternalNode(owner); int count = left.Header.Count; int splitPoint = count / 2; - K upKey = left.Keys[splitPoint]; + TK upKey = left.Keys[splitPoint]; int moveCount = count - splitPoint - 1; if (moveCount > 0) { - Span leftKeys = left.Keys; - Span rightKeys = right.Keys; + Span leftKeys = left.Keys; + Span rightKeys = right.Keys; leftKeys.Slice(splitPoint + 1, moveCount).CopyTo(rightKeys); - Span> leftChildren = left.Children; - Span> rightChildren = right.Children; + Span> leftChildren = left.Children!; + Span> rightChildren = right.Children!; leftChildren.Slice(splitPoint + 1, moveCount + 1).CopyTo(rightChildren); if (strategy.UsesPrefixes) @@ -484,8 +472,8 @@ namespace PersistentOrderedMap // Removal Logic // --------------------------------------------------------- - private static void RemoveFromLeaf(LeafNode leaf, int index, TStrategy strategy) - where TStrategy : IKeyStrategy + private static void RemoveFromLeaf(LeafNode leaf, int index, TStrategy strategy) + where TStrategy : IKeyStrategy { int count = leaf.Header.Count; int moveCount = count - index - 1; @@ -504,8 +492,8 @@ namespace PersistentOrderedMap leaf.SetCount(count - 1); } - private static bool HandleUnderflow(InternalNode parent, int childIndex, TStrategy strategy, OwnerId owner) - where TStrategy : IKeyStrategy + private static bool HandleUnderflow(InternalNode parent, int childIndex, TStrategy strategy, OwnerId owner) + where TStrategy : IKeyStrategy { if (childIndex < parent.Header.Count) { @@ -515,13 +503,13 @@ namespace PersistentOrderedMap if (CanBorrow(rightSibling)) { - RotateLeft(parent, childIndex, leftChild, rightSibling, strategy); + RotateLeft(parent, childIndex, leftChild, rightSibling, strategy); return false; } else { - Merge(parent, childIndex, leftChild, rightSibling, strategy); - return parent.Header.Count < LeafNode.MergeThreshold; + Merge(parent, childIndex, leftChild, rightSibling, strategy); + return parent.Header.Count < LeafNode.MergeThreshold; } } else if (childIndex > 0) @@ -532,31 +520,31 @@ namespace PersistentOrderedMap if (CanBorrow(leftSibling)) { - RotateRight(parent, childIndex - 1, leftSibling, rightChild, strategy); + RotateRight(parent, childIndex - 1, leftSibling, rightChild, strategy); return false; } else { - Merge(parent, childIndex - 1, leftSibling, rightChild, strategy); - return parent.Header.Count < LeafNode.MergeThreshold; + Merge(parent, childIndex - 1, leftSibling, rightChild, strategy); + return parent.Header.Count < LeafNode.MergeThreshold; } } return true; } - private static bool CanBorrow(Node node) + private static bool CanBorrow(Node node) { return node.Header.Count > 8 + 1; } - private static void Merge(InternalNode parent, int separatorIndex, Node left, Node right, TStrategy strategy) - where TStrategy : IKeyStrategy + private static void Merge(InternalNode parent, int separatorIndex, Node left, Node right, TStrategy strategy) + where TStrategy : IKeyStrategy { if (left.IsLeaf) { - var leftLeaf = left.AsLeaf(); - var rightLeaf = right.AsLeaf(); + var leftLeaf = left.AsLeaf(); + var rightLeaf = right.AsLeaf(); int lCount = leftLeaf.Header.Count; int rCount = rightLeaf.Header.Count; @@ -576,7 +564,7 @@ namespace PersistentOrderedMap var leftInternal = left.AsInternal(); var rightInternal = right.AsInternal(); - K separator = parent.Keys[separatorIndex]; + TK separator = parent.Keys[separatorIndex]; int lCount = leftInternal.Header.Count; leftInternal.Keys[lCount] = separator; @@ -587,8 +575,8 @@ namespace PersistentOrderedMap } int rCount = rightInternal.Header.Count; - Span rightKeys = rightInternal.Keys; - Span leftKeys = leftInternal.Keys; + Span rightKeys = rightInternal.Keys; + Span leftKeys = leftInternal.Keys; rightKeys.Slice(0, rCount).CopyTo(leftKeys.Slice(lCount + 1)); if (strategy.UsesPrefixes) @@ -596,8 +584,8 @@ namespace PersistentOrderedMap rightInternal.AllPrefixes.Slice(0, rCount).CopyTo(leftInternal.AllPrefixes.Slice(lCount + 1)); } - Span> rightChildren = rightInternal.Children; - Span> leftChildren = leftInternal.Children; + Span> rightChildren = rightInternal.Children!; + Span> leftChildren = leftInternal.Children!; rightChildren.Slice(0, rCount + 1).CopyTo(leftChildren.Slice(lCount + 1)); leftInternal.SetCount(lCount + 1 + rCount); @@ -608,7 +596,7 @@ namespace PersistentOrderedMap if (moveCount > 0) { - Span parentKeys = parent.Keys; + Span parentKeys = parent.Keys; parentKeys.Slice(separatorIndex + 1, moveCount).CopyTo(parentKeys.Slice(separatorIndex)); if (strategy.UsesPrefixes) @@ -616,22 +604,22 @@ namespace PersistentOrderedMap parent.AllPrefixes.Slice(separatorIndex + 1, moveCount).CopyTo(parent.AllPrefixes.Slice(separatorIndex)); } - Span> parentChildren = parent.Children; + Span> parentChildren = parent.Children!; parentChildren.Slice(separatorIndex + 2, moveCount).CopyTo(parentChildren.Slice(separatorIndex + 1)); } parent.SetCount(pCount - 1); } - private static void RotateLeft(InternalNode parent, int separatorIndex, Node left, Node right, TStrategy strategy) - where TStrategy : IKeyStrategy + private static void RotateLeft(InternalNode parent, int separatorIndex, Node left, Node right, TStrategy strategy) + where TStrategy : IKeyStrategy { if (left.IsLeaf) { - var leftLeaf = left.AsLeaf(); - var rightLeaf = right.AsLeaf(); + var leftLeaf = left.AsLeaf(); + var rightLeaf = right.AsLeaf(); - InsertIntoLeaf(leftLeaf, leftLeaf.Header.Count, rightLeaf.Keys[0], rightLeaf.Values[0], strategy); + InsertIntoLeaf(leftLeaf, leftLeaf.Header.Count, rightLeaf.Keys![0], rightLeaf.Values[0], strategy); RemoveFromLeaf(rightLeaf, 0, strategy); parent.Keys[separatorIndex] = rightLeaf.Keys[0]; @@ -645,7 +633,7 @@ namespace PersistentOrderedMap var leftInternal = left.AsInternal(); var rightInternal = right.AsInternal(); - K sep = parent.Keys[separatorIndex]; + TK sep = parent.Keys[separatorIndex]; InsertIntoInternal(leftInternal, leftInternal.Header.Count, sep, rightInternal.Children[0]!, strategy); parent.Keys[separatorIndex] = rightInternal.Keys[0]; @@ -656,12 +644,12 @@ namespace PersistentOrderedMap int rCount = rightInternal.Header.Count; - Span> rightChildren = rightInternal.Children; + Span> rightChildren = rightInternal.Children!; rightChildren.Slice(1, rCount).CopyTo(rightChildren); if (rCount > 1) { - Span rightKeys = rightInternal.Keys; + Span rightKeys = rightInternal.Keys; rightKeys.Slice(1, rCount - 1).CopyTo(rightKeys); if (strategy.UsesPrefixes) @@ -674,19 +662,19 @@ namespace PersistentOrderedMap } } - private static void RotateRight(InternalNode parent, int separatorIndex, Node left, Node right, TStrategy strategy) - where TStrategy : IKeyStrategy + private static void RotateRight(InternalNode parent, int separatorIndex, Node left, Node right, TStrategy strategy) + where TStrategy : IKeyStrategy { if (left.IsLeaf) { - var leftLeaf = left.AsLeaf(); - var rightLeaf = right.AsLeaf(); + var leftLeaf = left.AsLeaf(); + var rightLeaf = right.AsLeaf(); int last = leftLeaf.Header.Count - 1; - InsertIntoLeaf(rightLeaf, 0, leftLeaf.Keys[last], leftLeaf.Values[last], strategy); + InsertIntoLeaf(rightLeaf, 0, leftLeaf.Keys![last], leftLeaf.Values[last], strategy); RemoveFromLeaf(leftLeaf, last, strategy); - parent.Keys[separatorIndex] = rightLeaf.Keys[0]; + parent.Keys[separatorIndex] = rightLeaf.Keys![0]; if (strategy.UsesPrefixes) { parent.AllPrefixes[separatorIndex] = strategy.GetPrefix(rightLeaf.Keys[0]); @@ -698,7 +686,7 @@ namespace PersistentOrderedMap var rightInternal = right.AsInternal(); int last = leftInternal.Header.Count - 1; - K sep = parent.Keys[separatorIndex]; + TK sep = parent.Keys[separatorIndex]; InsertIntoInternal(rightInternal, 0, sep, leftInternal.Children[last + 1]!, strategy); parent.Keys[separatorIndex] = leftInternal.Keys[last]; @@ -711,7 +699,7 @@ namespace PersistentOrderedMap } } - public static bool TryGetMin(Node root, out K key, out V value) + public static bool TryGetMin(Node root, out TK key, out TV value) { var current = root; while (!current.IsLeaf) @@ -719,7 +707,7 @@ namespace PersistentOrderedMap current = current.AsInternal().Children[0]!; } - var leaf = current.AsLeaf(); + var leaf = current.AsLeaf(); if (leaf.Header.Count == 0) { key = default!; @@ -727,12 +715,12 @@ namespace PersistentOrderedMap return false; } - key = leaf.Keys[0]; + key = leaf.Keys![0]; value = leaf.Values[0]; return true; } - public static bool TryGetMax(Node root, out K key, out V value) + public static bool TryGetMax(Node root, out TK key, out TV value) { var current = root; while (!current.IsLeaf) @@ -741,7 +729,7 @@ namespace PersistentOrderedMap current = internalNode.Children[internalNode.Header.Count]!; } - var leaf = current.AsLeaf(); + var leaf = current.AsLeaf(); if (leaf.Header.Count == 0) { key = default!; @@ -750,15 +738,15 @@ namespace PersistentOrderedMap } int last = leaf.Header.Count - 1; - key = leaf.Keys[last]; + key = leaf.Keys![last]; value = leaf.Values[last]; return true; } - public static bool TryGetSuccessor(Node root, K key, TStrategy strategy, out K nextKey, out V nextValue) - where TStrategy : IKeyStrategy + public static bool TryGetSuccessor(Node root, TK key, TStrategy strategy, out TK nextKey, out TV nextValue) + where TStrategy : IKeyStrategy { - InternalNode[] path = new InternalNode[32]; + InternalNode[] path = new InternalNode[32]; int[] indices = new int[32]; int depth = 0; long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; @@ -774,14 +762,14 @@ namespace PersistentOrderedMap current = internalNode.Children[idx]!; } - var leaf = current.AsLeaf(); + var leaf = current.AsLeaf(); int index = FindIndex(leaf, key, keyPrefix, strategy); - if (index < leaf.Header.Count && strategy.Compare(leaf.Keys[index], key) == 0) index++; + if (index < leaf.Header.Count && strategy.Compare(leaf.Keys![index], key) == 0) index++; if (index < leaf.Header.Count) { - nextKey = leaf.Keys[index]; + nextKey = leaf.Keys![index]; nextValue = leaf.Values[index]; return true; } @@ -796,8 +784,8 @@ namespace PersistentOrderedMap current = current.AsInternal().Children[0]!; } - var targetLeaf = current.AsLeaf(); - nextKey = targetLeaf.Keys[0]; + var targetLeaf = current.AsLeaf(); + nextKey = targetLeaf.Keys![0]; nextValue = targetLeaf.Values[0]; return true; } @@ -808,10 +796,10 @@ namespace PersistentOrderedMap return false; } - public static bool TryGetPredecessor(Node root, K key, TStrategy strategy, out K prevKey, out V prevValue) - where TStrategy : IKeyStrategy + public static bool TryGetPredecessor(Node root, TK key, TStrategy strategy, out TK prevKey, out TV prevValue) + where TStrategy : IKeyStrategy { - InternalNode[] path = new InternalNode[32]; + InternalNode[] path = new InternalNode[32]; int[] indices = new int[32]; int depth = 0; long keyPrefix = strategy.UsesPrefixes ? strategy.GetPrefix(key) : 0; @@ -827,12 +815,12 @@ namespace PersistentOrderedMap current = internalNode.Children[idx]!; } - var leaf = current.AsLeaf(); + var leaf = current.AsLeaf(); int index = FindIndex(leaf, key, keyPrefix, strategy); if (index > 0) { - prevKey = leaf.Keys[index - 1]; + prevKey = leaf.Keys![index - 1]; prevValue = leaf.Values[index - 1]; return true; } @@ -848,9 +836,9 @@ namespace PersistentOrderedMap current = internalNode.Children[internalNode.Header.Count]!; } - var targetLeaf = current.AsLeaf(); + var targetLeaf = current.AsLeaf(); int last = targetLeaf.Header.Count - 1; - prevKey = targetLeaf.Keys[last]; + prevKey = targetLeaf.Keys![last]; prevValue = targetLeaf.Values[last]; return true; } diff --git a/PersistentOrderedMap/BaseOrderedMap.cs b/PersistentOrderedMap/BaseOrderedMap.cs index c3838eb..393e2ee 100644 --- a/PersistentOrderedMap/BaseOrderedMap.cs +++ b/PersistentOrderedMap/BaseOrderedMap.cs @@ -2,17 +2,17 @@ using System.Collections; namespace PersistentOrderedMap; -public abstract class BaseOrderedMap : IEnumerable> where TStrategy : IKeyStrategy +public abstract class BaseOrderedMap : IEnumerable> where TStrategy : IKeyStrategy { - internal Node _root; - internal readonly TStrategy _strategy; + internal Node Root; + internal readonly TStrategy Strategy; public int Count { get; protected set; } - protected BaseOrderedMap(Node root, TStrategy strategy, int count) + protected BaseOrderedMap(Node root, TStrategy strategy, int count) { - _root = root ?? throw new ArgumentNullException(nameof(root)); - _strategy = strategy ?? throw new ArgumentNullException(nameof(strategy)); + Root = root ?? throw new ArgumentNullException(nameof(root)); + Strategy = strategy ?? throw new ArgumentNullException(nameof(strategy)); Count = count; } @@ -21,14 +21,14 @@ public abstract class BaseOrderedMap : IEnumerable(_root, key, _strategy, out _); + return BTreeFunctions.TryGetValue(Root, key, Strategy, out _); } @@ -37,26 +37,26 @@ public abstract class BaseOrderedMap : IEnumerable Create(TStrategy strategy) + public static PersistentOrderedMap Create(TStrategy strategy) { // Start with an empty leaf owned by None so the first write triggers CoW. - var emptyRoot = new LeafNode(OwnerId.None, strategy.UsesPrefixes); - return new PersistentOrderedMap(emptyRoot, strategy, 0); + var emptyRoot = new LeafNode(OwnerId.None, strategy.UsesPrefixes); + return new PersistentOrderedMap(emptyRoot, strategy, 0); } - public static TransientOrderedMap CreateTransient(TStrategy strategy) + public static TransientOrderedMap CreateTransient(TStrategy strategy) { - var emptyRoot = new LeafNode(OwnerId.None, strategy.UsesPrefixes); - return new TransientOrderedMap(emptyRoot, strategy,0); + var emptyRoot = new LeafNode(OwnerId.None, strategy.UsesPrefixes); + return new TransientOrderedMap(emptyRoot, strategy,0); } - public BTreeEnumerator GetEnumerator() + public BTreeEnumerator GetEnumerator() { return AsEnumerable().GetEnumerator(); } - IEnumerator> IEnumerable>.GetEnumerator() + IEnumerator> IEnumerable>.GetEnumerator() { return GetEnumerator(); } @@ -67,19 +67,19 @@ public abstract class BaseOrderedMap : IEnumerable AsEnumerable() - => new(_root, _strategy, false, default, false, default); + public BTreeEnumerable AsEnumerable() + => new(Root, Strategy, false, default!, false, default!); // 2. Exact Range - public BTreeEnumerable Range(K min, K max) - => new(_root, _strategy, true, min, true, max); + public BTreeEnumerable Range(TK min, TK max) + => new(Root, Strategy, true, min, true, max); // 3. Start From (Open Ended) - public BTreeEnumerable From(K min) => new(_root, _strategy, true, min, false, default); + public BTreeEnumerable From(TK min) => new(Root, Strategy, true, min, false, default!); // 4. Until (Start at beginning) - public BTreeEnumerable Until(K max) - => new(_root, _strategy, false, default, true, max); + public BTreeEnumerable Until(TK max) + => new(Root, Strategy, false, default!, true, max); @@ -87,19 +87,19 @@ public abstract class BaseOrderedMap : IEnumerable BTreeFunctions.TryGetMin(_root, out key, out value); +public bool TryGetMin(out TK key, out TV value) => BTreeFunctions.TryGetMin(Root, out key, out value); -public bool TryGetMax(out K key, out V value) => BTreeFunctions.TryGetMax(_root, out key, out value); +public bool TryGetMax(out TK key, out TV value) => BTreeFunctions.TryGetMax(Root, out key, out value); -public bool TryGetSuccessor(K key, out K nextKey, out V nextValue) => BTreeFunctions.TryGetSuccessor(_root, key, _strategy, out nextKey, out nextValue); +public bool TryGetSuccessor(TK key, out TK nextKey, out TV nextValue) => BTreeFunctions.TryGetSuccessor(Root, key, Strategy, out nextKey, out nextValue); -public bool TryGetPredecessor(K key, out K prevKey, out V prevValue) => BTreeFunctions.TryGetPredecessor(_root, key, _strategy, out prevKey, out prevValue); +public bool TryGetPredecessor(TK key, out TK prevKey, out TV prevValue) => BTreeFunctions.TryGetPredecessor(Root, key, Strategy, out prevKey, out prevValue); // --------------------------------------------------------- // Set Operations (Linear Merge O(N+M)) // --------------------------------------------------------- -public IEnumerable> Intersect(BaseOrderedMap other) +public IEnumerable> Intersect(BaseOrderedMap other) { using var enum1 = this.GetEnumerator(); using var enum2 = other.GetEnumerator(); @@ -109,7 +109,7 @@ public IEnumerable> Intersect(BaseOrderedMap while (has1 && has2) { - int cmp = _strategy.Compare(enum1.Current.Key, enum2.Current.Key); + int cmp = Strategy.Compare(enum1.Current.Key, enum2.Current.Key); if (cmp == 0) { yield return enum1.Current; @@ -121,7 +121,7 @@ public IEnumerable> Intersect(BaseOrderedMap } } -public IEnumerable> Except(BaseOrderedMap other) +public IEnumerable> Except(BaseOrderedMap other) { using var enum1 = this.GetEnumerator(); using var enum2 = other.GetEnumerator(); @@ -131,7 +131,7 @@ public IEnumerable> Except(BaseOrderedMap ot while (has1 && has2) { - int cmp = _strategy.Compare(enum1.Current.Key, enum2.Current.Key); + int cmp = Strategy.Compare(enum1.Current.Key, enum2.Current.Key); if (cmp == 0) { has1 = enum1.MoveNext(); @@ -155,7 +155,7 @@ public IEnumerable> Except(BaseOrderedMap ot } } -public IEnumerable> SymmetricExcept(BaseOrderedMap other) +public IEnumerable> SymmetricExcept(BaseOrderedMap other) { using var enum1 = this.GetEnumerator(); using var enum2 = other.GetEnumerator(); @@ -165,7 +165,7 @@ public IEnumerable> SymmetricExcept(BaseOrderedMap : IEnumerable> -where TStrategy : IKeyStrategy +public struct BTreeEnumerable : IEnumerable> +where TStrategy : IKeyStrategy { - private readonly Node _root; + private readonly Node _root; private readonly TStrategy _strategy; - private readonly K _min, _max; + private readonly TK _min, _max; private readonly bool _hasMin, _hasMax; - public BTreeEnumerable(Node root, TStrategy strategy, bool hasMin, K min, bool hasMax, K max) + public BTreeEnumerable(Node root, TStrategy strategy, bool hasMin, TK min, bool hasMax, TK max) { _root = root; _strategy = strategy; _hasMin = hasMin; _min = min; _hasMax = hasMax; _max = max; } - public BTreeEnumerator GetEnumerator() + public BTreeEnumerator GetEnumerator() { - return new BTreeEnumerator(_root, _strategy, _hasMin, _min, _hasMax, _max); + return new BTreeEnumerator(_root, _strategy, _hasMin, _min, _hasMax, _max); } - IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); + IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } @@ -35,52 +35,52 @@ where TStrategy : IKeyStrategy // all int-indexed data structures (or bit partitioned ones). // This should be enough for anyone. [InlineArray(16)] -internal struct IterNodeBuffer +internal struct IterNodeBuffer { - private Node _element0; + private Node _element0; } [InlineArray(16)] -internal struct IterIndexBuffer +internal struct IterIndexBuffer { private int _element0; } -public struct BTreeEnumerator : IEnumerator> - where TStrategy : IKeyStrategy +public struct BTreeEnumerator : IEnumerator> + where TStrategy : IKeyStrategy { private readonly TStrategy _strategy; - private readonly Node _root; + private readonly Node _root; // --- BOUNDS --- private readonly bool _hasMax; - private readonly K _maxKey; + private readonly TK _maxKey; private readonly bool _hasMin; - private readonly K _minKey; + private readonly TK _minKey; // --- INLINE STACK --- - private IterNodeBuffer _nodeStack; - private IterIndexBuffer _indexStack; + private IterNodeBuffer _nodeStack; + private IterIndexBuffer _indexStack; private int _depth; // --- STATE --- - private LeafNode? _currentLeaf; + private LeafNode? _currentLeaf; private int _currentLeafIndex; - private KeyValuePair _current; + private KeyValuePair _current; // Unified Constructor // We use boolean flags because 'K' might be a struct where 'null' is impossible. - public BTreeEnumerator(Node root, TStrategy strategy, bool hasMin, K minKey, bool hasMax, K maxKey) + public BTreeEnumerator(Node? root, TStrategy strategy, bool hasMin, TK minKey, bool hasMax, TK maxKey) { - _root = root; + _root = root!; _strategy = strategy; _hasMax = hasMax; _maxKey = maxKey; _hasMin = hasMin; _minKey = minKey; - _nodeStack = new IterNodeBuffer(); - _indexStack = new IterIndexBuffer(); // Explicit struct init + _nodeStack = new IterNodeBuffer(); + _indexStack = new IterIndexBuffer(); // Explicit struct init _depth = 0; _currentLeaf = null; _currentLeafIndex = -1; @@ -102,7 +102,7 @@ public struct BTreeEnumerator : IEnumerator> // Logic 1: Unbounded Start (Go to very first item) private void DiveLeft() { - Node node = _root; + Node node = _root; _depth = 0; while (!node.IsLeaf) @@ -114,14 +114,14 @@ public struct BTreeEnumerator : IEnumerator> node = internalNode.Children[0]!; } - _currentLeaf = node.AsLeaf(); + _currentLeaf = node.AsLeaf(); _currentLeafIndex = -1; // Position before the first element (0) } // Logic 2: Bounded Start (Go to specific key) - private void Seek(K key) + private void Seek(TK key) { - Node node = _root; + Node node = _root; _depth = 0; long keyPrefix = _strategy.UsesPrefixes ? _strategy.GetPrefix(key) : 0; @@ -130,7 +130,7 @@ public struct BTreeEnumerator : IEnumerator> while (!node.IsLeaf) { var internalNode = node.AsInternal(); - int idx = BTreeFunctions.FindRoutingIndex(internalNode, key, keyPrefix, _strategy); + int idx = BTreeFunctions.FindRoutingIndex(internalNode, key, keyPrefix, _strategy); _nodeStack[_depth] = internalNode; _indexStack[_depth] = idx; @@ -140,8 +140,8 @@ public struct BTreeEnumerator : IEnumerator> } // Find index in Leaf - _currentLeaf = node.AsLeaf(); - int index = BTreeFunctions.FindIndex(_currentLeaf, key, keyPrefix, _strategy); + _currentLeaf = node.AsLeaf(); + int index = BTreeFunctions.FindIndex(_currentLeaf, key, keyPrefix, _strategy); // Set position to (index - 1) so that the first MoveNext() lands on 'index' _currentLeafIndex = index - 1; @@ -158,14 +158,14 @@ public struct BTreeEnumerator : IEnumerator> if (_hasMax) { // If Current Key > Max Key, we are done. - if (_strategy.Compare(_currentLeaf.Keys[_currentLeafIndex], _maxKey) > 0) + if (_strategy.Compare(_currentLeaf.Keys![_currentLeafIndex], _maxKey) > 0) { _currentLeaf = null; // Close iterator return false; } } - _current = new KeyValuePair(_currentLeaf.Keys[_currentLeafIndex], _currentLeaf.Values[_currentLeafIndex]); + _current = new KeyValuePair(_currentLeaf.Keys![_currentLeafIndex], _currentLeaf.Values[_currentLeafIndex]); return true; } @@ -176,14 +176,14 @@ public struct BTreeEnumerator : IEnumerator> // Check Max Bound immediately for the first item if (_hasMax) { - if (_strategy.Compare(_currentLeaf!.Keys[0], _maxKey) > 0) + if (_strategy.Compare(_currentLeaf.Keys![0], _maxKey) > 0) { _currentLeaf = null; return false; } } - _current = new KeyValuePair(_currentLeaf.Keys[0], _currentLeaf.Values[0]); + _current = new KeyValuePair(_currentLeaf.Keys![0], _currentLeaf.Values[0]); return true; } @@ -204,7 +204,7 @@ public struct BTreeEnumerator : IEnumerator> _indexStack[_depth] = nextIndex; _depth++; - Node node = internalNode.Children[nextIndex]!; + Node node = internalNode.Children[nextIndex]!; while (!node.IsLeaf) { _nodeStack[_depth] = node; @@ -213,7 +213,7 @@ public struct BTreeEnumerator : IEnumerator> node = node.AsInternal().Children[0]!; } - _currentLeaf = node.AsLeaf(); + _currentLeaf = node.AsLeaf(); _currentLeafIndex = 0; return true; } @@ -221,7 +221,7 @@ public struct BTreeEnumerator : IEnumerator> return false; } - public KeyValuePair Current => _current; + public KeyValuePair Current => _current; object IEnumerator.Current => _current; public void Reset() { diff --git a/PersistentOrderedMap/KeyStrategies.cs b/PersistentOrderedMap/KeyStrategies.cs index f960d1d..9ef540d 100644 --- a/PersistentOrderedMap/KeyStrategies.cs +++ b/PersistentOrderedMap/KeyStrategies.cs @@ -1,7 +1,3 @@ -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; namespace PersistentOrderedMap; @@ -9,10 +5,10 @@ using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; -public interface IKeyStrategy +public interface IKeyStrategy { - int Compare(K x, K y); - long GetPrefix(K key); + int Compare(TK x, TK y); + long GetPrefix(TK key); bool UsesPrefixes => true; diff --git a/PersistentOrderedMap/KeyStrategies/ComparableStrategy.cs b/PersistentOrderedMap/KeyStrategies/ComparableStrategy.cs index a9aa726..c41acab 100644 --- a/PersistentOrderedMap/KeyStrategies/ComparableStrategy.cs +++ b/PersistentOrderedMap/KeyStrategies/ComparableStrategy.cs @@ -4,14 +4,14 @@ using System.Runtime.CompilerServices; // This is a comparable strategy that may squeeze some extra time out of value types -public readonly struct ComparableStrategy : IKeyStrategy where K : IComparable +public readonly struct ComparableStrategy : IKeyStrategy where TK : IComparable { public bool UsesPrefixes => false; public bool UseBinarySearch => true; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long GetPrefix(K key) => 0; + public long GetPrefix(TK key) => 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Compare(K x, K y) => x.CompareTo(y); + public int Compare(TK x, TK y) => x.CompareTo(y); } diff --git a/PersistentOrderedMap/KeyStrategies/StandardStrategy.cs b/PersistentOrderedMap/KeyStrategies/StandardStrategy.cs index aefca25..996c808 100644 --- a/PersistentOrderedMap/KeyStrategies/StandardStrategy.cs +++ b/PersistentOrderedMap/KeyStrategies/StandardStrategy.cs @@ -5,21 +5,21 @@ using System.Runtime.CompilerServices; /// A universal key strategy for any type that relies on standard comparisons /// (IComparable, IComparer, or custom StringComparers) without SIMD prefixes. /// -public readonly struct StandardStrategy : IKeyStrategy +public readonly struct StandardStrategy : IKeyStrategy { - private readonly IComparer _comparer; + private readonly IComparer _comparer; // If no comparer is provided, it defaults to Comparer.Default // which automatically uses IComparable if the type implements it. public StandardStrategy() { - _comparer = Comparer.Default; + _comparer = Comparer.Default; } - public StandardStrategy(IComparer? comparer) + public StandardStrategy(IComparer? comparer) { - _comparer = comparer ?? Comparer.Default; + _comparer = comparer ?? Comparer.Default; } // Tell the B-Tree to skip SIMD routing and just use LinearSearch public bool UsesPrefixes => false; @@ -27,16 +27,16 @@ public readonly struct StandardStrategy : IKeyStrategy // This will never be called because UsesPrefixes is false, // but we must satisfy the interface. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long GetPrefix(K key) => 0; + public long GetPrefix(TK key) => 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Compare(K x, K y) + public int Compare(TK x, TK y) { return _comparer.Compare(x, y); } } -public readonly struct StandardStrategy2 : IKeyStrategy - where TComparer : struct, IComparer +public readonly struct StandardStrategy2 : IKeyStrategy + where TComparer : struct, IComparer { private readonly TComparer _comparer; @@ -46,8 +46,8 @@ public readonly struct StandardStrategy2 : IKeyStrategy public bool UseBinarySearch => true; [MethodImpl(MethodImplOptions.AggressiveInlining)] -public int Compare(K x, K y) => _comparer.Compare(x, y); +public int Compare(TK x, TK y) => _comparer.Compare(x, y); - public long GetPrefix(K key) => 0; + public long GetPrefix(TK key) => 0; } diff --git a/PersistentOrderedMap/Nodes.cs b/PersistentOrderedMap/Nodes.cs index 8e1cc76..20499ca 100644 --- a/PersistentOrderedMap/Nodes.cs +++ b/PersistentOrderedMap/Nodes.cs @@ -33,17 +33,17 @@ public struct NodeHeader } [InlineArray(32)] -public struct KeyBuffer +public struct KeyBuffer { - private K _element0; + private TK _element0; } // Constraint: Internal Nodes fixed at 32 children. // This removes the need for a separate array allocation for children references. [InlineArray(32)] -public struct NodeBuffer +public struct NodeBuffer { - private Node? _element0; + private Node? _element0; } [InlineArray(32)] @@ -52,7 +52,7 @@ internal struct InternalPrefixBuffer private long _element0; } -public abstract class Node +public abstract class Node { public NodeHeader Header; @@ -61,7 +61,7 @@ public abstract class Node Header = new NodeHeader(owner, 0, flags); } - public abstract Span GetKeys(); + public abstract Span GetKeys(); // Abstract access to prefixes regardless of storage backing public abstract Span AllPrefixes { get; } @@ -71,47 +71,47 @@ public abstract class Node public bool IsLeaf => (Header.Flags & NodeFlags.IsLeaf) != 0; - public abstract Node EnsureEditable(OwnerId transactionId); + public abstract Node EnsureEditable(OwnerId transactionId); public void SetCount(int newCount) => Header.Count = (byte)newCount; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public LeafNode AsLeaf() + public LeafNode AsLeaf() { // Zero-overhead cast. Assumes you checked IsLeaf or know logic flow. - return Unsafe.As>(this); + return Unsafe.As>(this); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public InternalNode AsInternal() + public InternalNode AsInternal() { // Zero-overhead cast. Assumes you checked !IsLeaf or know logic flow. - return Unsafe.As>(this); + return Unsafe.As>(this); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public PrefixInternalNode AsPrefixInternal() + public PrefixInternalNode AsPrefixInternal() { - return Unsafe.As>(this); + return Unsafe.As>(this); } } -public sealed class LeafNode : Node +public sealed class LeafNode : Node { public const int Capacity = 64; public const int MergeThreshold = 8; - public K[]? Keys; - public V[] Values; + public TK[]? Keys; + public TV[] Values; - internal long[]? _prefixes; + private long[]? _prefixes; public override Span AllPrefixes => _prefixes != null ? _prefixes : Span.Empty; public LeafNode(OwnerId owner, bool usePrefixes) : base(owner, NodeFlags.IsLeaf | (usePrefixes ? NodeFlags.HasPrefixes : NodeFlags.None)) { - Keys = new K[Capacity]; - Values = new V[Capacity]; + Keys = new TK[Capacity]; + Values = new TV[Capacity]; if (usePrefixes) { _prefixes = new long[Capacity]; @@ -119,21 +119,21 @@ public sealed class LeafNode : Node } // Copy Constructor for CoW - private LeafNode(LeafNode original, OwnerId newOwner) + private LeafNode(LeafNode original, OwnerId newOwner) : base(newOwner, original.Header.Flags) { - Keys = new K[Capacity]; - Values = new V[Capacity]; + Keys = new TK[Capacity]; + Values = new TV[Capacity]; Header.Count = original.Header.Count; _prefixes = new long[Capacity]; // Copy data - Array.Copy(original.Keys, Keys, original.Header.Count); + Array.Copy(original.Keys!, Keys, original.Header.Count); Array.Copy(original.Values, Values, original.Header.Count); if (original._prefixes != null) Array.Copy(original._prefixes, _prefixes, original.Header.Count); } - public override Node EnsureEditable(OwnerId transactionId) + public override Node EnsureEditable(OwnerId transactionId) { // CASE 1: Persistent Mode (transactionId is None). // We MUST create a copy, because we cannot distinguish "Shared Immutable Node (0)" @@ -142,7 +142,7 @@ public sealed class LeafNode : Node // we won't copy the same fresh node twice. if (transactionId == OwnerId.None) { - return new LeafNode(this, OwnerId.None); + return new LeafNode(this, OwnerId.None); } // CASE 2: Transient Mode. @@ -153,26 +153,26 @@ public sealed class LeafNode : Node } // CASE 3: CoW needed (Ownership mismatch). - return new LeafNode(this, transactionId); + return new LeafNode(this, transactionId); } - public override Span GetKeys() + public override Span GetKeys() { return Keys.AsSpan(0, Header.Count); } - public Span GetValues() + public Span GetValues() { return Values.AsSpan(0, Header.Count); } } -public class InternalNode : Node +public class InternalNode : Node { public const int Capacity = 32; - public KeyBuffer Keys; - public NodeBuffer Children; + public KeyBuffer Keys; + public NodeBuffer Children; public override Span AllPrefixes => Span.Empty; @@ -182,7 +182,7 @@ public class InternalNode : Node } // Fixed CoW Constructor - protected InternalNode(InternalNode original, OwnerId newOwner, NodeFlags flags) + protected InternalNode(InternalNode original, OwnerId newOwner, NodeFlags flags) : base(newOwner, flags) { Header.Count = original.Header.Count; @@ -195,28 +195,28 @@ public class InternalNode : Node // The missing method needed by BTreeFunctions for routing [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span> GetChildren() + public Span> GetChildren() { // An internal node always has (Count + 1) children - return MemoryMarshal.CreateSpan(ref Children[0], Header.Count + 1); + return MemoryMarshal.CreateSpan(ref Children[0]!, Header.Count + 1); } - public override Span GetKeys() => MemoryMarshal.CreateSpan(ref Keys[0], Header.Count); + public override Span GetKeys() => MemoryMarshal.CreateSpan(ref Keys[0], Header.Count); - public override Node EnsureEditable(OwnerId transactionId) + public override Node EnsureEditable(OwnerId transactionId) { - if (transactionId == OwnerId.None) return new InternalNode(this, OwnerId.None, Header.Flags); + if (transactionId == OwnerId.None) return new InternalNode(this, OwnerId.None, Header.Flags); if (Header.Owner == transactionId) return this; - return new InternalNode(this, transactionId, Header.Flags); + return new InternalNode(this, transactionId, Header.Flags); } } -public sealed class PrefixInternalNode : InternalNode +public sealed class PrefixInternalNode : InternalNode { - internal InternalPrefixBuffer _prefixBuffer; + internal InternalPrefixBuffer PrefixBuffer; - public override Span AllPrefixes => MemoryMarshal.CreateSpan(ref _prefixBuffer[0], Capacity); + public override Span AllPrefixes => MemoryMarshal.CreateSpan(ref PrefixBuffer[0], Capacity); public PrefixInternalNode(OwnerId owner) : base(owner, NodeFlags.HasPrefixes) @@ -224,18 +224,18 @@ public sealed class PrefixInternalNode : InternalNode } // CoW Constructor - private PrefixInternalNode(PrefixInternalNode original, OwnerId newOwner) + private PrefixInternalNode(PrefixInternalNode original, OwnerId newOwner) : base(original, newOwner, original.Header.Flags) { // Copy the base Keys and Children, then blit the prefix buffer - this._prefixBuffer = original._prefixBuffer; + this.PrefixBuffer = original.PrefixBuffer; } - public override Node EnsureEditable(OwnerId transactionId) + public override Node EnsureEditable(OwnerId transactionId) { - if (transactionId == OwnerId.None) return new PrefixInternalNode(this, OwnerId.None); + if (transactionId == OwnerId.None) return new PrefixInternalNode(this, OwnerId.None); if (Header.Owner == transactionId) return this; - return new PrefixInternalNode(this, transactionId); + return new PrefixInternalNode(this, transactionId); } } diff --git a/PersistentOrderedMap/PersistentOrderedMap.cs b/PersistentOrderedMap/PersistentOrderedMap.cs index 5d45114..9731141 100644 --- a/PersistentOrderedMap/PersistentOrderedMap.cs +++ b/PersistentOrderedMap/PersistentOrderedMap.cs @@ -1,44 +1,43 @@ -using System.Collections; namespace PersistentOrderedMap; -public sealed class PersistentOrderedMap : BaseOrderedMap, IEnumerable, IEnumerable> where TStrategy : IKeyStrategy +public sealed class PersistentOrderedMap : BaseOrderedMap where TStrategy : IKeyStrategy { - internal PersistentOrderedMap(Node root, TStrategy strategy, int count) + internal PersistentOrderedMap(Node root, TStrategy strategy, int count) : base(root, strategy, count) { } // --------------------------------------------------------- // Immutable Write API (Returns new Map) // --------------------------------------------------------- - public PersistentOrderedMap Set(K key, V value) + public PersistentOrderedMap Set(TK key, TV value) { // OPTIMIZATION: Use OwnerId.None (0). // This signals EnsureEditable to always copy the root path, // producing a new tree of nodes that also have OwnerId.None. - var newRoot = BTreeFunctions.Set(_root, key, value, _strategy, OwnerId.None, out bool countChanged); - return new PersistentOrderedMap(newRoot, _strategy, countChanged ? Count + 1 : Count); + var newRoot = BTreeFunctions.Set(Root, key, value, Strategy, OwnerId.None, out bool countChanged); + return new PersistentOrderedMap(newRoot, Strategy, countChanged ? Count + 1 : Count); } - public static PersistentOrderedMap Empty(TStrategy strategy) + public static PersistentOrderedMap Empty(TStrategy strategy) { // Create an empty Leaf Node. // 'default(OwnerId)' (usually 0) marks this node as Immutable/Persistent. // This ensures that any subsequent Set/Remove will clone this node // instead of modifying it in place. - var emptyRoot = new LeafNode(default(OwnerId), strategy.UsesPrefixes); + var emptyRoot = new LeafNode(default(OwnerId), strategy.UsesPrefixes); - return new PersistentOrderedMap(emptyRoot, strategy, 0); + return new PersistentOrderedMap(emptyRoot, strategy, 0); } - public PersistentOrderedMap Remove(K key) + public PersistentOrderedMap Remove(TK key) { - var newRoot = BTreeFunctions.Remove(_root, key, _strategy, OwnerId.None, out bool removed); + var newRoot = BTreeFunctions.Remove(Root, key, Strategy, OwnerId.None, out bool removed); if (!removed) return this; - return new PersistentOrderedMap(newRoot, _strategy, Count - 1); + return new PersistentOrderedMap(newRoot, Strategy, Count - 1); } - public TransientOrderedMap ToTransient() + public TransientOrderedMap ToTransient() { - return new TransientOrderedMap(_root, _strategy, Count); + return new TransientOrderedMap(Root, Strategy, Count); } } diff --git a/PersistentOrderedMap/TransientOrderedMap.cs b/PersistentOrderedMap/TransientOrderedMap.cs index 48d4248..93786f8 100644 --- a/PersistentOrderedMap/TransientOrderedMap.cs +++ b/PersistentOrderedMap/TransientOrderedMap.cs @@ -1,35 +1,34 @@ -using System.Collections; namespace PersistentOrderedMap; -public sealed class TransientOrderedMap : BaseOrderedMap where TStrategy : IKeyStrategy +public sealed class TransientOrderedMap : BaseOrderedMap where TStrategy : IKeyStrategy { // This is mutable, but we treat it as readonly for the ID generation logic usually. private OwnerId _transactionId; - public TransientOrderedMap(Node root, TStrategy strategy, int count) + public TransientOrderedMap(Node root, TStrategy strategy, int count) : base(root, strategy, count) { _transactionId = OwnerId.Next(); } - public void Set(K key, V value) + public void Set(TK key, TV value) { - _root = BTreeFunctions.Set(_root, key, value, _strategy, _transactionId, out bool countChanged); + Root = BTreeFunctions.Set(Root, key, value, Strategy, _transactionId, out bool countChanged); if (countChanged) Count++; } - public void Remove(K key) + public void Remove(TK key) { - _root = BTreeFunctions.Remove(_root, key, _strategy, _transactionId, out bool removed); + Root = BTreeFunctions.Remove(Root, key, Strategy, _transactionId, out bool removed); if (removed) Count--; } - public PersistentOrderedMap ToPersistent() + public PersistentOrderedMap ToPersistent() { // 1. Create the snapshot by copying all relevant information - var snapshot = new PersistentOrderedMap(_root, _strategy, Count); + var snapshot = new PersistentOrderedMap(Root, Strategy, Count); // 2. Protect the snapshot from THIS TransientOrderedMap by getting a new ownerId // so that future edits will be done by CoW diff --git a/TestProject1/FuzzTest.cs b/TestProject1/FuzzTest.cs index 643db48..bc4a081 100644 --- a/TestProject1/FuzzTest.cs +++ b/TestProject1/FuzzTest.cs @@ -19,25 +19,25 @@ public class BTreeFuzzTests public void Fuzz_Insert_And_Remove_consistency() { // CONFIGURATION - const int Iterations = 100_000; // High enough to trigger all splits/merges - const int KeyRange = 5000; // Small enough to cause frequent collisions + const int iterations = 100_000; // High enough to trigger all splits/merges + const int keyRange = 5000; // Small enough to cause frequent collisions const bool showOps = false; - int Seed = 2135974; // Environment.TickCount; + int seed = 2135974; // Environment.TickCount; // ORACLES var reference = new SortedDictionary(); var subject = BaseOrderedMap.CreateTransient(_strategy); - var random = new Random(Seed); - _output.WriteLine($"Starting Fuzz Test with Seed: {Seed}"); + var random = new Random(seed); + _output.WriteLine($"Starting Fuzz Test with Seed: {seed}"); try { - for (int i = 0; i < Iterations; i++) + for (int i = 0; i < iterations; i++) { // 1. Pick an Action: 70% Insert/Update, 30% Remove bool isInsert = random.NextDouble() < 0.7; - int key = random.Next(KeyRange); + int key = random.Next(keyRange); int val = key * 100; if (isInsert) @@ -82,7 +82,7 @@ public class BTreeFuzzTests } catch (Exception) { - _output.WriteLine($"FAILED at iteration with SEED: {Seed}"); + _output.WriteLine($"FAILED at iteration with SEED: {seed}"); throw; // Re-throw to fail the test } } @@ -91,28 +91,28 @@ public class BTreeFuzzTests public void Fuzz_Range_Queries() { // Validates that your Range Enumerator matches LINQ on the reference - const int Iterations = 1000; - const int KeyRange = 2000; - int Seed = Environment.TickCount; + const int iterations = 1000; + const int keyRange = 2000; + int seed = Environment.TickCount; var reference = new SortedDictionary(); var subject = BaseOrderedMap.CreateTransient(_strategy); - var random = new Random(Seed); + var random = new Random(seed); // Fill Data - for(int i=0; i= min + int min = random.Next(keyRange); + int max = min + random.Next(keyRange - min); // Ensure max >= min // 1. Reference Result (LINQ) // Note: SortedDictionary doesn't have a direct Range query, so we filter memory. diff --git a/TestProject1/FuzzTestStandardStrategy.cs b/TestProject1/FuzzTestStandardStrategy.cs index 958498a..5c5ee61 100644 --- a/TestProject1/FuzzTestStandardStrategy.cs +++ b/TestProject1/FuzzTestStandardStrategy.cs @@ -19,25 +19,25 @@ public class BTreeFuzzTestStandardStrategy public void Fuzz_Insert_And_Remove_consistency() { // CONFIGURATION - const int Iterations = 100_000; // High enough to trigger all splits/merges - const int KeyRange = 5000; // Small enough to cause frequent collisions + const int iterations = 100_000; // High enough to trigger all splits/merges + const int keyRange = 5000; // Small enough to cause frequent collisions const bool showOps = false; - int Seed = 2135974; // Environment.TickCount; + int seed = 2135974; // Environment.TickCount; // ORACLES var reference = new SortedDictionary(); var subject = BaseOrderedMap>.CreateTransient(_strategy); - var random = new Random(Seed); - _output.WriteLine($"Starting Fuzz Test with Seed: {Seed}"); + var random = new Random(seed); + _output.WriteLine($"Starting Fuzz Test with Seed: {seed}"); try { - for (int i = 0; i < Iterations; i++) + for (int i = 0; i < iterations; i++) { // 1. Pick an Action: 70% Insert/Update, 30% Remove bool isInsert = random.NextDouble() < 0.7; - int key = random.Next(KeyRange); + int key = random.Next(keyRange); int val = key * 100; if (isInsert) @@ -82,7 +82,7 @@ public class BTreeFuzzTestStandardStrategy } catch (Exception) { - _output.WriteLine($"FAILED at iteration with SEED: {Seed}"); + _output.WriteLine($"FAILED at iteration with SEED: {seed}"); throw; // Re-throw to fail the test } } @@ -91,28 +91,28 @@ public class BTreeFuzzTestStandardStrategy public void Fuzz_Range_Queries() { // Validates that your Range Enumerator matches LINQ on the reference - const int Iterations = 1000; - const int KeyRange = 2000; - int Seed = Environment.TickCount; + const int iterations = 1000; + const int keyRange = 2000; + int seed = Environment.TickCount; var reference = new SortedDictionary(); var subject = BaseOrderedMap>.CreateTransient(_strategy); - var random = new Random(Seed); + var random = new Random(seed); // Fill Data - for(int i=0; i= min + int min = random.Next(keyRange); + int max = min + random.Next(keyRange - min); // Ensure max >= min // 1. Reference Result (LINQ) // Note: SortedDictionary doesn't have a direct Range query, so we filter memory. diff --git a/TestProject1/StandardStrategy.cs b/TestProject1/StandardStrategy.cs index b5467b0..94cc484 100644 --- a/TestProject1/StandardStrategy.cs +++ b/TestProject1/StandardStrategy.cs @@ -13,26 +13,26 @@ public class StandardStrategy [Fact] public void Setup() { - var N = 1000; - var _stdStrategy = new StandardStrategy(); - var _uniStrategy = new UnicodeStrategy(); + var n = 1000; + var stdStrategy = new StandardStrategy(); + var uniStrategy = new UnicodeStrategy(); var rnd = new Random(42); - var StringLength = 10; + var stringLength = 10; // Build random strings - var _allKeys = Enumerable.Range(0, N).Select(_ => GenerateRandomString(StringLength, rnd)).Distinct().ToArray(); + var allKeys = Enumerable.Range(0, n).Select(_ => GenerateRandomString(stringLength, rnd)).Distinct().ToArray(); // Regenerate if Distinct() reduced array size (highly unlikely with length 8/50, but safe) - while (_allKeys.Length < N) + while (allKeys.Length < n) { - _allKeys = _allKeys.Concat(new[] { GenerateRandomString(StringLength, rnd) }).Distinct().ToArray(); + allKeys = allKeys.Concat(new[] { GenerateRandomString(stringLength, rnd) }).Distinct().ToArray(); } - var transStd = BaseOrderedMap>.CreateTransient(_stdStrategy); - var transUni = BaseOrderedMap.CreateTransient(_uniStrategy); - for (int i = 0; i < _allKeys.Length; i++) + var transStd = BaseOrderedMap>.CreateTransient(stdStrategy); + var transUni = BaseOrderedMap.CreateTransient(uniStrategy); + for (int i = 0; i < allKeys.Length; i++) { - transStd.Set(_allKeys[i], i); - transUni.Set(_allKeys[i], i); + transStd.Set(allKeys[i], i); + transUni.Set(allKeys[i], i); } } } \ No newline at end of file