Package: cppbktree
C++ BK-TreeThis module provides a BK-Tree class written in C++ to hopefully better speed than pure-python solutions.InstallationYou can simply install it from PyPI:pip install cppbktreeFrom Sourcepython3-mbuild.&&python3-mpipinstalldist/cppbktree-*.whlUsagefromcppbktreeimportBKTreetree=BKTree([bytes([x])forxin[0,4,5,14]])tree.add(bytes([15]))print(tree.find(13,1))Because of the Python/C++ interface, currently this BK-Tree is limited to only hamming distances of bytearrays.
Pull requests are welcome.BenchmarksThe benchmark consists of inserting a varying amount of random 64-bit elements into the tree and then querying a value at varying distances.
This is done five times to get a hint for the standard deviation as is plotted with error bars.Comparison pybktree vs. cppbktree 0.1.0 with 32 K element chunking for 64-bit elementsThis is a benchmark for the specializedBKTree64, which operates on 64-bit values.
Doing the same withBKTreeand byte vectors of size 8 to represent 64-bit values is roughly 4x slower
because of additional allocations and because of pointer-chasing slowing down the linear lookup.
A more intricate version could simply concatenate the element vectors into one consecutive memory chunk to avoid these performance issues.Operationpybktree / scppbktree (8K chunks) / sSpeedupTree creation time88.531.5756Distance threshold 02.42e-041.81e-0513Distance threshold 17.49e-041.11e-046.7Distance threshold 28.55e-034.46e-0419Distance threshold 42.21e-013.35e-0366Distance threshold 84.22e+001.55e-02272Distance threshold 161.15e+013.00e-02383Scaling AnalysisIn this log-log plot, it can be seen that the lookups and creations follow various sublinear power laws.
Inserting one element in a tree of depth $d$ should roughly takeO(log(d))hamming distance evaluations.
Assuming an evenly distributed tree, the number of elements is given asN=d^nwherenis the maximum distance the metric returns.
For the hamming distance, n is the number of bits of the hash.
Solving this for the depth, givesd=log N / log n.
If you are only interested in the dependence toN, thenlog ncan be seen as a small constant factor.
Henceforth, the tree creation should followO(\sum_i^N \log i) = O( log( \product_i^N i )  ) = O(log(N!)).
Using the Stirling's approximation for the faculty, we getO(log(sqrt(N) N^N)) = O(log(N^(N+1/2))) = O(N log(N)).
However,log(N)is a very slow growing function, so the tree creation looks almost a linear function.Both, pybktree and cppbktree, have some jumps at roughly 1e4 elements but only cppbktree as a second jump at ~2e6 elements but only when looking up elements with distance <= 16.
I can't explain these jumps.
They almost look like memory caching effects.
Because of these jumps, the effective speedups for 10M elements varies quite a lot depending on the lookup distance.
Only the tree creation scaling is a very smooth curve except for some outliers for smaller runtimes.Here are the fitted power laws to the curves from the plot:operationpybktreecppbktreeTree creation1.12e-06 N^1.124.92e-07 N^1.05Distance threshold 02.04e-06 N^0.274.19e-07 N^0.27Distance threshold 12.06e-06 N^0.372.77e-07 N^0.38Distance threshold 21.68e-06 N^0.511.76e-07 N^0.54Distance threshold 41.36e-06 N^0.731.11e-07 N^0.74Distance threshold 81.10e-06 N^0.927.47e-08 N^0.94Distance threshold 161.05e-06 N^0.994.93e-08 N^1.05And here are the timings and speedups for operations on a tree with 10 million 64-bit elements:Operationpybktree / scppbktree / sSpeedupTree creation time88.5319.354.6Distance threshold 02.42e-042.38e-0510.2Distance threshold 17.49e-041.55e-044.8Distance threshold 28.55e-031.61e-035.3Distance threshold 42.21e-013.73e-025.9Distance threshold 84.220.607.1Distance threshold 1611.56.931.7The speedups of cppbktree over pybktree vary between ~2 and 10.
For smaller trees, the speedups would be even better.
Only the tree creation time speedup is quite independent of the tree size at roughly 5.Comparison pybktree vs. vptreeAt least in this benchmark with only 64-bit hashes and a hamming distance as metric and at least with this pure python implementation of a VP-Tree, the results are quite disappointing.
The vptree module is almost always slower.
The lookups are actually quite similar to pybktree (meaning still slower than lookups with cppbktree) but the tree creation is a full magnitude slower.
For the 100k elements, this results in pybktree being 7.7 times faster than vptree.Comparison linear lookup vs. cppbktreeThis log-log comparison plot shows that a simple linear lookup can compete with a BK tree.
For exact and almost exact lookups, the BK tree can become faster but even for a distance of 2,
it requires more than 1 M elements of size 8 B to amortize.
For lookups with even larger distance, the simple linear lookup dominates for the whole tested range up to 80 MB of data!The advantage of the linear lookup is better streaming behavior and less branching making it run faster on modern CPUs.
For batched lookup, the advantage might be even larger because whole batches can be looked up while the chunks of the haystack is still in CPU caches.
A mixture a BK tree and linear lookup, e.g., a BK tree with nodes with 1-10k elements, might be better over the whole range of test configurations.