From 4a36a94e793cb97f8ecfa11f1b9abcb90522ce33 Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Fri, 18 Nov 2022 15:51:34 +0100
Subject: [PATCH 1/8] minmask/maxmask

---
 CHANGELOG.md                     |  2 ++
 include/phtree/v16/for_each_hc.h | 47 ++++++++++++++++----------------
 include/phtree/v16/iterator_hc.h | 34 ++++++++++-------------
 3 files changed, 39 insertions(+), 44 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50bfe300..5067b062 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
   This works best for point datasets, and somewhat for box datasets with "include" queries.
   There is no benefit for "intersection" queries. [#88](https://github.com/tzaeschke/phtree-cpp/issues/88)
 - Improved benchmarks for insert and query to use a more compact format. [91](https://github.com/tzaeschke/phtree-cpp/pull/91)
+- Improved performance of window queries by optimizing calculation of min/max masks.
+  [#95](https://github.com/tzaeschke/phtree-cpp/pull/95)
 
 ### Removed
 - bazel version requirement file `.bazelversion`. [#89](https://github.com/tzaeschke/phtree-cpp/issues/89)
diff --git a/include/phtree/v16/for_each_hc.h b/include/phtree/v16/for_each_hc.h
index 4500d76d..3c3d63af 100644
--- a/include/phtree/v16/for_each_hc.h
+++ b/include/phtree/v16/for_each_hc.h
@@ -94,18 +94,23 @@ class ForEachHC {
         // An infix with len=0 implies that at least part of the child node overlaps with the query,
         // otherwise the bit mask checking would have returned 'false'.
         // Putting it differently, if the infix has len=0, then there is no point in validating it.
+        bool mismatch = false;
         if (entry.HasNodeInfix(parent_postfix_len)) {
             // Mask for comparing the prefix with the query boundaries.
             assert(entry.GetNodePostfixLen() + 1 < MAX_BIT_WIDTH<SCALAR>);
             SCALAR comparison_mask = MAX_MASK<SCALAR> << (entry.GetNodePostfixLen() + 1);
+//            for (dimension_t dim = 0; dim < DIM; ++dim) {
+//                SCALAR prefix = key[dim] & comparison_mask;
+//                if (prefix > range_max_[dim] || prefix < (range_min_[dim] & comparison_mask)) {
+//                    return false;
+//                }
+//            }
             for (dimension_t dim = 0; dim < DIM; ++dim) {
                 SCALAR prefix = key[dim] & comparison_mask;
-                if (prefix > range_max_[dim] || prefix < (range_min_[dim] & comparison_mask)) {
-                    return false;
-                }
+                mismatch |= (prefix > range_max_[dim] || prefix < (range_min_[dim] & comparison_mask));
             }
         }
-        return filter_.IsNodeValid(key, entry.GetNodePostfixLen() + 1);
+        return mismatch ? false : filter_.IsNodeValid(key, entry.GetNodePostfixLen() + 1);
     }
 
     void CalcLimits(
@@ -136,14 +141,10 @@ class ForEachHC {
                 lower_limit <<= 1;
                 upper_limit <<= 1;
                 SCALAR nodeBisection = (prefix[i] | maskHcBit) & maskVT;
-                if (range_min_[i] >= nodeBisection) {
-                    //==> set to 1 if lower value should not be queried
-                    lower_limit |= ONE;
-                }
-                if (range_max_[i] >= nodeBisection) {
-                    // Leave 0 if higher value should not be queried.
-                    upper_limit |= ONE;
-                }
+                //==> set to 1 if lower value should not be queried
+                lower_limit |= range_min_[i] >= nodeBisection;
+                // Leave 0 if higher value should not be queried.
+                upper_limit |= range_max_[i] >= nodeBisection;
             }
         } else {
             // special treatment for signed longs
@@ -154,18 +155,16 @@ class ForEachHC {
             for (dimension_t i = 0; i < DIM; ++i) {
                 lower_limit <<= 1;
                 upper_limit <<= 1;
-                if (range_min_[i] < 0) {
-                    // If minimum is positive, we don't need the search negative values
-                    //==> set upper_limit to 0, prevent searching values starting with '1'.
-                    upper_limit |= ONE;
-                }
-                if (range_max_[i] < 0) {
-                    // Leave 0 if higher value should not be queried
-                    // If maximum is negative, we do not need to search positive values
-                    //(starting with '0').
-                    //--> lower_limit = '1'
-                    lower_limit |= ONE;
-                }
+
+                // If minimum is positive, we don't need the search negative values
+                //==> set upper_limit to 0, prevent searching values starting with '1'.
+                upper_limit |= range_min_[i] < 0;
+
+                // Leave 0 if higher value should not be queried
+                // If maximum is negative, we do not need to search positive values
+                //(starting with '0').
+                //--> lower_limit = '1'
+                lower_limit |= range_max_[i] < 0;
             }
         }
     }
diff --git a/include/phtree/v16/iterator_hc.h b/include/phtree/v16/iterator_hc.h
index cd71794a..f37c10d9 100644
--- a/include/phtree/v16/iterator_hc.h
+++ b/include/phtree/v16/iterator_hc.h
@@ -221,14 +221,10 @@ class NodeIterator {
                 lower_limit <<= 1;
                 upper_limit <<= 1;
                 SCALAR nodeBisection = (prefix[i] | maskHcBit) & maskVT;
-                if (range_min[i] >= nodeBisection) {
-                    //==> set to 1 if lower value should not be queried
-                    lower_limit |= ONE;
-                }
-                if (range_max[i] >= nodeBisection) {
-                    // Leave 0 if higher value should not be queried.
-                    upper_limit |= ONE;
-                }
+                //==> set to 1 if lower value should not be queried
+                lower_limit |= range_min[i] >= nodeBisection;
+                // Leave 0 if higher value should not be queried.
+                upper_limit |= range_max[i] >= nodeBisection;
             }
         } else {
             // special treatment for signed longs
@@ -240,18 +236,16 @@ class NodeIterator {
             for (dimension_t i = 0; i < DIM; ++i) {
                 lower_limit <<= 1;
                 upper_limit <<= 1;
-                if (range_min[i] < 0) {
-                    // If minimum is positive, we don't need the search negative values
-                    //==> set upper_limit to 0, prevent searching values starting with '1'.
-                    upper_limit |= ONE;
-                }
-                if (range_max[i] < 0) {
-                    // Leave 0 if higher value should not be queried
-                    // If maximum is negative, we do not need to search positive values
-                    //(starting with '0').
-                    //--> lower_limit = '1'
-                    lower_limit |= ONE;
-                }
+
+                // If minimum is positive, we don't need the search negative values
+                //==> set upper_limit to 0, prevent searching values starting with '1'.
+                upper_limit |= range_min[i] < 0;
+
+                // Leave 0 if higher value should not be queried
+                // If maximum is negative, we do not need to search positive values
+                //(starting with '0').
+                //--> lower_limit = '1'
+                lower_limit |= range_max[i] < 0;
             }
         }
         mask_lower_ = lower_limit;

From a685c37f5984560d94710ae0aa1b0aefd9e01378 Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Fri, 18 Nov 2022 17:06:37 +0100
Subject: [PATCH 2/8] minmask/maxmask

---
 include/phtree/v16/entry.h       | 26 ++++++++++++++++++++++++++
 include/phtree/v16/for_each_hc.h | 18 ++++++++----------
 include/phtree/v16/iterator_hc.h | 21 +++++++++------------
 include/phtree/v16/node.h        | 14 ++++++++++++++
 4 files changed, 57 insertions(+), 22 deletions(-)

diff --git a/include/phtree/v16/entry.h b/include/phtree/v16/entry.h
index 6b2a2dbf..dc661bab 100644
--- a/include/phtree/v16/entry.h
+++ b/include/phtree/v16/entry.h
@@ -125,6 +125,27 @@ class Entry {
         DestroyUnion();
     }
 
+    void SetNodeCenter() {
+        assert(union_type_ == NODE);
+        bit_mask_t<SCALAR> maskHcBit = bit_mask_t<SCALAR>(1) << postfix_len_;
+        bit_mask_t<SCALAR> maskVT = MAX_MASK<SCALAR> << postfix_len_;
+        // to prevent problems with signed long when using 64 bit
+        if (postfix_len_ < MAX_BIT_WIDTH<SCALAR> - 1) {
+            for (dimension_t i = 0; i < DIM; ++i) {
+                kd_key_[i] = (kd_key_[i] | maskHcBit) & maskVT;
+            }
+        } else {
+            // special treatment for signed longs
+            // The problem (difference) here is that a '1' at the leading bit does indicate a
+            // LOWER value, opposed to indicating a HIGHER value as in the remaining 63 bits.
+            // The hypercube assumes that a leading '0' indicates a lower value.
+            // Solution: We leave HC as it is.
+            for (dimension_t i = 0; i < DIM; ++i) {
+                kd_key_[i] = 0;
+            }
+        }
+    }
+
     [[nodiscard]] const KeyT& GetKey() const {
         return kd_key_;
     }
@@ -148,6 +169,7 @@ class Entry {
     }
 
     void SetKey(const KeyT& key) noexcept {
+        assert(union_type_ == VALUE);  // Do we have any other use?
         kd_key_ = key;
     }
 
@@ -157,6 +179,7 @@ class Entry {
         union_type_ = NODE;
         new (&node_) std::unique_ptr<NodeT>{std::move(node)};
         assert(!node);
+        SetNodeCenter();
     }
 
     [[nodiscard]] bit_width_t GetNodePostfixLen() const noexcept {
@@ -194,6 +217,9 @@ class Entry {
         union_type_ = EMPTY;
         *this = std::move(other);
         node.reset();
+        if (IsNode()) {
+            SetNodeCenter();
+        }
     }
 
   private:
diff --git a/include/phtree/v16/for_each_hc.h b/include/phtree/v16/for_each_hc.h
index 3c3d63af..6467b3d7 100644
--- a/include/phtree/v16/for_each_hc.h
+++ b/include/phtree/v16/for_each_hc.h
@@ -132,19 +132,17 @@ class ForEachHC {
         // query higher ||                                       NO                  YES
         //
         assert(postfix_len < MAX_BIT_WIDTH<SCALAR>);
-        bit_mask_t<SCALAR> maskHcBit = bit_mask_t<SCALAR>(1) << postfix_len;
-        bit_mask_t<SCALAR> maskVT = MAX_MASK<SCALAR> << postfix_len;
-        constexpr hc_pos_t ONE = 1;
         // to prevent problems with signed long when using 64 bit
         if (postfix_len < MAX_BIT_WIDTH<SCALAR> - 1) {
             for (dimension_t i = 0; i < DIM; ++i) {
                 lower_limit <<= 1;
-                upper_limit <<= 1;
-                SCALAR nodeBisection = (prefix[i] | maskHcBit) & maskVT;
                 //==> set to 1 if lower value should not be queried
-                lower_limit |= range_min_[i] >= nodeBisection;
+                lower_limit |= range_min_[i] >= prefix[i];
+            }
+            for (dimension_t i = 0; i < DIM; ++i) {
+                upper_limit <<= 1;
                 // Leave 0 if higher value should not be queried.
-                upper_limit |= range_max_[i] >= nodeBisection;
+                upper_limit |= range_max_[i] >= prefix[i];
             }
         } else {
             // special treatment for signed longs
@@ -153,13 +151,13 @@ class ForEachHC {
             // The hypercube assumes that a leading '0' indicates a lower value.
             // Solution: We leave HC as it is.
             for (dimension_t i = 0; i < DIM; ++i) {
-                lower_limit <<= 1;
                 upper_limit <<= 1;
-
                 // If minimum is positive, we don't need the search negative values
                 //==> set upper_limit to 0, prevent searching values starting with '1'.
                 upper_limit |= range_min_[i] < 0;
-
+            }
+            for (dimension_t i = 0; i < DIM; ++i) {
+                lower_limit <<= 1;
                 // Leave 0 if higher value should not be queried
                 // If maximum is negative, we do not need to search positive values
                 //(starting with '0').
diff --git a/include/phtree/v16/iterator_hc.h b/include/phtree/v16/iterator_hc.h
index f37c10d9..184ccb86 100644
--- a/include/phtree/v16/iterator_hc.h
+++ b/include/phtree/v16/iterator_hc.h
@@ -17,8 +17,8 @@
 #ifndef PHTREE_V16_ITERATOR_HC_H
 #define PHTREE_V16_ITERATOR_HC_H
 
-#include "phtree/common/common.h"
 #include "iterator_with_parent.h"
+#include "phtree/common/common.h"
 
 namespace improbable::phtree::v16 {
 
@@ -210,21 +210,19 @@ class NodeIterator {
         // query higher ||                                       NO                  YES
         //
         assert(postfix_len < MAX_BIT_WIDTH<SCALAR>);
-        bit_mask_t<SCALAR> maskHcBit = bit_mask_t<SCALAR>(1) << postfix_len;
-        bit_mask_t<SCALAR> maskVT = MAX_MASK<SCALAR> << postfix_len;
         hc_pos_t lower_limit = 0;
         hc_pos_t upper_limit = 0;
-        constexpr hc_pos_t ONE = 1;
         // to prevent problems with signed long when using 64 bit
         if (postfix_len < MAX_BIT_WIDTH<SCALAR> - 1) {
             for (dimension_t i = 0; i < DIM; ++i) {
                 lower_limit <<= 1;
-                upper_limit <<= 1;
-                SCALAR nodeBisection = (prefix[i] | maskHcBit) & maskVT;
                 //==> set to 1 if lower value should not be queried
-                lower_limit |= range_min[i] >= nodeBisection;
+                lower_limit |= range_min[i] >= prefix[i];
+            }
+            for (dimension_t i = 0; i < DIM; ++i) {
+                upper_limit <<= 1;
                 // Leave 0 if higher value should not be queried.
-                upper_limit |= range_max[i] >= nodeBisection;
+                upper_limit |= range_max[i] >= prefix[i];
             }
         } else {
             // special treatment for signed longs
@@ -232,15 +230,14 @@ class NodeIterator {
             // LOWER value, opposed to indicating a HIGHER value as in the remaining 63 bits.
             // The hypercube assumes that a leading '0' indicates a lower value.
             // Solution: We leave HC as it is.
-
             for (dimension_t i = 0; i < DIM; ++i) {
-                lower_limit <<= 1;
                 upper_limit <<= 1;
-
                 // If minimum is positive, we don't need the search negative values
                 //==> set upper_limit to 0, prevent searching values starting with '1'.
                 upper_limit |= range_min[i] < 0;
-
+            }
+            for (dimension_t i = 0; i < DIM; ++i) {
+                lower_limit <<= 1;
                 // Leave 0 if higher value should not be queried
                 // If maximum is negative, we do not need to search positive values
                 //(starting with '0').
diff --git a/include/phtree/v16/node.h b/include/phtree/v16/node.h
index 765ef69a..73e07859 100644
--- a/include/phtree/v16/node.h
+++ b/include/phtree/v16/node.h
@@ -251,6 +251,20 @@ class Node {
                 ++num_entries_local;
             }
         }
+
+        // Check node center
+        auto post_len = current_entry.GetNodePostfixLen();
+        if (post_len == MAX_BIT_WIDTH<SCALAR> - 1) {
+            for (auto d : current_entry.GetKey()) {
+                assert(d == 0);
+            }
+        } else {
+            for (auto d : current_entry.GetKey()) {
+                assert(((d >> post_len) & 0x1) == 1 && "Last bit of node center must be `1`");
+                assert(((d >> post_len) << post_len) == d && "postlen bits must all be `0`");
+            }
+        }
+
         return num_entries_local + num_entries_children;
     }
 

From 9737abe588748f60d4fdb1b952d01cec6884b5e5 Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Fri, 18 Nov 2022 17:24:35 +0100
Subject: [PATCH 3/8] minmask/maxmask

---
 include/phtree/v16/entry.h | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/include/phtree/v16/entry.h b/include/phtree/v16/entry.h
index dc661bab..f133783d 100644
--- a/include/phtree/v16/entry.h
+++ b/include/phtree/v16/entry.h
@@ -126,6 +126,10 @@ class Entry {
     }
 
     void SetNodeCenter() {
+        // The node center is defined as the prefix + a '1' bit after the prefix. The remaining
+        // bits, i.e. all post_len bits must be '0'.
+        // This is required for window queries which would otherwise need to calculate the
+        // center each time they traverse a node..
         assert(union_type_ == NODE);
         bit_mask_t<SCALAR> maskHcBit = bit_mask_t<SCALAR>(1) << postfix_len_;
         bit_mask_t<SCALAR> maskVT = MAX_MASK<SCALAR> << postfix_len_;
@@ -135,11 +139,6 @@ class Entry {
                 kd_key_[i] = (kd_key_[i] | maskHcBit) & maskVT;
             }
         } else {
-            // special treatment for signed longs
-            // The problem (difference) here is that a '1' at the leading bit does indicate a
-            // LOWER value, opposed to indicating a HIGHER value as in the remaining 63 bits.
-            // The hypercube assumes that a leading '0' indicates a lower value.
-            // Solution: We leave HC as it is.
             for (dimension_t i = 0; i < DIM; ++i) {
                 kd_key_[i] = 0;
             }

From 5cfd3795a76f2cdc400ea98d31b0ed66aed722e6 Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Fri, 18 Nov 2022 17:38:52 +0100
Subject: [PATCH 4/8] minmask/maxmask

---
 include/phtree/v16/iterator_knn_hs.h | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/include/phtree/v16/iterator_knn_hs.h b/include/phtree/v16/iterator_knn_hs.h
index 5af0902e..7d1b7195 100644
--- a/include/phtree/v16/iterator_knn_hs.h
+++ b/include/phtree/v16/iterator_knn_hs.h
@@ -17,8 +17,8 @@
 #ifndef PHTREE_V16_QUERY_KNN_HS_H
 #define PHTREE_V16_QUERY_KNN_HS_H
 
-#include "phtree/common/common.h"
 #include "iterator_base.h"
+#include "phtree/common/common.h"
 #include <queue>
 
 namespace improbable::phtree::v16 {
@@ -134,11 +134,9 @@ class IteratorKnnHS : public IteratorWithFilter<T, CONVERT, FILTER> {
         SCALAR mask_min = MAX_MASK<SCALAR> << bits_to_ignore;
         SCALAR mask_max = ~mask_min;
         KeyInternal buf;
-        // The following calculates the point inside of the node that is closest to center_.
-        // If center is inside the node this returns center_, otherwise it finds a point on the
-        // node's surface.
+        // The following calculates the point inside the node that is closest to center_.
         for (dimension_t i = 0; i < DIM; ++i) {
-            // if center_[i] is outside the node, return distance to closest edge,
+            // if center_[i] is outside the node, return distance to the closest edge,
             // otherwise return center_[i] itself (assume possible distance=0)
             SCALAR min = prefix[i] & mask_min;
             SCALAR max = prefix[i] | mask_max;

From dcf19db2797c632409b9c4b5b894b9a121940cfa Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Mon, 21 Nov 2022 15:30:51 +0100
Subject: [PATCH 5/8] debug helper fix

---
 include/phtree/v16/debug_helper_v16.h | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/include/phtree/v16/debug_helper_v16.h b/include/phtree/v16/debug_helper_v16.h
index bb62942f..017c8a54 100644
--- a/include/phtree/v16/debug_helper_v16.h
+++ b/include/phtree/v16/debug_helper_v16.h
@@ -17,9 +17,9 @@
 #ifndef PHTREE_V16_DEBUG_HELPER_H
 #define PHTREE_V16_DEBUG_HELPER_H
 
+#include "node.h"
 #include "phtree/common/common.h"
 #include "phtree/common/debug_helper.h"
-#include "node.h"
 #include "phtree_v16.h"
 #include <string>
 
@@ -99,7 +99,7 @@ class DebugHelperV16 : public PhTreeDebugHelper::DebugHelper {
         bit_width_t current_depth,
         const EntryT& entry,
         const bit_width_t parent_postfix_len,
-        bool printValue) const {
+        bool print_value) const {
         std::string ind = "*";
         for (bit_width_t i = 0; i < current_depth; ++i) {
             ind += "-";
@@ -127,15 +127,15 @@ class DebugHelperV16 : public PhTreeDebugHelper::DebugHelper {
         // To clean previous postfixes.
         for (auto& it : node.Entries()) {
             const auto& child = it.second;
-            hc_pos_t hcPos = it.first;
+            auto hc_pos = it.first;
             if (child.IsNode()) {
-                sb << ind << "# " << hcPos << "  Node: " << std::endl;
-                ToStringTree(sb, current_depth + 1, child, postfix_len, printValue);
+                sb << ind << "# " << hc_pos << "  Node: " << std::endl;
+                ToStringTree(sb, current_depth + 1, child, postfix_len, print_value);
             } else {
                 // post-fix
                 sb << ind << ToBinary(child.GetKey());
-                sb << "  hcPos=" << hcPos;
-                if (printValue) {
+                sb << "  hcPos=" << hc_pos;
+                if (print_value) {
                     sb << "  v=" << (child.IsValue() ? "T" : "null");
                 }
                 sb << std::endl;

From f866e5e8ae844a8033fc12611fe024eb2e672a8d Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Mon, 21 Nov 2022 18:03:14 +0100
Subject: [PATCH 6/8] debug helper fix

---
 CHANGELOG.md                            |  5 ++--
 include/phtree/common/base_types.h      | 10 ++++++-
 include/phtree/common/common.h          |  4 +--
 include/phtree/common/flat_sparse_map.h | 38 +++++++++++--------------
 include/phtree/v16/for_each_hc.h        |  2 +-
 include/phtree/v16/iterator_hc.h        |  1 +
 include/phtree/v16/node.h               | 12 ++++----
 7 files changed, 39 insertions(+), 33 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5067b062..37a4129f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,9 +10,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
 - Improved performance of window queries by executing them partially as point queries.
   This works best for point datasets, and somewhat for box datasets with "include" queries.
   There is no benefit for "intersection" queries. [#88](https://github.com/tzaeschke/phtree-cpp/issues/88)
-- Improved benchmarks for insert and query to use a more compact format. [91](https://github.com/tzaeschke/phtree-cpp/pull/91)
+- Improved benchmarks for insert and query to use a more compact format. [#91](https://github.com/tzaeschke/phtree-cpp/pull/91)
 - Improved performance of window queries by optimizing calculation of min/max masks.
-  [#95](https://github.com/tzaeschke/phtree-cpp/pull/95)
+  Improved performance of queries and updates by changing bit-width of min/max masks and
+  hc_pos_t. [#95](https://github.com/tzaeschke/phtree-cpp/pull/95)
 
 ### Removed
 - bazel version requirement file `.bazelversion`. [#89](https://github.com/tzaeschke/phtree-cpp/issues/89)
diff --git a/include/phtree/common/base_types.h b/include/phtree/common/base_types.h
index a95a721b..dcb91dac 100644
--- a/include/phtree/common/base_types.h
+++ b/include/phtree/common/base_types.h
@@ -55,7 +55,15 @@ using bit_mask_t = typename std::make_unsigned<SCALAR>::type;
 template <typename SCALAR>
 static constexpr bit_mask_t<SCALAR> MAX_MASK = std::numeric_limits<bit_mask_t<SCALAR>>::max();
 using dimension_t = size_t;  // Number of dimensions
-using hc_pos_t = uint64_t;
+// We have two types that represent hypercube addresses (HC position).
+// The hc_pos_dim_t uses a template parameter to determine how many bits are needed, this is either
+// 32bit or 64bit. This parameter is used where HC positions are stored because benchmarks show a
+// difference in performance when this is used.
+// The hc_pos_64_t type is always set to 64. It is used where computations play a role that appear
+// to prefer being in always 64bit, mainly in CalcPosInArray() and in Node.
+template<dimension_t DIM>
+using hc_pos_dim_t = std::conditional_t<(DIM < 32), uint32_t, uint64_t>;
+using hc_pos_64_t = uint64_t;
 
 // ************************************************************************
 // Basic structs and classes
diff --git a/include/phtree/common/common.h b/include/phtree/common/common.h
index 638d0e0a..92fb40a0 100644
--- a/include/phtree/common/common.h
+++ b/include/phtree/common/common.h
@@ -49,7 +49,7 @@ namespace improbable::phtree {
  * an array.
  */
 template <dimension_t DIM, typename SCALAR>
-static hc_pos_t CalcPosInArray(const PhPoint<DIM, SCALAR>& valSet, bit_width_t postfix_len) {
+static hc_pos_64_t CalcPosInArray(const PhPoint<DIM, SCALAR>& valSet, bit_width_t postfix_len) {
     // n=DIM,  i={0..n-1}
     // i = 0 :  |0|1|0|1|0|1|0|1|
     // i = 1 :  | 0 | 1 | 0 | 1 |
@@ -58,7 +58,7 @@ static hc_pos_t CalcPosInArray(const PhPoint<DIM, SCALAR>& valSet, bit_width_t p
     // Following formula was for inverse ordering of current ordering...
     // pos = sum (i=1..n, len/2^i) = sum (..., 2^(n-i))
     bit_mask_t<SCALAR> valMask = bit_mask_t<SCALAR>(1) << postfix_len;
-    hc_pos_t pos = 0;
+    hc_pos_64_t pos = 0;
     for (dimension_t i = 0; i < DIM; ++i) {
         pos <<= 1;
         // set pos-bit if bit is set in value
diff --git a/include/phtree/common/flat_sparse_map.h b/include/phtree/common/flat_sparse_map.h
index f822d3d8..15bf9eac 100644
--- a/include/phtree/common/flat_sparse_map.h
+++ b/include/phtree/common/flat_sparse_map.h
@@ -30,27 +30,22 @@
  */
 namespace improbable::phtree {
 
-namespace {
-template <typename T>
-using PhSparseMapPair = std::pair<size_t, T>;
-
-using index_t = std::int32_t;
-}  // namespace
-
 /*
  * The sparse_map is a flat map implementation that uses an array of *at* *most* SIZE=2^DIM.
  * The array contains a list sorted by key.
  *
  * It has O(log n) lookup and O(n) insertion/removal time complexity, space complexity is O(n).
  */
-template <typename T>
+template <typename KeyT, typename ValueT>
 class sparse_map {
+    using Entry = std::pair<KeyT, ValueT>;
+
   public:
     explicit sparse_map() : data_{} {
         data_.reserve(4);
     }
 
-    [[nodiscard]] auto find(size_t key) {
+    [[nodiscard]] auto find(KeyT key) {
         auto it = lower_bound(key);
         if (it != data_.end() && it->first == key) {
             return it;
@@ -58,7 +53,7 @@ class sparse_map {
         return data_.end();
     }
 
-    [[nodiscard]] auto find(size_t key) const {
+    [[nodiscard]] auto find(KeyT key) const {
         auto it = lower_bound(key);
         if (it != data_.end() && it->first == key) {
             return it;
@@ -66,16 +61,15 @@ class sparse_map {
         return data_.end();
     }
 
-    [[nodiscard]] auto lower_bound(size_t key) {
-        return std::lower_bound(
-            data_.begin(), data_.end(), key, [](PhSparseMapPair<T>& left, const size_t key) {
-                return left.first < key;
-            });
+    [[nodiscard]] auto lower_bound(KeyT key) {
+        return std::lower_bound(data_.begin(), data_.end(), key, [](Entry& left, const KeyT key) {
+            return left.first < key;
+        });
     }
 
-    [[nodiscard]] auto lower_bound(size_t key) const {
+    [[nodiscard]] auto lower_bound(KeyT key) const {
         return std::lower_bound(
-            data_.cbegin(), data_.cend(), key, [](const PhSparseMapPair<T>& left, const size_t key) {
+            data_.cbegin(), data_.cend(), key, [](const Entry& left, const KeyT key) {
                 return left.first < key;
             });
     }
@@ -110,14 +104,14 @@ class sparse_map {
         return try_emplace_base(key, std::forward<Args>(args)...);
     }
 
-    void erase(size_t key) {
+    void erase(KeyT key) {
         auto it = lower_bound(key);
         if (it != end() && it->first == key) {
             data_.erase(it);
         }
     }
 
-    void erase(const typename std::vector<PhSparseMapPair<T>>::iterator& iterator) {
+    void erase(const typename std::vector<Entry>::iterator& iterator) {
         data_.erase(iterator);
     }
 
@@ -127,7 +121,7 @@ class sparse_map {
 
   private:
     template <typename... Args>
-    auto emplace_base(size_t key, Args&&... args) {
+    auto emplace_base(KeyT key, Args&&... args) {
         auto it = lower_bound(key);
         if (it != end() && it->first == key) {
             return std::make_pair(it, false);
@@ -137,7 +131,7 @@ class sparse_map {
     }
 
     template <typename... Args>
-    auto try_emplace_base(size_t key, Args&&... args) {
+    auto try_emplace_base(KeyT key, Args&&... args) {
         auto it = lower_bound(key);
         if (it != end() && it->first == key) {
             return std::make_pair(it, false);
@@ -151,7 +145,7 @@ class sparse_map {
         }
     }
 
-    std::vector<PhSparseMapPair<T>> data_;
+    std::vector<Entry> data_;
 };
 
 }  // namespace improbable::phtree
diff --git a/include/phtree/v16/for_each_hc.h b/include/phtree/v16/for_each_hc.h
index 6467b3d7..4fee5eb0 100644
--- a/include/phtree/v16/for_each_hc.h
+++ b/include/phtree/v16/for_each_hc.h
@@ -40,6 +40,7 @@ class ForEachHC {
     using KeyInternal = typename CONVERT::KeyInternal;
     using SCALAR = typename CONVERT::ScalarInternal;
     using EntryT = Entry<DIM, T, SCALAR>;
+    using hc_pos_t = hc_pos_dim_t<DIM>;
 
   public:
     template <typename CB, typename F>
@@ -64,7 +65,6 @@ class ForEachHC {
         auto postfix_len = entry.GetNodePostfixLen();
         auto end = entries.end();
         auto iter = opt_it != nullptr && *opt_it != end ? *opt_it : entries.lower_bound(mask_lower);
-        //auto iter = opt_it != nullptr ? *opt_it : entries.lower_bound(mask_lower);
         for (; iter != end && iter->first <= mask_upper; ++iter) {
             auto child_hc_pos = iter->first;
             // Use bit-mask magic to check whether we are in a valid quadrant.
diff --git a/include/phtree/v16/iterator_hc.h b/include/phtree/v16/iterator_hc.h
index 184ccb86..9c9f8f03 100644
--- a/include/phtree/v16/iterator_hc.h
+++ b/include/phtree/v16/iterator_hc.h
@@ -134,6 +134,7 @@ class NodeIterator {
     using KeyT = PhPoint<DIM, SCALAR>;
     using EntryT = Entry<DIM, T, SCALAR>;
     using EntriesT = EntryMap<DIM, EntryT>;
+    using hc_pos_t = hc_pos_dim_t<DIM>;
 
   public:
     NodeIterator() : iter_{}, entries_{nullptr}, mask_lower_{0}, mask_upper_{0}, postfix_len_{0} {}
diff --git a/include/phtree/v16/node.h b/include/phtree/v16/node.h
index 73e07859..94d23280 100644
--- a/include/phtree/v16/node.h
+++ b/include/phtree/v16/node.h
@@ -33,7 +33,7 @@ namespace improbable::phtree::v16 {
  * - `array_map` is the fastest, but has O(2^DIM) space complexity. This can be very wasteful
  *   because many nodes may have only 2 entries.
  *   Also, iteration depends on some bit operations and is also O(DIM) per step if the CPU/compiler
- *   does not support CTZ (count trailing bits).
+ *   does not support CTZ (count trailing zeroes).
  * - `sparse_map` is slower, but requires only O(n) memory (n = number of entries/children).
  *   However, insertion/deletion is O(n), i.e. O(2^DIM) time complexity in the worst case.
  * - 'b_plus_tree_map` is the least efficient for small node sizes but scales best with larger
@@ -42,10 +42,11 @@ namespace improbable::phtree::v16 {
 template <dimension_t DIM, typename Entry>
 using EntryMap = typename std::conditional<
     DIM <= 3,
-    array_map<Entry, (hc_pos_t(1) << DIM)>,
-    typename std::
-        conditional<DIM <= 8, sparse_map<Entry>, b_plus_tree_map<Entry, (hc_pos_t(1) << DIM)>>::
-            type>::type;
+    array_map<Entry, (1u << DIM)>,
+    typename std::conditional<
+        DIM <= 8,
+        sparse_map<hc_pos_dim_t<DIM>, Entry>,
+        b_plus_tree_map<Entry, (1u << DIM)>>::type>::type;
 
 template <dimension_t DIM, typename Entry>
 using EntryIterator = decltype(EntryMap<DIM, Entry>().begin());
@@ -75,6 +76,7 @@ template <dimension_t DIM, typename T, typename SCALAR>
 class Node {
     using KeyT = PhPoint<DIM, SCALAR>;
     using EntryT = Entry<DIM, T, SCALAR>;
+    using hc_pos_t = hc_pos_64_t;
 
   public:
     Node() : entries_{} {}

From 122f7e4954e8c86870163b51180ee9bcb283d799 Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Mon, 21 Nov 2022 18:33:21 +0100
Subject: [PATCH 7/8] debug helper fix

---
 include/phtree/v16/node.h           | 4 ++--
 test/common/flat_sparse_map_test.cc | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/include/phtree/v16/node.h b/include/phtree/v16/node.h
index 94d23280..641e4941 100644
--- a/include/phtree/v16/node.h
+++ b/include/phtree/v16/node.h
@@ -42,11 +42,11 @@ namespace improbable::phtree::v16 {
 template <dimension_t DIM, typename Entry>
 using EntryMap = typename std::conditional<
     DIM <= 3,
-    array_map<Entry, (1u << DIM)>,
+    array_map<Entry, (uint64_t(1) << DIM)>,
     typename std::conditional<
         DIM <= 8,
         sparse_map<hc_pos_dim_t<DIM>, Entry>,
-        b_plus_tree_map<Entry, (1u << DIM)>>::type>::type;
+        b_plus_tree_map<Entry, (uint64_t(1) << DIM)>>::type>::type;
 
 template <dimension_t DIM, typename Entry>
 using EntryIterator = decltype(EntryMap<DIM, Entry>().begin());
diff --git a/test/common/flat_sparse_map_test.cc b/test/common/flat_sparse_map_test.cc
index 99d581d7..b4c3adc3 100644
--- a/test/common/flat_sparse_map_test.cc
+++ b/test/common/flat_sparse_map_test.cc
@@ -27,7 +27,7 @@ TEST(PhTreeFlatSparseMapTest, SmokeTest) {
     std::uniform_int_distribution<> cube_distribution(0, max_size - 1);
 
     for (int i = 0; i < 10; i++) {
-        sparse_map<size_t> test_map;
+        sparse_map<size_t, size_t> test_map;
         std::map<size_t, size_t> reference_map;
         for (int j = 0; j < 2 * max_size; j++) {
             size_t val = cube_distribution(random_engine);
@@ -61,7 +61,7 @@ TEST(PhTreeFlatSparseMapTest, SmokeTestWithTryEmplace) {
     std::uniform_int_distribution<> cube_distribution(0, max_size - 1);
 
     for (int i = 0; i < 10; i++) {
-        sparse_map<size_t> test_map;
+        sparse_map<size_t, size_t> test_map;
         std::map<size_t, size_t> reference_map;
         for (int j = 0; j < 2 * max_size; j++) {
             size_t val = cube_distribution(random_engine);

From 93971d7aa2c32c93dfe4b6e9dc7bb995802715fa Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Mon, 21 Nov 2022 19:19:40 +0100
Subject: [PATCH 8/8] debug helper fix

---
 benchmark/hd_query_d_benchmark.cc | 22 ++++++++++++++++++++++
 include/phtree/v16/entry.h        |  2 +-
 include/phtree/v16/for_each_hc.h  |  6 ------
 3 files changed, 23 insertions(+), 7 deletions(-)

diff --git a/benchmark/hd_query_d_benchmark.cc b/benchmark/hd_query_d_benchmark.cc
index ac2ac82a..25f268ce 100644
--- a/benchmark/hd_query_d_benchmark.cc
+++ b/benchmark/hd_query_d_benchmark.cc
@@ -172,6 +172,18 @@ void PhTree6D_FE(benchmark::State& state, Arguments&&...) {
     benchmark.Benchmark(state);
 }
 
+template <typename... Arguments>
+void PhTree10D_FE(benchmark::State& state, Arguments&&...) {
+    IndexBenchmark<10, MIN_MAX_FOR_EACH> benchmark{state};
+    benchmark.Benchmark(state);
+}
+
+template <typename... Arguments>
+void PhTree20D_FE(benchmark::State& state, Arguments&&...) {
+    IndexBenchmark<20, MIN_MAX_FOR_EACH> benchmark{state};
+    benchmark.Benchmark(state);
+}
+
 template <typename... Arguments>
 void PhTree6D_IT(benchmark::State& state, Arguments&&...) {
     IndexBenchmark<6, MIN_MAX_ITER> benchmark{state};
@@ -196,6 +208,16 @@ BENCHMARK_CAPTURE(PhTree6D_FE, WQ, 0)
     ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}})
     ->Unit(benchmark::kMillisecond);
 
+BENCHMARK_CAPTURE(PhTree10D_FE, WQ, 0)
+    ->RangeMultiplier(10)
+    ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}})
+    ->Unit(benchmark::kMillisecond);
+
+BENCHMARK_CAPTURE(PhTree20D_FE, WQ, 0)
+    ->RangeMultiplier(10)
+    ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}})
+    ->Unit(benchmark::kMillisecond);
+
 BENCHMARK_CAPTURE(PhTree6D_IT, WQ, 0)
     ->RangeMultiplier(10)
     ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CLUSTER, TestGenerator::CUBE}})
diff --git a/include/phtree/v16/entry.h b/include/phtree/v16/entry.h
index f133783d..7f0acf97 100644
--- a/include/phtree/v16/entry.h
+++ b/include/phtree/v16/entry.h
@@ -129,7 +129,7 @@ class Entry {
         // The node center is defined as the prefix + a '1' bit after the prefix. The remaining
         // bits, i.e. all post_len bits must be '0'.
         // This is required for window queries which would otherwise need to calculate the
-        // center each time they traverse a node..
+        // center each time they traverse a node.
         assert(union_type_ == NODE);
         bit_mask_t<SCALAR> maskHcBit = bit_mask_t<SCALAR>(1) << postfix_len_;
         bit_mask_t<SCALAR> maskVT = MAX_MASK<SCALAR> << postfix_len_;
diff --git a/include/phtree/v16/for_each_hc.h b/include/phtree/v16/for_each_hc.h
index 4fee5eb0..ef2a7c6d 100644
--- a/include/phtree/v16/for_each_hc.h
+++ b/include/phtree/v16/for_each_hc.h
@@ -99,12 +99,6 @@ class ForEachHC {
             // Mask for comparing the prefix with the query boundaries.
             assert(entry.GetNodePostfixLen() + 1 < MAX_BIT_WIDTH<SCALAR>);
             SCALAR comparison_mask = MAX_MASK<SCALAR> << (entry.GetNodePostfixLen() + 1);
-//            for (dimension_t dim = 0; dim < DIM; ++dim) {
-//                SCALAR prefix = key[dim] & comparison_mask;
-//                if (prefix > range_max_[dim] || prefix < (range_min_[dim] & comparison_mask)) {
-//                    return false;
-//                }
-//            }
             for (dimension_t dim = 0; dim < DIM; ++dim) {
                 SCALAR prefix = key[dim] & comparison_mask;
                 mismatch |= (prefix > range_max_[dim] || prefix < (range_min_[dim] & comparison_mask));
