Pkd-tree is an implementation of kd-tree that is:
- Blazingly fast.
- Scale to the billions input size.
- All operations are in parallel, including:
- tree construction,
- batch insertion/deletion,
-
$k$ -NN, - range count & range query
Necessary:
- CMake >= 3.15
- g++ or clang++ with C++20 features support (Tested using clang17, pass compilation on g++13) on Linux machines, we suggest clang++ for better performance.
- We use ParlayLib to support fork-join parallelism and some parallel primitives. It is provided as a submodule in our repository.
Optional:
- jemalloc, slightly memory allocation improvement.
- NUMA control, improve the performance for parallelism.
- ParGeo, CGAL, (and Zdtree), for benchmark comparison.
Try:
- Clone the repository, then
cd KDtree
- Initialize the submodule:
git submodule update --init
File structure:
.
├── include
├── parlaylib
└── tests
Name | Usage |
---|---|
include |
Source of Pkd-tree |
parlaylib |
Provide parallelism |
tests |
Helpers for testing |
mkdir build && cd build
cmake -DDEBUG=OFF ..
make
For better performance, please use clang++
for compilation, i.e.,
cmake -DDEBUG=OFF -DCMAKE_CXX_COMPILER=/usr/bin/clang++ ..
make
Useful flags:
Name | Usage | Default Value |
---|---|---|
DEBUG |
Compile in debug mode | OFF |
SERIAL |
Disable the parallelism | OFF |
CGAL |
Compile all executable related to CGAL | OFF |
JEMA |
Allocate the memory using jemalloc |
OFF |
More options can be found in CMakeLists.txt
.
Implemented in tests/test.cpp
. See also tests/cgal.cpp
and tests/zdtree/neighborsTime.C
.
./test -p [input_path] -d [dimension] -t [batch_mode] \
-r [rounds] -q [query_type] -i [read_insert_file_flag]
Parameters:
Name | Usage | Sample |
---|---|---|
-p |
Input points path | -p benchmark/sample.in |
-d |
Dimension of points | -d 3 |
-t |
Batch mode, see below | -t 0 |
-r |
How many rounds one test case run | -r 3 |
-q |
Query type, see below | -q 1 |
-i |
Whether to read the file for batch update | -i 0 |
Under build/
folder, first generate two inputs,
mkdir ../benchmark
./data_generator ../benchmark 10000 3 2 0
which will generate two files named 1.in
and 2.in
in benchmark/10000_3/
. Then to build the tree
./test -p ../benchmark/10000_3/1.in -d 3 -t 1 -q 1 -r 3 -i 1
To parse the output, see Test Framework Format below.
In default, the Pkd-tree stores all coordinates of points in 64-bit integer (long
). The balancing parameter is set to
Implemented in tests/testFramework.h
.
The test starts with
All outputs should be in one line, starts with the input file name and separated by a single space. If any output item is unavailable, outputs -1
instead.
The execution flow is shown below:
-
Function
buildTree (t>=0)
builds a $k$d-tree$T$ over$P$ . Outputs construction time and average tree depth separated by a single space. -
Function
insert (t>=1)
insert$Q[0,\cdots, \alpha\cdot n]$ into$T$ . Outputs insertion time. -
Function
delete (t>=2)
delete$Q[0,\cdots, \alpha\cdot n]$ from$T$ . Outputs delete time. -
Query
q & (1<<0)
asks KNN of$P$ on$T$ .If
$t=0$ , then$k={1,10,100}$ , otherwise,$k=10$ . For each KNN, outputs time for query, average depth and average # nodes visited. -
Query
q & (1<<1)
asks 10-NN w.r.t points$P'={P_0,\cdots,P_{1e6}}$ (assume$|P|\geq 1e6$ ). For each KNN, outputs time for query, average depth and average # nodes visited. -
Query
q & (1<<2)
asks range count on$T$ each with size$n = [0,n^{1/4}), [n^{1/4}, n^{1/2}), [n^{1/2}, n)$ For each query, outputs time for that query. -
Query
q & (1<<3)
asks range query on$T$ with size$n$ .If
$t=0$ , then$n = [0,n^{1/4}), [n^{1/4}, n^{1/2}), [n^{1/2}, n)$ , otherwise,$n=[n^{1/2},n)$ . For each query, outputs time for that query.Same outputs as in 6.
-
Update
q & (1<<4)
insert$Q$ into$T$ incrementally with step$10%$ .$\alpha=1$ . For each step, outputs the total time for such insertion. -
Update
q & (1<<5)
delete$P$ from$T$ incrementally with step$10%$ .$\alpha=1$ . For each step, outputs the total time for such deletion. -
Update
q & (1<<6)
construct$T$ from$P$ incrementally using steps$[0.1, 0.2, 0.25, 0.5]$ For each step, outputs the total construction time and the average tree depth. -
Update
q & (1<<7)
first construct a tree$T'$ using$P\cup Q$ , then delete$Q$ from$T'$ incrementally using steps$[0.1, 0.2, 0.25, 0.5]$ For each step, outputs the deletion time and the average tree depth afterwards. -
Query
q & (1<<8)
first construct a tree$T$ using$P$ and run a 10-NN on it, then construct another tree$T'$ from$P$ incrementally and perform a 10-NN on it. For each NN, outputs the total time, average depth and average # nodes visited. -
Query
q & (1<<9)
first construct a tree$T$ using$P$ and run a 10-NN on it, then construct another tree$T'$ from$P\cup Q$ , delete$Q$ from$T'$ incrementally, after which performs a 10-NN on it. For each NN, outputs the total time, average depth and average # nodes visited.
-
ParGeo
See
ParGeo
branch for more details. -
CGAL
- Make sure
CGAL
is installed. - Compile the whole program with the flag
-DCGAL=ON
attached. - There should be binary named
cgal
inbuild/
, which can be run using the same argument as mentioned in Usage above.
- Make sure
-
Zdtree (optional)
- Clone the repository of PBBS, then checkout the version
9820e9f
. - Copy folder
tests/zdtree/octTree
topbbsbench/benchmarks/nearestNeighbors/
. - Copy file
tests/zdtree/neighborsTime.C
topbbsbench/benchmarks/nearestNeighbors/bench/
. - Copy file
tests/common/time_loop.h
topbbsbench/common/
. - Run the program according to the tutorial in PBBS.
- Clone the repository of PBBS, then checkout the version
If you use our code, please cite our paper:
@misc{men2024pkdtreeparallelkdtreebatch,
title={Pkd-tree: Parallel $k$d-tree with Batch Updates},
author={Ziyang Men and Zheqi Shen and Yan Gu and Yihan Sun},
year={2024},
eprint={2411.09275},
archivePrefix={arXiv},
primaryClass={cs.DS},
url={https://arxiv.org/abs/2411.09275},
}