July 08, 2026
Spherical range queries are a fundamental primitive for working with spatial data. Many spatial data structures have been developed to answer these queries, but choosing the optimal one for a specific application is a difficult task. This is because theoretical worst-case bounds are often overly pessimistic, and existing average-case analyses are rather restricted and hard to compare.
We address this problem with two main contributions. First, we present a comprehensive evaluation of state-of-the-art spatial indices across a diverse set of benchmarks. This includes a new benchmark based on graph embeddings alongside multiple real-world datasets from the literature. Our benchmark covers instances scaling up to \(\SI{10}{M}\) points and ranging between 2 and 960 dimensions.
Second, we introduce the Sorted-Projection Radius KD-tree (SPRK-tree), a high-performance KD-tree variant. The SPRK-tree combines aggressive subtree pruning via radius reduction, sorted projection-based leaf nodes, and careful implementation optimizations. It consistently achieves the fastest query times in almost all benchmarks, and ranks second in the few remaining cases.
Spherical range queries are a fundamental primitive for working with spatial data. Given a set of \(n\) points in \(d\)-dimensional Euclidean space, the objective is to retrieve all points that lie within the specified radius of a query point. In low-dimensional spaces, spatial indices significantly accelerate these queries by pruning large portions of the search space. However, in higher dimensions a brute-force approach that checks the distance to every point is often the only viable option due to the curse of dimensionality [1]. One application of spatial indices is the computation of graph embeddings [2]–[5], which play an important role in machine learning tasks such as community detection, link prediction, and node classification [6]–[10]. These methods map the vertices of a graph into a continuous space such that spatial distance reflects structural similarity [11]. This is often achieved by force-directed optimization, which applies attracting forces to adjacent vertices that are too distant from each other and repelling forces to non-adjacent vertices that are too close. The attracting forces can be computed in linear time \(O(m)\) by iterating over all \(m\) edges. However, computing repelling forces requires identifying all non-adjacent vertices within a specific distance of each other. A naive brute-force implementation would check all \(O(n^2)\) pairs of vertices, which is particularly prohibitive for large sparse graphs. Alternatively, the close vertex pairs can be identified by performing a spherical range query for each vertex, making the performance of the embedding framework heavily dependent on the efficiency of the underlying spatial index.
Beyond graph embedding, range queries also play a crucial role in numerous other domains such as particle-based simulations, database systems, and computer graphics [12]–[14]. For a comprehensive overview of applications, see the book by Samet [15]. To support these applications, numerous spatial indices have been developed; see 1 for a list. The theoretical worst-case bounds also shown in 1 give the impression that these spatial indices do not yield significant improvements over the brute-force approach. However, this is highly misleading and comes from the fact that the worst-case is often too pessimistic. In practice, the different spatial indices are often successful in pruning large chunks of the search space. In the following, we give a brief overview of the different spatial indices and broadly categorize them by how they organize and prune the search space.
The first category of spatial indices is based on partitioning the geometric space. Uniform grids partition space into fixed-size cells and insert the points into them. Queries are answered by a lookup of the corresponding cell and inspecting it and adjacent cells. These yield especially fast lookups in low dimensions when the points are evenly distributed. However, they lack adaptability and in datasets with heterogeneous density, dense cells often become a bottleneck. Orthtrees (also known as Quadtrees in 2D and Octrees in 3D) [16] address this by recursively dividing the space only where necessary. While more flexible than grids, they share the same limitation, where each split generates \(2^d\) children, which is infeasible in higher dimensions.
Unlike space partitioning methods, data partitioning methods divide the point set itself. KD-trees [17], [18] split the point set along axis-aligned hyperplanes, to construct a balanced tree. To answer a query, the tree is traversed recursively, pruning subtrees that cannot contain query results. R-trees [19] group points into bounding rectangles, VP-trees [20], [21] partition by distance to a vantage point, and Balltrees [22] use bounding hyperspheres.
The SNN structure of Chen and Güttel [23] avoids hierarchical traversal entirely. It uses the singular value decomposition (SVD) to identify the direction of maximum variance in the dataset. Points are projected onto this axis and sorted. The query algorithm uses a binary search to identify a contiguous range of candidate points that is then scanned exhaustively.
This shows that there are plenty of spatial indices to choose from. As the theoretical worst-case bounds do not provide strong guidance, choosing the right data structure for the application at hand, e.g., graph embedding, can be a daunting task. While there are some average-case results that give guarantees in certain situations [24], they are rather restricted and sometimes difficult to compare. Moreover, the performance depends on the data distribution of the specific application as well as implementation details. Consequently, an empirical evaluation is essential to choose the best data structure. The only empirical study in this direction we are aware of is by Lawson, Gropp, and Lofstead [25], whose comparison is tailored for the HPC-context, focusing on orthogonal range queries in 3-dimensional space in a distributed setting. Additionally, some libraries, e.g., the KD-tree library Kiddo [26], include benchmarks. However, to the best of our knowledge, there has been no extensive scientific comparison of spatial indices on a diverse set of inputs with varying dimensions yet.
| Structure | Construction | Space | Query (Worst-case) |
|---|---|---|---|
| Brute-Force | \(O(1)\) | \(O(nd)\) | \(O(nd)\) |
| Uniform Grid | \(O(nd + g^d)\) | \(O(nd + g^d)\) | \(O(nd + 2^d)\) |
| Orthtree [16] | \(O(n \cdot 2^d)\) | \(O(n \cdot 2^d)\) | \(O(n\cdot 2^d)\) |
| KD-tree [17], [18] | \(O(dn \log n)\) | \(O(nd)\) | \(O(d \cdot n^{1-1/d} + dk)\) |
| R-tree [19] | \(O(dn \log n)\) | \(O(nd)\) | \(O(nd)\) |
| VP-tree [20], [21] | \(O(dn \log n)\) | \(O(nd)\) | \(O(nd)\) |
| Balltree [22] | \(O(dn \log n)\) | \(O(nd)\) | \(O(nd)\) |
| SNN | \(O(dn \log n + nd^2)\) | \(O(nd)\) | \(O(nd)\) |
We have two main contributions. First, we address the need for an empirical comparison by providing a comprehensive evaluation of different state-of-the-art spatial indices. Secondly, we provide a new KD-tree variant that we call Sorted-Projection Radius KD-tree (SPRK-tree; pronounced “spark”), which outperforms the competition on almost all benchmarks. In the following, we discuss these two contributions in more detail; starting with the SPRK-tree.
At its core, the SPRK-tree is a KD-tree variant that recursively partitions the data points until the number of points falls below a certain predefined bucket size for the leaves; see 2.1. In the leaves, we use a variant of SNN [23], i.e., we sort the remaining points with respect to a projection line; see 2.3. For the query, we utilize pruning techniques, which reduce the query radius incrementally while traversing the hierarchy [18], [27], [28]. This allows more aggressive pruning of subtrees, while still ensuring that no points in the original query ball are omitted; see 2.2. For high-dimensional data sets, we initially rotate the data based on a singular value decomposition of sampled points, such that the first splits done by the KD-tree separate along directions with high spread; see 2.4. Our Rust implementation features SIMD vectorization and cache-aware memory layout (see 2.5), and provides bindings for C and Python.
Our evaluation uses a large and diverse set of benchmarks, including a newly created benchmark set. Our new set comes from the graph embedding application discussed above. It features over 240 embedding instances with up to 1 M points in dimensions \(\{2,3,\ldots,16,32\}\). Beyond that, we also consider points distributed uniformly at random and benchmarks from the literature, including data sets from clustering, point of interest queries, and a high-dimensional real-world data set with point sets of up to 960 dimensions. Besides our SPRK-tree, we evaluate 10 other implementations as well as a brute-force approach. The evaluated data structures are diverse (Quadtree, uniform grid, Balltree, VP-tree, R-tree, KD-tree, SNN) and include state-of-the-art implementations of KD-trees in multiple programming languages. Our findings can be summarized as follows.
For the embedding benchmark, KD-trees are the method of choice. While the KD-tree libraries Kiddo [26] and Neighbourhood [29] already perform well, our SPRK-tree consistently outperforms them. For sufficiently high dimensions, brute-force eventually becomes the best option, yet our SPRK-tree still outperforms brute-force by an order of magnitude for 16 dimensions.
For randomly distributed points, tree-based methods perform well for low dimensions but degrade for higher dimensions. This is contrasted by SNN and brute-force, which perform much worse on lower dimensions but outperform the tree-based methods for dimension 32. The exception to this is our SPRK-tree. While being the best tree-based method for low dimensions, it is also (slightly) faster than SNN and brute-force for dimension 32, outperforming the other tree-based methods by an order of magnitude.
While we developed the SPRK-tree with the embedding application in mind, it still outperforms the competitors on most benchmarks from other applications. The only exception is the benchmark of fixed-radius search in (very) high dimensions, where the SPRK-tree is usually second after SNN or brute-force, but still stays competitive.
Beyond the extensive comparison with other data structures, we provide an empirical analysis of how our design and optimization choices contribute to the performance of the SPRK-tree.
In this section we introduce the SPRK-tree, which improves upon the standard KD-tree through three primary mechanisms: aggressive pruning of subtrees using radius reduction, the use of SNN-inspired sorted leaf buckets, and an SVD-based rotation to improve performance on high-dimensional data. We begin with a brief description of the standard KD-tree, followed by a detailed explanation of our improvements. We conclude by describing implementation optimizations that further boost the performance of the SPRK-tree.
The KD-tree [17], [18] is a spatial index that organizes a point set \(P\subseteq\mathbb{R}^d\) for range queries. It chooses an axis-aligned hyperplane to split the whole space into two half-spaces such that both half-spaces contain the same number of points. This procedure is applied recursively, yielding a binary tree, where each internal node corresponds to a hyperplane and its two children correspond to the half-spaces. When the number of points falls below a predefined bucket size, the recursion stops and creates a leaf node. Each node is associated with a region, namely the intersection of all half-spaces on the path from the root to the node. With this, the leaves form a partition of the whole space \(\mathbb{R}^d\); for an example see 1.
A degree of freedom to fill in is the choice of the axis-aligned hyperplane in each step. We achieve this by cycling through the dimensions in round-robin order and placing the hyperplane at the median coordinate of the current points along that dimension. This halves the point set at every split and produces a tree of logarithmic depth.
A spherical range query with query point \(q\in\mathbb{R}^d\) and radius \(r\) asks for all points in \(P\) within distance \(r\) of \(q\). The KD-tree resolves this by recursively traversing the tree and pruning subtrees that cannot contain query results. Specifically, at an internal node, a child subtree is pruned if the query sphere does not intersect the child’s half-space. Upon reaching a leaf, every point in the leaf is checked against the query radius by an exhaustive search.
Note that the default pruning of a KD-tree does visit unnecessary regions, e.g., Region \(2\) in 1 despite the fact that Region \(2\) does not intersect the query ball. This can be prevented by a more informed pruning procedure [18], [27], [28]. For this, consider the example in [fig:sprk1] (which is the same as in 1), and let \(p\) be the point of Region \(2\) closest to the query point \(q\). Let \(\delta_1\) and \(\delta_2\) be the distance between \(p\) and \(q\) in dimensions \(1\) and \(2\), respectively. If we had computed \(\delta_1\) and \(\delta_2\), we could have pruned early by observing that \(\|(\delta_1, \delta_2)\| > r\). To compute \(\delta_1\) and \(\delta_2\), note that they are also the distances of \(q\) to the hyperplanes \(a\) and \(b\), respectively, which both separate \(q\) from Region \(2\). This is not a coincidence: for any given region, we can compute the corresponding \(\delta_1\) and \(\delta_2\) by going up the tree; for each dimension taking the maximum distance between \(q\) and a hyperplane (of that dimension) separating \(q\) from the region.
Similar to [27], [28], we do not recompute all \(\delta\) values for each node of the tree individually but keep track of a vector \(\Delta = (\delta_1, \dots, \delta_d)\) while traversing the tree. This allows us to update \(\|\Delta\|\) using a constant number of arithmetic operations for the distance check. We start with \(\delta_i = 0\) for all \(i \in [d]\). Now let \(u\) be a node in the tree with corresponding hyperplane \(H_u\). Consider traversing from \(u\) to a child \(v\) such that \(H_u\) separates the query point \(q\) from the region corresponding to \(v\). Then we update \(\delta_i\) to the distance from the query point \(q\) to the hyperplane \(H_u\), where \(i\) is the dimension of \(H_u\).
In a regular KD-tree, the points in a leaf node are filtered by a linear scan. The SPRK-tree replaces this with an SNN data structure [23] for each leaf. The SNN structure is constructed by projecting all the points of the leaf onto an axis-aligned line and sorting along its dimension; see Region \(8\) in [fig:sprk2]. Instead of now scanning all points in the leaf, we can prune some points based on the projection. We can do this by projecting the query point \(q\) to the SNN line and then finding all points within distance \(r\) in both directions from \(q\) on the line. This can be done with a binary search followed by a linear scan along the order. Note that the resulting set of points has to be filtered for points actually in the query ball.
Observe in [fig:sprk2] that the intersection of the query ball with Region \(8\) gives rise to an improvement over this. In the direction of the SNN line, this intersection spans only a smaller range of size \(2\cdot r_{\text{SNN}}\) than the range of \(2\cdot r\) queried above. To compute this smaller radius \(r_{\text{SNN}}\), we can utilize the distance vector \(\Delta\) from the previous section. To make this precise, let the dimension of the SNN line be, without loss of generality, the first dimension. Then \(r_{\text{SNN}}\) is the maximum value such that \((r_{\text{SNN}}, \delta_2, \dots, \delta_d)\) has length at most \(r\).
Furthermore, to avoid the binary search, the SPRK-tree computes a lookup table for the sorted coordinates of the leaf nodes by discretizing the coordinate range into fixed-width bins. Pseudocode for the query procedure of a node is given in 5.
In many scenarios, high-dimensional data often resides within a lower-dimensional subspace. The SPRK-tree exploits this property by rotating the data so that the \(d\) principal components with the largest variance align in descending order with the coordinate axes. Because the tree splits dimensions in a round-robin sequence, this rotation ensures that the earliest partitions occur along the directions of maximum spread, enabling more efficient pruning near the root. The rotation is computed via the SVD of a sampled subset of up to 10 k points of \(P\), which ensures that the SVD computation does not dominate the overall construction cost.
However, this transformation introduces a slight overhead during the query phase, as every subsequent query requires an initial rotation into the new coordinate space. Consequently, the SPRK-tree only applies this rotation for datasets with more than 16 dimensions. In these high-dimensional cases, the tree depth is often smaller than \(d\), meaning a standard traversal would never cycle through all dimensions. The SVD rotation guarantees that the limited number of splits are used to separate the data along the most informative dimensions.
Beyond algorithmic improvements, we engineered our implementation to utilize the full capabilities of modern hardware. In the following, we describe the most important optimizations.
During a query, the spatial index spends a large part of the time computing distances between the query point and candidate points in the leaf nodes. A naive SIMD (Single Instruction, Multiple Data [30]) implementation might attempt to accelerate this by computing the differences across the \(d\) dimensions of a single point simultaneously. However, this can be wasteful since the dimensionality \(d\) varies per dataset and does not necessarily align with the fixed width of hardware SIMD registers.
To resolve this, we vectorize across points rather than dimensions. The leaf buckets already store points in a contiguous array, and we group them into blocks of fixed width \(W\). We represent each block as a matrix \(M \in \mathbb{R}^{d\times W}\) in row-major order. By choosing \(W = 8\), the matrix rows fit exactly into one 256-bit SIMD register using 32-bit floating-point arithmetic.
Let \(q\) be the query point. To compute the squared distance to all \(W\) points simultaneously, we broadcast its \(i\)-th coordinate \(q_i\) into a SIMD register \(Q_i\), creating \(W\) copies of that value. Second, we calculate the coordinate-wise differences \(D_i = Q_i - M_i\) using one SIMD subtraction per dimension, where \(M_i\) is the \(i\)-th row of the matrix. We then square and sum these differences element-wise into an accumulator vector \(\mathrm{acc}\) using one fused multiply-add (FMA) instruction per dimension, effectively computing \(\mathrm{acc} = \sum_{i=1}^d D_i^2\) element-wise. This requires \(2d\) SIMD instructions (\(d\) subtractions and \(d\) FMAs) to evaluate the distance to \(W\) vectors; see 3.
For higher dimensions, we reduce the instruction count further by rewriting the squared distance \(\|p - q\|^2\) between a candidate point \(p\) and the query point \(q\) as \((p^\top p + q^\top q) - 2p^\top q\) and precomputing the squared norms \(p^\top p\), as suggested by Chen and Güttel [23]. By storing the halved squared norms, we also avoid a multiplication by \(2\) during the accumulation phase: \[\|p - q\|^2 = (p^\top p + q^\top q) - 2p^\top q \le r^2 \Longleftrightarrow \left(\frac{p^\top p}{2} + \frac{q^\top q}{2}\right) - p^\top q \le \frac{r^2}{2}.\] Computing the left side of the inequality for all \(W\) points simultaneously requires only \(d\) FMA instructions. However, it requires loading the precomputed values \(\frac{p^\top p}{2}\) from memory. Due to this overhead, it only becomes faster in practice for \(d \geq 6\).
To break data dependencies and maximize instruction throughput, we split the computation across 2 independent accumulators. We initialize them to the SIMD vectors containing \(\frac{p^\top p}{2}\) and \(\frac{q^\top q}{2}\) respectively, and alternate the subtraction of the coordinate-wise products of \(p^\top q\) using the previously allocated SIMD registers \(M_i\) and \(Q_i\). For \(d > 30\), utilizing 4 accumulation registers provides additional performance gains by further hiding instruction latency; see the detailed analysis in 3.3. Note that the dot-product reformulation potentially introduces numerical instabilities. We therefore add a small fixed \(\varepsilon=10^{-4}\) to the query radius in our implementation. Across all our test cases, this returned every point found by a naive reference implementation.
After computing all candidate distances, the indices corresponding to points fulfilling \(\|p - q\| \le r\) must be compacted into the result buffer. On CPUs with AVX-512F [31], [32] support, we use the vcompressps [33] instruction to gather the indices in a single step.
In addition to vectorized arithmetic, we optimize for cache-efficiency. Inner nodes are extremely lightweight, storing only the 32-bit floating-point split value of the dividing hyperplane. In contrast, leaf nodes are heavy, containing the SNN bucket with metadata and point range indices. Therefore, we keep leaves and inner nodes in separate arrays, and use for the inner nodes an implicit tree layout analogous to binary heaps.
Beyond the memory layout, we also optimize memory access patterns. During a query, a traditional KD-tree implementation frequently switches between traversing the tree and processing distance computations in the leaf nodes. This causes cache misses, as the CPU evicts the tree structure from the cache to load point coordinates. To resolve this, we decouple the query into a two-phase algorithm. The first phase traverses the tree in depth-first search order to compute the candidate ranges for the leaves. It identifies all ranges that intersect the query ball and collects them into an intermediate buffer. The second phase then computes the actual distances. Because the coordinate data is stored contiguously, iterating over the collected ranges results in a mostly linear access pattern (with skips between ranges), thereby improving the memory prefetcher performance.1
In this section, we present our experiments comparing the SPRK-tree against state-of-the-art spatial data structures. 3.1 outlines the experimental setup, introduces the baseline data structures, and details our new graph embedding benchmark. Following this, we compare query performance across a diverse set of benchmarks in 3.2, showing that the SPRK-tree consistently achieves the fastest query times across most evaluated datasets. Finally, we identify which optimizations contribute most to its efficiency in 3.3.
All benchmarks are performed single-threaded on a dual-socket Intel Xeon Gold 6144 CPU @ 3.50 GHz with 8 cores per socket, 32 L1 and 1 L2 cache per core, and 24.75 L3 cache per socket, supporting AVX-512. The system has 192 DDR4 RAM and runs
Ubuntu 24.04 LTS. Rust code was compiled with rustc version 1.94.0 in release mode with RUSTFLAGS = -Ctarget-cpu=native. C code was compiled with gcc version 14.2 using -O3 and -march=native.
For the Python libraries, CPython v3.12.3 was used.
After a 3-second warm-up, each benchmark runs for at least 20 seconds and at least 10 repetitions. We additionally verified that the measured times are consistent with hardware performance counters (cpu_cycles, cpu_ref_cycles,
and instructions).
We compare the SPRK-tree against a broad selection of state-of-the-art spatial index implementations. The evaluated implementations span the scikit-learn Balltree [36], a VP-tree implemented in Rust [37], the Boost R-tree [38], and SNN [23]. Since KD-trees proved to be the strongest category overall, we include KD-tree implementations across multiple languages: the Rust crates Kiddo [26], nabo [39] and Neighbourhood [29], the C libraries nanoflann [40] and CGAL [28], and the scikit-learn KD-tree [36].
In addition to our SPRK-tree, we provide a brute-force baseline and our own Orthtree and uniform grid implementations. Notably, our grid implementation requires the query radius to be specified at construction time, which may be prohibitive for some applications. For datasets with varying query radius, we build the grid using the average query radius. To exclude performance differences resulting from the choice of implementation language, we also provide our own Rust reimplementation of SNN, evaluated in 11.
As introduced in 1, we provide a new benchmark set based on graph embeddings. We use geometric inhomogeneous random graphs (GIRGs) [41] generated using the method in [42]. GIRGs are a widely used model for real-world networks with heterogeneous degree distributions and community structure. For generation, we set the average degree to 15, the power-law exponent to 2.5, the dimension to 4, and the temperature to 0 (corresponding to \(\alpha = \infty\)). We vary the number of vertices \(n\) such that \(n\in\{10^4, 10^{4.5}, 10^5, 10^{5.5}, 10^6 \}\) and use 3 different seeds for each parameter configuration.
To obtain a data set from these graphs, we compute embeddings using the WEmbed algorithm [3]. The algorithm iteratively adjusts vertex positions starting from a random initial state. In each iteration, the algorithm performs \(n\) spherical range queries, finding the set of vertices that are within a certain distance for each vertex. The query radius depends on the vertex degree, with high-degree vertices having a larger query radius. To obtain data sets with varying dimension, we set the embedding dimension \(d\) from 2–16 and additionally include 32. This results in \(5 \cdot 3 \cdot 16 = 240\) different embeddings across all graph sizes.
The embedding algorithm updates the vertex positions for \(t\) iterations until convergence (averaging \(t\approx 530\)), yielding \(t\) different position and query sets per embedding. For each run, we choose the positions from the latest iteration divisible by 100 to include in our benchmark, as later iterations feature less random and more interesting vertex positions and are usually more expensive. To normalize the number of queries, we choose at most 10 k queries per embedding, sorting the vertices by degree and then choosing every \(k\)-th vertex for \(k = n / 10^4\).
We also tested other parameter configurations during generation, but observed similar performance trends across all configurations; see 6. For readability, we only show results for \(n\in\{10^4, 10^5, 10^6\}\) in our evaluation. For benchmarks spanning all vertex counts see 7.
In this section, we compare the query performance of the evaluated data structures. We begin with the graph embedding benchmark, which motivated the design of the SPRK-tree. To evaluate the SPRK-tree’s applicability beyond graph embedding and obtain a more robust comparison, we consider four additional categories: uniform point distributions, high-dimensional real-world data, clustering workloads, and Point of Interest (POI) queries. For these, we also provide unaggregated results in 10.
| num vertices | 10000 | 100000 | 1000000 | ||||||
| dimensions | 2 | 8 | 16 | 2 | 8 | 16 | 2 | 8 | 16 |
| \(\text{sklearn KD-tree}\) | 67.7 | 110.4 | 316.8 | 144.9 | 265.9 | 2038.0 | 1009.5 | 1171.7 | 14708.7 |
| \(\text{CGAL KD-tree}\) | 4.3 | 50.5 | 253.5 | 8.6 | 93.8 | 1248.1 | 29.1 | 153.7 | 7734.9 |
| \(\text{nanoflann}\) | 8.6 | 15.7 | 121.2 | 27.5 | 41.6 | 869.4 | 183.6 | 189.0 | 8439.3 |
| \(\text{nabo}\) | 5.6 | 18.9 | 135.3 | 15.0 | 34.9 | 668.7 | 50.4 | 65.6 | 2866.3 |
| \(\text{Neighbourhood}\) | 4.6 | 13.0 | 81.3 | 17.0 | 28.1 | 448.7 | 156.9 | 72.3 | 2339.3 |
| \(\text{Kiddo}\) | 1.7 | 10.1 | 98.0 | 3.6 | 23.8 | 549.6 | 15.7 | 52.5 | 2900.8 |
| \(\text{sklearn Balltree}\) | 66.1 | 126.0 | 239.0 | 142.1 | 294.9 | 2041.0 | 990.5 | 2036.5 | 40821.5 |
| \(\text{SNN}\) | 39.2 | 39.6 | 62.2 | 69.2 | 227.3 | 451.5 | 238.8 | 2174.0 | 6875.8 |
| \(\text{VP-tree}\) | 17.5 | 38.0 | 119.3 | 53.5 | 116.0 | 1096.5 | 236.0 | 493.9 | 14639.7 |
| \(\text{Boost R-tree}\) | 2.6 | 30.9 | 146.6 | 5.3 | 71.7 | 1129.7 | 18.1 | 183.8 | 10543.4 |
| \(\text{Brute-Force}\) | 13.2 | 21.8 | 39.0 | 268.0 | 398.8 | 570.8 | 6133.0 | 7111.6 | 11808.4 |
| \(\text{Orthtree}\) | 0.6 | 55.1 | 1175.6 | 1.2 | 165.7 | 2162.7 | 3.2 | 269.9 | oom |
| \(\text{Uniform Grid}\) | 0.2 | 21.3 | oom | 0.5 | 76.0 | oom | 2.5 | 239.1 | oom |
| \(\text{SPRK-tree}\) | 0.2 | 1.8 | 7.8 | 0.5 | 6.2 | 141.7 | 2.6 | 31.1 | 1056.5 |
4 illustrates query times across embedding dimensions and vertex counts for a subset of data structures, including the best performing implementation for each algorithm. The SPRK-tree achieves the best query
time in every tested configuration, across all vertex counts and dimensions. For \(n=\SI{10}{k}\) and \(d \ge 12\), all structures except the SPRK-tree are outperformed by brute-force, an
expected consequence of the curse of dimensionality. Here, the SPRK-tree likely prevails due to better-optimized constant factors. SNN generally exhibits similar performance to brute-force for high dimensions. We observe that SNN performs worse in
dimensions not divisible by \(4\), which is likely due to the BLAS [43] implementation using a \(4\times4\) kernel for the sgemv subroutine. Furthermore, at 1 M vertices several structures (Kiddo, Boost R-tree, Neighbourhood, and scikit-learn Balltree) are slower in 2 dimensions than in 4. This might be an
artifact of the embedding benchmark, where lower dimensions often have a high density of points just outside the query radius, thereby slowing structures that are unable to efficiently prune these points.
2 lists the comprehensive results for all evaluated structures. Most competitors not shown in 4 are clearly outperformed by the remaining structures. In higher dimensions the grid and Orthtree ran out of memory (oom). For construction times see 8.
Next, we evaluate a benchmark of uniformly distributed points within a unit hypercube. We vary the number of points from 10 k to 10 M and the dimension from 2 to 32. For each point set, we perform 10 k queries with the query radius set such that the expected number of returned points is 15. 5 shows the results, restricted to the best-performing implementations for readability. The SPRK-tree is the fastest structure in every tested configuration. Its advantage is most pronounced at low point counts and low dimensions, where it outperforms the next-best competitor by up to an order of magnitude. This gap narrows at high dimensions with many points, maintaining a factor of roughly \(\num{1.6}\times\) over brute-force at dimension 32 with 10 M points. Most tree-based methods scale similarly to the SPRK-tree, but for low point counts the SPRK-tree is significantly faster, likely due to better cache performance; see 9 for additional details.
The relative ranking of the competitors shifts with dimensionality. In low dimensions (2 and 8), Kiddo and the Boost R-Tree are close to the SPRK-tree, while SNN is significantly slower. For larger dimensions, the more sophisticated tree-based structures tend to be increasingly outperformed by brute-force, and SNN converges toward brute-force performance.
| Data Set | \(n\) | \(d\) | |||||||
|---|---|---|---|---|---|---|---|---|---|
| Real-World high-dimensional datasets (in ms) [44] | |||||||||
| SIFT1M | 100 k | 128 | 5.8 | oom | 15.3 | 19.9 | 2.1 | 2.3 | |
| SIFT10K | 25 k | 128 | 0.9 | oom | 2.2 | 4.1 | 0.3 | 0.4 | |
| GIST | 1 M | 960 | 433.8 | oom | 2591.8 | 1415.4 | 183.2 | 255.4 | |
| GloVe100 | 1 M | 100 | 55.4 | oom | 49.5 | 295.7 | 29.7 | 4.4 | |
| Deep1B | 10 M | 96 | 308.0 | oom | 2831.2 | 2700.9 | 453.7 | 312.2 | |
| F-MNIST | 60 k | 784 | 21.2 | oom | 41.0 | 60.8 | 5.9 | 6.0 | |
| Clustering (in ns) [45] | |||||||||
| Banknote | 1.5 k | 4 | 1750.6 | 294.8 | 672.4 | 57833.6 | 21593.0 | 99.8 | |
| Wine | 0.2 k | 13 | 1131.4 | 204595.4 | 1922.6 | 54822.0 | 18907.0 | 143.0 | |
| Dermatology | 0.4 k | 34 | 3023.2 | oom | 10815.4 | 65718.8 | 23220.4 | 1136.4 | |
| Ecoli | 0.3 k | 7 | 884.4 | 1018.4 | 816.6 | 54875.8 | 18502.8 | 78.6 | |
| Point of Interest Search (in ns) [46] | |||||||||
| ATM | 12 k | 2 | 10189.0 | 242.0 | 269.0 | 53160.2 | 20069.5 | 86.0 | |
| Bakery | 33 k | 2 | 27424.0 | 454.2 | 648.8 | 69502.0 | 23148.8 | 112.0 | |
| Parking | 769 k | 2 | 637569.5 | 1990.5 | 3199.8 | 605426.2 | 66871.8 | 619.5 | |
| Bus Stop | 761 k | 2 | 628722.5 | 1481.2 | 2094.8 | 512312.8 | 51150.8 | 444.8 | |
| Restaurant | 103 k | 2 | 84881.0 | 598.0 | 745.0 | 109636.5 | 27474.8 | 167.5 | |
We further evaluate data structures on high-dimensional real-world datasets from the benchmark collection of Aumüller, Bernhardsson and Faithfull [44]. This collection features datasets with up to 10 M points and up to 960 dimensions; see 3 (first section). Some datasets exhibit very high dimension-to-point ratios; the small SIFT dataset, for instance, has 128 dimensions but only 25 k points.
Based on our previous benchmark results, one would expect tree-based methods to struggle heavily in this regime. While other tree-based implementations indeed fall behind by an order of magnitude or more, the SPRK-tree remains competitive, converging toward brute-force and SNN performance levels. SNN is the fastest method on four of the six nearest neighbor datasets, while the SPRK-tree consistently places second. We attribute the SPRK-tree’s strong performance mostly to the initial SVD rotation and optimized distance computation. The SVD is also applied by SNN, explaining the similar performance.
We also evaluate the clustering benchmarks introduced in the SNN paper [23]. These benchmarks use DBSCAN [47], a density-based clustering algorithm whose core operation is a fixed-radius range query for every point in the dataset. We isolate the all-to-all query component to measure spatial index performance independently of the clustering logic (note that [23] reports the end-to-end runtime). The datasets are small, containing 200–4500 points, but span dimensions from 4–256.
As shown in the second section of 3, the SPRK-tree achieves the best query time on all clustering datasets, often by a wide margin. Brute-force and Kiddo alternate as the second-best method depending on the dimensionality: Kiddo places second on the lower-dimensional datasets (Banknote, Ecoli), while brute-force takes second on the higher-dimensional ones (Dermatology, Wine). SNN performs poorly across all clustering datasets, likely due to constant overheads that dominate at these small point counts.
To evaluate performance on geographic data, we benchmark fixed-radius POI queries, e.g., finding all bus stops within 500 m of a given location. These datasets are two-dimensional but exhibit highly heterogeneous density, with dense clusters in cities and sparse coverage in rural areas. We use data from the OpenStreetMap project for Germany [46], with five categories: ATMs (\(\approx \SI{12}{k}\)), bakeries (\(\approx \SI{33}{k}\)), parking facilities (\(\approx \SI{769}{k}\)), bus stops (\(\approx \SI{761}{k}\)), and restaurants (\(\approx \SI{103}{k}\)). Each query set is constructed by using POIs of one category as dataset and POIs of a different category as query points. Queries are run at radii of 500 m, 1000 m, 2000 m, and 5000 m, and times are averaged across all radii and queries.
As shown in the POI section of 3, the SPRK-tree achieves the best query time across all five datasets. SNN is 2 to 3 orders of magnitude slower in this setting. The reason is likely that in 2 dimensions, the pruning of tree-based methods is more effective than projecting onto a single axis. Among tree-based methods, the Orthtree is the strongest competitor, yet the SPRK-tree outperforms it by a factor of \(2.8 \times\) on the smallest dataset (ATM, 12 k points) and up to \(3.3 \times\) on larger datasets (Bus Stop, 761 k points). We attribute this to spherical pruning, which reduces the distance checks by roughly 50 % in 2 dimensions.
In this section, we analyze the individual contributions of the different optimizations (both algorithmic and low-level) to the overall performance of the SPRK-tree. We first measure the reduction in distance checks achieved by radius reduction and SNN buckets, followed by evaluating the SIMD and memory layout optimizations.
Recall that the SPRK-tree is a KD-tree with radius reduction and SNN in the leaves (Section 2). In 6, we present an ablation study for these optimizations on the 100 k embedding dataset. We measure the number of distance computations performed by the data structure, which is independent of low-level optimizations.
The baseline is our KD-tree implementation without radius reduction or SNN (KD-tree) as described in 2.1. Using only radius reduction (Radius Reduction) cuts the number of distance checks
significantly, achieving up to 50 % fewer checks in higher dimensions. On the other hand, SNN buckets in isolation (SNN Buckets) are most effective in lower dimensions, where they achieve a similar reduction of 50 %. The full benefit is
realized by combining both techniques (Radius + SNN) and also using the reduced radius to further narrow the SNN candidate range (representing the full SPRK-Tree). This brings the total number of distance checks down to about 25 %
of a regular KD-tree for \(d \ge 6\).
To evaluate the low-level optimizations of the distance computation introduced in 2.5, we conduct a microbenchmark. We measure the computation of 128 distances to a single query point across varying
dimensions. We compare the direct distance computation without optimizations (naive), the approach using fused multiply-add instructions (FMA), the inner product reformulation (SNN Acc), and the variants using 2 and 4
accumulation registers (SNN 2 Acc and SNN 4 Acc). The results are shown in 7.
The fused multiply-add approach provides moderate improvement over the naive baseline, reducing runtime by roughly 10 % on average. The inner product reformulation initially performs worse, but becomes faster than FMA at 6 dimensions. In
low dimensions, loading the precomputed norms from memory likely outweighs the benefits of reduced arithmetic operations. The variants with multiple accumulators provide additional improvements in higher dimensions. For the dimensions targeted by our main
benchmarks (\(d \leq 16\)), 2 accumulators offer a good trade-off, with a speedup of up to 35 % over one accumulator. This is likely due to fewer data dependencies, allowing the CPU to better hide instruction latency.
Based on these insights, the SPRK-tree uses a dimension-dependent case distinction. For \(d < 6\) the SPRK-tree uses the FMA-based distance computation. For dimensions up to \(d = 32\), it switches to the inner product method with 2 accumulators. Beyond that, it uses 4 accumulators. We note that the optimal number of accumulators is architecture-dependent, as it interacts with register pressure and the compiler’s auto-vectorization logic.
In 8, we compare the full SPRK-tree against a naive variant that retains all algorithmic optimizations but disables the low-level optimizations from 2.5, including the memory layout and access pattern optimizations. The gains are most pronounced for low point counts, where for example even writing results to the output buffer constitutes a significant fraction of the total query time. Notably, the naive SPRK-tree already performs well for small point counts and high dimensions. This demonstrates the effectiveness of the algorithmic optimizations alone, with the low-level engineering further improving the performance by up to an order of magnitude at small point counts.
While developed primarily for repelling-force computation in graph embeddings, the SPRK-tree proves to be an efficient, general-purpose spatial index across a wide range of applications. Even in extreme scenarios, such as high-dimensional real-world datasets, the SPRK-tree remains competitive with the best-performing methods. This is achieved with a combination of algorithmic optimizations, such as the spherical pruning and SNN buckets, and low-level engineering that maximizes hardware utilization. Integrating the SPRK-tree should allow to substantially improve the performance of applications across various domains.
Another common strategy for high-dimensional regimes is to resort to approximate queries. Our initial attempts in this direction were rather unpromising, due to a significant drop in embedding quality for only small performance gain. However, additional work is warranted to fully evaluate the potential of this direction.
In 9 and 10, we provide the pseudocode for the SPRK-tree query procedure, which is described in 2. Since the SPRK-tree is balanced and every branch has the same depth, whether a node is a leaf or an internal node can be determined by its depth.
In 3, we described our new graph embedding benchmark set. In 11, we provide our justification for focusing on a single parameter configuration in the main benchmark. We benchmarked the effect of varying the GIRG generation parameters (average degree, power-law exponent, temperature, and sampling dimension) and observed similar performance trends across all configurations. Most notably, the change in relative performance of the different data structures remains largely within the margin of error across all configurations.
In 4 we evaluated the structures on a subset of the vertex counts of our benchmarking set to highlight the interesting performance characteristics. In 12 we show the results for all vertex counts for dimensions 2, 8, and 16. The analyzed trends remain consistent with the subset shown in 4, with the SPRK-tree achieving the best query time across all configurations.
In the context of graph embedding, the index needs to be reconstructed from scratch in every iteration of the embedding algorithm, making the index construction time a relevant performance metric. The index construction time of the benchmarked data structures, on the other hand, is negligible compared to the query time of the \(n\) queries performed in each iteration, so we do not include it in the main benchmark. 13 shows the index construction time of all evaluated data structures for the embedding benchmark. The implementations were tested in their default configurations, some of which use multithreading by default. The SPRK-tree has a competitive construction time, staying within the same order of magnitude as the other fast-constructing, tree-based methods. The Orthtree exhibits a noteworthy construction time with a steep increase with the number of dimensions but periodic large drops. The steepness naturally correlates to the exponential increase in the number of cells with increasing dimension. The periodic drops on the other hand are likely due to the fact that the depth of the tree decreases. Both the Orthtree and the uniform grid run out of memory for larger point counts at higher dimensions.
In 3 we highlighted the performance gains of the SPRK-tree on the uniform distribution benchmark, especially at low point counts and low dimensions. We attribute this to better cache performance, which is a result of the low-level optimizations described in 2.5. In 14 we demonstrate the effect of these optimizations for this benchmark by comparing the fully optimized SPRK-tree to a naive variant that retains all algorithmic optimizations but disables the low-level optimizations from 2.5. We observe that the optimizations provide a significant performance boost for lower point counts in 2D, emphasizing that the slope of the fully optimized SPRK-tree is due to better cache performance rather than worse scaling.
This can also be explained mathematically by considering the cache occupancy of the dataset. For example, in dimension 8, each point occupies 8 times 32 bits, so 10 k points (312 ) fit comfortably in the L2 cache (1 ). From 10 k to 100 k points the data starts to outgrow L2, causing a steep initial rise, exceeding its capacity at 100 k points (3.1 ). Beyond this range the scaling flattens again: although the full dataset surpasses the 24.75 L3 cache, the tree-traversal metadata remains substantially smaller and stays resident in L3. Because cache occupancy also depends on the dimension, this transition shifts to lower point counts as dimension increases, producing steeper initial slopes at higher dimensions.
In 3 we reported the average query time across all queries and multiple radii for the high-dimensional real-world datasets, clustering, and POI benchmarks. In 4, 5, and 6 we provide the unaggregated results. In contrast to the aggregated results in 3, 6 includes the uniform grid instead of the ball tree since it provides additional interesting insights. Keep in mind that the uniform grid profits strongly from the fact that the radius is fixed within each benchmark, providing a somewhat optimistic lower bound on its performance in realistic scenarios. Nonetheless, it is noteworthy that the uniform grid is still beaten by the SPRK-tree in almost all POI benchmark configurations, only surpassing it for small radii on the smaller datasets.
| Data Set | \(n\) | \(d\) | Radius | |||||
|---|---|---|---|---|---|---|---|---|
| SIFT1M | 100 k | 128 | 210.0 | 5.54 | 13.34 | 17.17 | 2.03 | 1.85 |
| SIFT1M | 100 k | 128 | 230.0 | 5.34 | 18.59 | 18.81 | 2.01 | 2.3 |
| SIFT1M | 100 k | 128 | 250.0 | 5.26 | 18.33 | 20.23 | 2.02 | 2.28 |
| SIFT1M | 100 k | 128 | 270.0 | 5.13 | 13.18 | 20.89 | 2.09 | 2.39 |
| SIFT1M | 100 k | 128 | 290.0 | 8.05 | 13.12 | 22.31 | 2.21 | 2.72 |
| SIFT10K | 25 k | 128 | 210.0 | 0.96 | 2.2 | 3.53 | 0.31 | 0.36 |
| SIFT10K | 25 k | 128 | 230.0 | 0.9 | 2.17 | 3.93 | 0.31 | 0.39 |
| SIFT10K | 25 k | 128 | 250.0 | 0.9 | 2.21 | 4.29 | 0.31 | 0.43 |
| SIFT10K | 25 k | 128 | 270.0 | 0.89 | 2.25 | 4.37 | 0.34 | 0.43 |
| SIFT10K | 25 k | 128 | 290.0 | 0.89 | 2.24 | 4.47 | 0.33 | 0.42 |
| GIST | 1 M | 960 | 0.8 | 450.18 | 2369.59 | 1318.36 | 172.8 | 228.73 |
| GIST | 1 M | 960 | 0.85 | 422.84 | 2979.02 | 1510.28 | 177.49 | 247.91 |
| GIST | 1 M | 960 | 0.9 | 426.66 | 2686.43 | 1402.26 | 186.33 | 263.32 |
| GIST | 1 M | 960 | 0.95 | 431.7 | 2453.25 | 1413.04 | 188.76 | 266.99 |
| GIST | 1 M | 960 | 1.0 | 437.73 | 2470.93 | 1433.22 | 190.58 | 270.03 |
| GloVe100 | 1 M | 100 | 0.94 | 56.01 | 34.47 | 291.37 | 30.37 | 3.45 |
| GloVe100 | 1 M | 100 | 0.97 | 54.28 | 39.83 | 292.71 | 30.95 | 3.88 |
| GloVe100 | 1 M | 100 | 1.01 | 58.04 | 60.35 | 300.59 | 28.46 | 4.36 |
| GloVe100 | 1 M | 100 | 1.04 | 53.36 | 54.16 | 301.59 | 29.56 | 5.01 |
| GloVe100 | 1 M | 100 | 1.07 | 55.32 | 58.87 | 292.27 | 29.03 | 5.48 |
| Deep1B | 10 M | 96 | 0.69 | 312.0 | 2701.79 | 2731.03 | 351.63 | 293.52 |
| Deep1B | 10 M | 96 | 0.75 | 307.12 | 3077.48 | 2621.94 | 369.2 | 312.19 |
| Deep1B | 10 M | 96 | 0.82 | 306.93 | 2463.67 | 2744.74 | 540.62 | 317.95 |
| Deep1B | 10 M | 96 | 0.88 | 306.91 | 2907.12 | 2818.33 | 505.08 | 318.85 |
| Deep1B | 10 M | 96 | 0.94 | 307.07 | 3005.74 | 2588.49 | 502.21 | 318.37 |
| F-MNIST | 60 k | 784 | 800.0 | 20.78 | 41.16 | 59.67 | 4.85 | 4.55 |
| F-MNIST | 60 k | 784 | 900.0 | 20.88 | 37.47 | 60.45 | 5.44 | 5.07 |
| F-MNIST | 60 k | 784 | 1000.0 | 21.25 | 38.84 | 60.34 | 5.93 | 5.9 |
| F-MNIST | 60 k | 784 | 1100.0 | 21.38 | 39.3 | 62.12 | 6.53 | 6.78 |
| F-MNIST | 60 k | 784 | 1200.0 | 21.52 | 48.19 | 61.39 | 7.09 | 7.47 |
| Data Set | \(n\) | \(d\) | Radius | ||||||
|---|---|---|---|---|---|---|---|---|---|
| Banknote | 1.5 k | 4 | 0.1 | 1624 | 164 | 219 | 54767 | 25491 | 57 |
| Banknote | 1.5 k | 4 | 0.2 | 1655 | 219 | 396 | 55098 | 19182 | 75 |
| Banknote | 1.5 k | 4 | 0.3 | 1739 | 292 | 664 | 57099 | 20545 | 96 |
| Banknote | 1.5 k | 4 | 0.4 | 1824 | 341 | 916 | 57155 | 20895 | 137 |
| Banknote | 1.5 k | 4 | 0.5 | 1911 | 458 | 1167 | 65049 | 21852 | 134 |
| Wine | 0.2 k | 13 | 2.2 | 1099 | 190648 | 1795 | 54623 | 18629 | 134 |
| Wine | 0.2 k | 13 | 2.3 | 1111 | 199223 | 1848 | 54697 | 18742 | 169 |
| Wine | 0.2 k | 13 | 2.4 | 1121 | 208220 | 1905 | 54926 | 18873 | 135 |
| Wine | 0.2 k | 13 | 2.5 | 1147 | 208048 | 1988 | 55108 | 19032 | 138 |
| Wine | 0.2 k | 13 | 2.6 | 1179 | 216838 | 2077 | 54756 | 19259 | 139 |
| Dermatology | 0.4 k | 34 | 5.0 | 2964 | oom | 10764 | 64381 | 22710 | 1132 |
| Dermatology | 0.4 k | 34 | 5.1 | 2971 | oom | 10597 | 66008 | 22897 | 1124 |
| Dermatology | 0.4 k | 34 | 5.2 | 3000 | oom | 10937 | 65162 | 23085 | 1136 |
| Dermatology | 0.4 k | 34 | 5.3 | 3040 | oom | 10756 | 66889 | 23731 | 1137 |
| Dermatology | 0.4 k | 34 | 5.4 | 3141 | oom | 11023 | 66154 | 23679 | 1153 |
| Ecoli | 0.3 k | 7 | 0.5 | 847 | 838 | 622 | 54748 | 18320 | 68 |
| Ecoli | 0.3 k | 7 | 0.6 | 864 | 937 | 696 | 54839 | 18378 | 74 |
| Ecoli | 0.3 k | 7 | 0.7 | 871 | 990 | 808 | 54171 | 18210 | 78 |
| Ecoli | 0.3 k | 7 | 0.8 | 899 | 1129 | 908 | 54538 | 18437 | 83 |
| Ecoli | 0.3 k | 7 | 0.9 | 941 | 1198 | 1049 | 56083 | 19169 | 90 |
| Radius | ||||||||
|---|---|---|---|---|---|---|---|---|
| ATM (12K) queried from supermarkets | ||||||||
| 500.0 | 10177 | 146 | 123 | 52143 | 49 | 18010 | 73 | |
| 1000.0 | 10032 | 177 | 167 | 51568 | 61 | 18270 | 78 | |
| 2000.0 | 10076 | 242 | 255 | 54878 | 89 | 22356 | 84 | |
| 5000.0 | 10471 | 403 | 531 | 54052 | 152 | 21642 | 109 | |
| Bakery (33K) queried from universities | ||||||||
| 500.0 | 27058 | 197 | 212 | 67264 | 82 | 18052 | 84 | |
| 1000.0 | 27247 | 316 | 400 | 66353 | 114 | 19452 | 93 | |
| 2000.0 | 27341 | 487 | 709 | 68633 | 155 | 22730 | 111 | |
| 5000.0 | 28050 | 817 | 1274 | 75758 | 249 | 32361 | 160 | |
| Parking (769K) queried from hospitals | ||||||||
| 500.0 | 635225 | 794 | 815 | 532944 | 441 | 26809 | 259 | |
| 1000.0 | 634616 | 1175 | 1364 | 508476 | 574 | 36600 | 367 | |
| 2000.0 | 638349 | 1974 | 3001 | 524187 | 960 | 53653 | 574 | |
| 5000.0 | 642088 | 4019 | 7619 | 856098 | 2486 | 150425 | 1278 | |
| Bus Stop (761K) queried from train stations | ||||||||
| 500.0 | 626257 | 584 | 590 | 487757 | 357 | 23940 | 226 | |
| 1000.0 | 626627 | 882 | 954 | 493638 | 405 | 29101 | 277 | |
| 2000.0 | 629476 | 1387 | 1630 | 519413 | 671 | 43714 | 401 | |
| 5000.0 | 632530 | 3072 | 5205 | 548443 | 1870 | 107848 | 875 | |
| Restaurant (103K) queried from train stations | ||||||||
| 500.0 | 83112 | 273 | 245 | 105247 | 122 | 21998 | 118 | |
| 1000.0 | 84002 | 382 | 389 | 106802 | 180 | 20737 | 131 | |
| 2000.0 | 85221 | 575 | 676 | 107534 | 255 | 29747 | 160 | |
| 5000.0 | 87189 | 1162 | 1670 | 118963 | 495 | 37417 | 261 | |
Throughout the paper we used the Python implementation of SNN provided by the original paper [23]. In 7 we compare this implementation to our own Rust implementation of SNN, which utilizes some of the low-level optimizations described in 2.5 such as the accumulation register optimization from 7. We observe that our implementation is significantly faster than the original Python implementation for low dimensions but is beaten by the original implementation in very high dimensions. This is likely due to the original implementation using a highly optimized BLAS library for the matrix-vector multiplications. Additionally, our implementation always processes at least 8 points per iteration, while the vectorized approach of the original can be more efficient when the candidate set is very small.
| Data Set | \(n\) | \(d\) | |||||
|---|---|---|---|---|---|---|---|
| Real-World high-dimensional datasets (in ms) [44] | |||||||
| SIFT1M | 100 k | 128 | 5.8 | 2.1 | 1.9 | 2.3 | |
| SIFT10K | 25 k | 128 | 0.9 | 0.3 | 0.3 | 0.4 | |
| GIST | 1 M | 960 | 433.8 | 183.2 | 223.0 | 255.4 | |
| GloVe100 | 1 M | 100 | 55.4 | 29.7 | 25.3 | 4.4 | |
| Deep1B | 10 M | 96 | 308.0 | 453.7 | 345.9 | 312.2 | |
| F-MNIST | 60 k | 784 | 21.2 | 5.9 | 6.4 | 6.0 | |
| Clustering (in ns) [45] | |||||||
| Banknote | 1.5 k | 4 | 1750.6 | 21593.0 | 208.4 | 99.8 | |
| Wine | 0.2 k | 13 | 1131.4 | 18907.0 | 118.6 | 143.0 | |
| Dermatology | 0.4 k | 34 | 3023.2 | 23220.4 | 842.4 | 1136.4 | |
| Ecoli | 0.3 k | 7 | 884.4 | 18502.8 | 84.6 | 78.6 | |
| Point of Interest Search (in ns) [46] | |||||||
| ATM | 12 k | 2 | 10189.0 | 20069.5 | 115.5 | 86.0 | |
| Bakery | 33 k | 2 | 27424.0 | 23148.8 | 319.5 | 112.0 | |
| Parking | 769 k | 2 | 637569.5 | 66871.8 | 5001.5 | 619.5 | |
| Bus Stop | 761 k | 2 | 628722.5 | 51150.8 | 4183.5 | 444.8 | |
| Restaurant | 103 k | 2 | 84881.0 | 27474.8 | 598.8 | 167.5 | |
| Embedding (in μs) | |||||||
| Embedding | 10.0 k | 2 | 13.1 | 39.2 | 1.0 | 0.2 | |
| Embedding | 100.0 k | 2 | 267.9 | 69.2 | 3.3 | 0.5 | |
| Embedding | 1000.0 k | 2 | 6133.1 | 238.8 | 17.4 | 2.6 | |
| Embedding | 10.0 k | 8 | 21.8 | 39.6 | 4.0 | 1.8 | |
| Embedding | 100.0 k | 8 | 398.8 | 227.3 | 63.4 | 6.2 | |
| Embedding | 1000.0 k | 8 | 7111.5 | 2174.0 | 745.4 | 31.1 | |
| Embedding | 10.0 k | 16 | 39.0 | 62.2 | 9.6 | 7.8 | |
| Embedding | 100.0 k | 16 | 570.7 | 451.5 | 216.7 | 141.7 | |
| Embedding | 1000.0 k | 16 | 11808.4 | 6875.8 | 3704.7 | 1056.5 | |