diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 1934d21e243..c08671b801a 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -39,3 +39,7 @@ PCL_ADD_BENCHMARK(sample_consensus_sac_model_cylinder FILES sample_consensus/sac PCL_ADD_BENCHMARK(search_radius_search FILES search/radius_search.cpp LINK_WITH pcl_io pcl_search pcl_filters ARGUMENTS "${PCL_SOURCE_DIR}/test/table_scene_mug_stereo_textured.pcd") + +PCL_ADD_BENCHMARK(search_kdtree_flann_vs_nanoflann FILES search/bench_kdtree.cpp + LINK_WITH pcl_io pcl_search pcl_kdtree + ARGUMENTS "${PCL_SOURCE_DIR}/test/table_scene_mug_stereo_textured.pcd") \ No newline at end of file diff --git a/benchmarks/search/bench_kdtree.cpp b/benchmarks/search/bench_kdtree.cpp new file mode 100644 index 00000000000..328ec97baa3 --- /dev/null +++ b/benchmarks/search/bench_kdtree.cpp @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Benchmark: pcl::search::KdTree (FLANN) vs pcl::search::KdTreeNanoflann +// Covers: tree construction, kNN query, radius search +// Motivation: PCL 1.15.1 added nanoflann support (#6250); this benchmark +// provides a regression guard and documents expected speedups. +// +// Usage: +// ./search_kdtree_flann_vs_nanoflann # synthetic 100k pts +// ./search_kdtree_flann_vs_nanoflann cloud.pcd # real PCD file + +#include +#include +#include +#include +#include +#include + +#ifdef PCL_HAS_NANOFLANN +#include +#endif + +#include +#include + +// --------------------------------------------------------------------------- +// Global cloud — loaded once, shared across all benchmarks +// --------------------------------------------------------------------------- + +static pcl::PointCloud::Ptr g_cloud; + +static pcl::PointCloud::Ptr +makeCloud(int N, float range = 100.f, unsigned seed = 42) +{ + auto cloud = std::make_shared>(); + cloud->reserve(N); + std::mt19937 rng(seed); + std::uniform_real_distribution dist(-range, range); + for (int i = 0; i < N; ++i) + cloud->emplace_back(dist(rng), dist(rng), dist(rng)); + cloud->width = N; + cloud->height = 1; + cloud->is_dense = true; + return cloud; +} + +// --------------------------------------------------------------------------- +// FLANN KdTree — construction +// --------------------------------------------------------------------------- + +static void BM_FlannKdTree_Build(benchmark::State& state) +{ + auto cloud = makeCloud(static_cast(state.range(0))); + for (auto _ : state) { + pcl::search::KdTree tree; + tree.setInputCloud(cloud); + benchmark::DoNotOptimize(tree); + } + state.SetItemsProcessed(state.iterations() * state.range(0)); + state.SetLabel("FLANN build"); +} +BENCHMARK(BM_FlannKdTree_Build) + ->Arg(10000)->Arg(100000)->Arg(500000) + ->Unit(benchmark::kMillisecond); + +// --------------------------------------------------------------------------- +// FLANN KdTree — kNN query +// --------------------------------------------------------------------------- + +static void BM_FlannKdTree_kNN(benchmark::State& state) +{ + const int k = static_cast(state.range(0)); + + pcl::search::KdTree tree; + tree.setInputCloud(g_cloud); + + pcl::PointXYZ query(0.f, 0.f, 0.f); + std::vector indices(k); + std::vector dists(k); + + for (auto _ : state) + benchmark::DoNotOptimize(tree.nearestKSearch(query, k, indices, dists)); + + state.SetItemsProcessed(state.iterations()); + state.SetLabel("FLANN kNN"); +} +BENCHMARK(BM_FlannKdTree_kNN) + ->Arg(1)->Arg(5)->Arg(20)->Arg(50) + ->Unit(benchmark::kMicrosecond); + +// --------------------------------------------------------------------------- +// FLANN KdTree — radius search +// --------------------------------------------------------------------------- + +static void BM_FlannKdTree_Radius(benchmark::State& state) +{ + const float r = static_cast(state.range(0)); + + pcl::search::KdTree tree; + tree.setInputCloud(g_cloud); + + pcl::PointXYZ query(0.f, 0.f, 0.f); + std::vector indices; + std::vector dists; + + for (auto _ : state) + benchmark::DoNotOptimize(tree.radiusSearch(query, r, indices, dists)); + + state.SetItemsProcessed(state.iterations()); + state.SetLabel("FLANN radius"); +} +BENCHMARK(BM_FlannKdTree_Radius) + ->Arg(5)->Arg(10)->Arg(20) + ->Unit(benchmark::kMicrosecond); + +// --------------------------------------------------------------------------- +// Nanoflann KdTree — same benchmarks +// --------------------------------------------------------------------------- + +#ifdef PCL_HAS_NANOFLANN + +static void BM_NanoflannKdTree_Build(benchmark::State& state) +{ + auto cloud = makeCloud(static_cast(state.range(0))); + for (auto _ : state) { + pcl::search::KdTreeNanoflann tree; + tree.setInputCloud(cloud); + benchmark::DoNotOptimize(tree); + } + state.SetItemsProcessed(state.iterations() * state.range(0)); + state.SetLabel("nanoflann build"); +} +BENCHMARK(BM_NanoflannKdTree_Build) + ->Arg(10000)->Arg(100000)->Arg(500000) + ->Unit(benchmark::kMillisecond); + +static void BM_NanoflannKdTree_kNN(benchmark::State& state) +{ + const int k = static_cast(state.range(0)); + + pcl::search::KdTreeNanoflann tree; + tree.setInputCloud(g_cloud); + + pcl::PointXYZ query(0.f, 0.f, 0.f); + std::vector indices(k); + std::vector dists(k); + + for (auto _ : state) + benchmark::DoNotOptimize(tree.nearestKSearch(query, k, indices, dists)); + + state.SetItemsProcessed(state.iterations()); + state.SetLabel("nanoflann kNN"); +} +BENCHMARK(BM_NanoflannKdTree_kNN) + ->Arg(1)->Arg(5)->Arg(20)->Arg(50) + ->Unit(benchmark::kMicrosecond); + +static void BM_NanoflannKdTree_Radius(benchmark::State& state) +{ + const float r = static_cast(state.range(0)); + + pcl::search::KdTreeNanoflann tree; + tree.setInputCloud(g_cloud); + + pcl::PointXYZ query(0.f, 0.f, 0.f); + std::vector indices; + std::vector dists; + + for (auto _ : state) + benchmark::DoNotOptimize(tree.radiusSearch(query, r, indices, dists)); + + state.SetItemsProcessed(state.iterations()); + state.SetLabel("nanoflann radius"); +} +BENCHMARK(BM_NanoflannKdTree_Radius) + ->Arg(5)->Arg(10)->Arg(20) + ->Unit(benchmark::kMicrosecond); + +#endif // PCL_HAS_NANOFLANN + +// --------------------------------------------------------------------------- +// main — load cloud from argv[1] or fall back to synthetic 100k points +// --------------------------------------------------------------------------- + +int main(int argc, char** argv) +{ + if (argc > 1) { + g_cloud = std::make_shared>(); + if (pcl::io::loadPCDFile(argv[1], *g_cloud) < 0) { + std::cerr << "Failed to load " << argv[1] << " — using synthetic cloud\n"; + g_cloud = makeCloud(100000); + } else { + std::cout << "Loaded " << g_cloud->size() << " pts from " << argv[1] << "\n"; + } + } else { + g_cloud = makeCloud(100000); + } + + ::benchmark::Initialize(&argc, argv); + ::benchmark::RunSpecifiedBenchmarks(); + ::benchmark::Shutdown(); + return 0; +}