diff --git a/CMakeLists.txt b/CMakeLists.txt index c6fbbc6a237449bff2a035f9a36e948d328fbf0e..ecc5fe0c0ca3ea290982f6325f577eb8517674e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ set(FLANN_VERSION 1.7.0) DISSECT_VERSION() GET_OS_INFO() +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) + # Add an "uninstall" target CONFIGURE_FILE ("${PROJECT_SOURCE_DIR}/cmake/uninstall_target.cmake.in" "${PROJECT_BINARY_DIR}/uninstall_target.cmake" IMMEDIATE @ONLY) @@ -101,6 +103,27 @@ else(NOT CUDA_FOUND) include_directories(${CUDA_INCLUDE_DIRS}) endif(NOT CUDA_FOUND) +# find Intel TBB +find_package(TBB) +if(TBB_FOUND AND TBB_DEBUG_FOUND) + message(STATUS "Intel TBB include dir: " ${TBB_INCLUDE_DIRS}) + message(STATUS "Intel TBB libs: " ${TBB_LIBRARIES}) + message(STATUS "Intel TBB libs (debug): " ${TBB_DEBUG_LIBRARIES}) + include_directories(${TBB_INCLUDE_DIRS}) +endif(TBB_FOUND AND TBB_DEBUG_FOUND) + +# print additional info +if(TBB_FOUND AND NOT TBB_DEBUG_FOUND) + message(WARNING "Only the Intel TBB (release) libraries were found") +endif() + +if(TBB_DEBUG_FOUND AND NOT TBB_FOUND) + message(WARNING "Only the Intel TBB (debug) libraries were found") +endif() + +if(NOT TBB_FOUND AND NOT TBB_DEBUG_FOUND) + message(WARNING "No intel TBB libraries were found") +endif() #set the C/C++ include path to the "include" directory diff --git a/README.md b/README.md index a0de91cf3908a22ead7d89ca986742a01e42ab4c..21e34b212e2972ad3d2f88d34526d99be89c3657 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,40 @@ If you want to try out the latest changes or contribute to FLANN, then it's reco If you just want to browse the repository, you can do so by going [here](https://github.com/mariusmuja/flann). + +Compiling FLANN with multithreading support +------------------------------------------ + +Make sure you have Intel Threading Building Blocks installed. You can get the latest version from www.threadingbuildingblocks.org. Alternatively you can also get it from package manager (this will probably not be the latest version, e.g. on Ubuntu 10.04 LTS only version 2.2 is found this way). + +For CMake to be able to detect the installation of Intel TBB, you need to have a tbb.pc in one of the directories of your PKG_CONFIG_PATH, it should look somewhat like this: + _________________________________________________ + + tbb.pc + _________________________________________________ + + prefix=/usr + exec_prefix=${prefix} + libdir=${exec_prefix}/lib + includedir=${prefix}/include + + Name: Threading Building Blocks + Description: Intel's parallelism library for C++ + URL: http://www.threadingbuildingblocks.org/ + Version: 3.0update7 + Libs: -L${libdir} -ltbb + Cflags: -I${includedir} + _________________________________________________ + + +Using multithreaded FLANN in your project +----------------------------------------- + +When multithreaded FLANN is compiled and installed, all you need to do now is compile your project with the -DTBB compiler flag to enable multithreading support. For example; in CMake you can achieve this by stating "ADD_DEFINITIONS(-DTBB)" for your target. + +Have a look at the "flann::Index::knnSearch" section in the manual on how to specify the number of cores for knn- and radiussearch. + + Conditions of use ----------------- diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake new file mode 100644 index 0000000000000000000000000000000000000000..06634b5276c465ec0cb439042782761006acd7fe --- /dev/null +++ b/cmake/FindTBB.cmake @@ -0,0 +1,59 @@ +############################################################################### +# Find Intel Threading Building Blocks +# +# This sets the following variables: +# +# TBB_INCLUDE_DIRS - Directories containing the TBB include files. +# TBB_LIBRARY_DIRS - Directories containing the TBB libs. +# +# (release libs) +# TBB_FOUND - True if TBB was found. +# TBB_LIBRARIES - Libraries needed to use TBB. +# +# (debug libs) +# TBB_DEBUG_FOUND - True if TBB was found. +# TBB_DEBUG_LIBRARIES - Libraries needed to use TBB. + +find_package(PkgConfig) +pkg_check_modules(PC_TBB tbb) + +# Find include directory +find_path(TBB_INCLUDE_DIR tbb/task_scheduler_init.h + HINTS ${PC_TBB_INCLUDEDIR} ${PC_TBB_INCLUDE_DIRS}) + +# Find libraries +find_library(TBB_LIBRARY tbb + HINTS ${PC_TBB_LIBDIR} ${PC_TBB_LIBRARY_DIRS}) + +find_library(TBB_DEBUG_LIBRARY tbb_debug + HINTS ${PC_TBB_LIBDIR} ${PC_TBB_LIBRARY_DIRS}) + +#find_library(TBB_MALLOC_LIBRARY tbbmalloc +# HINTS ${PC_TBB_LIBDIR} ${PC_TBB_LIBRARY_DIRS}) + +#find_library(TBB_MALLOC_LIBRARY tbbmalloc_debug +# HINTS ${PC_TBB_LIBDIR} ${PC_TBB_LIBRARY_DIRS}) + +#find_library(TBB_MALLOC_PROXY_LIBRARY tbbmalloc_proxy +# HINTS ${PC_TBB_LIBDIR} ${PC_TBB_LIBRARY_DIRS}) + +#find_library(TBB_MALLOC_PROXY_LIBRARY tbbmalloc_proxy_debug +# HINTS ${PC_TBB_LIBDIR} ${PC_TBB_LIBRARY_DIRS}) + +# Set the appropriate CMake variables and mark them as advanced +set(TBB_INCLUDE_DIRS ${PC_TBB_INCLUDEDIR}) +set(TBB_LIBRARY_DIRS ${PC_TBB_LIBDIR}) +#set(TBB_LIBRARIES ${TBB_LIBRARY};${TBB_MALLOC_LIBRARY};${TBB_MALLOC_PROXY_LIBRARY}) +set(TBB_LIBRARIES ${TBB_LIBRARY}) +#set(TBB_DEBUG_LIBRARIES ${TBB_DEBUG_LIBRARY};${TBB_MALLOC_DEBUG_LIBRARY};${TBB_MALLOC_PROXY_DEBUG_LIBRARY}) +set(TBB_DEBUG_LIBRARIES ${TBB_DEBUG_LIBRARY}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(tbb DEFAULT_MSG TBB_LIBRARY TBB_INCLUDE_DIR) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(tbb_debug DEFAULT_MSG TBB_DEBUG_LIBRARY TBB_INCLUDE_DIR) + +mark_as_advanced(TBB_LIBRARY TBB_DEBUG_LIBRARY TBB_INCLUDE_DIR) +#mark_as_advanced(TBB_LIBRARY TBB_DEBUG_LIBRARY TBB_MALLOC_LIBRARY TBB_DEBUG_MALLOC_LIBRARY TBB_MALLOC_PROXY_LIBRARY TBB_MALLOC_PROXY_DEBUG_LIBRARY TBB_INCLUDE_DIR) + diff --git a/doc/manual.tex b/doc/manual.tex index acbae5fab907e1db8e5467d37cd0bf4b6f74f312..a9d80d40e7e0b676b1026ed7aa8cc4af261f08ed 100644 --- a/doc/manual.tex +++ b/doc/manual.tex @@ -247,8 +247,15 @@ options use the \texttt{cmake-gui} application after \texttt{cmake} has finished \end{center} \end{figure} +\subsection{Compiling FLANN with multithreading support} +To make use of FLANN multithreading support, it is required that Intel Threading Building Blocks is installed correctly +on your system. You can either get it from your package manager or from: +\begin{center} + \texttt{http://threadingbuildingblocks.org/} +\end{center} - +You also need a pkgconfig file for locating the Intel TBB install and need to specify a -DTBB compiler flag on compilation +of your project. Have a look at the readme file in the root folder of FLANN for more detailed instructions. \section{Using FLANN} @@ -491,7 +498,8 @@ struct SearchParams { SearchParams(int checks = 32, float eps = 0, - bool sorted = true); + bool sorted = true, + int cores = 1); }; \end{Verbatim} \begin{description} @@ -502,6 +510,7 @@ If automatic configuration was used when the index was created, the number of ch required to achieve the specified precision was also computed, to use that value specify \texttt{CHECKS\_AUTOTUNED}. \item[eps] Search for eps-approximate neighbors (only used by KDTreeSingleIndex and KDTreeCuda3dIndex). \item[sorted] Used only by radius search, specifies if the neighbors returned should be sorted by distance. + \item[cores] How many cores to assign to the search (specify -1 for automatic core selection). \end{description} \end{description} diff --git a/src/cpp/CMakeLists.txt b/src/cpp/CMakeLists.txt index f7128e66f25bdde561a1c0c06667d09814d2efa9..397a4afafad81bc48a120dd88626dcdb7da58b86 100644 --- a/src/cpp/CMakeLists.txt +++ b/src/cpp/CMakeLists.txt @@ -50,6 +50,9 @@ else() cuda_add_library(flann_cpp SHARED ${CPP_SOURCES}) endif() endif() +if(TBB_FOUND AND TBB_DEBUG_FOUND) + target_link_libraries(flann_cpp ${TBB_LIBRARIES}) +endif(TBB_FOUND AND TBB_DEBUG_FOUND) set_target_properties(flann_cpp PROPERTIES VERSION ${FLANN_VERSION} @@ -68,6 +71,9 @@ endif() #debug libraries add_library(flann_cpp-gd SHARED ${CPP_SOURCES}) +if(TBB_FOUND AND TBB_DEBUG_FOUND) + target_link_libraries(flann_cpp-gd ${TBB_DEBUG_LIBRARIES}) +endif(TBB_FOUND AND TBB_DEBUG_FOUND) set_target_properties(flann_cpp-gd PROPERTIES COMPILE_FLAGS ${CMAKE_CXX_FLAGS_DEBUG} DEFINE_SYMBOL FLANN_EXPORTS diff --git a/src/cpp/flann/algorithms/kdtree_single_index.h b/src/cpp/flann/algorithms/kdtree_single_index.h index c3e55fb8935bff59bcce987cab473f65165b518c..6f469363e6b355eb6590c77acb874dd7af0c8839 100644 --- a/src/cpp/flann/algorithms/kdtree_single_index.h +++ b/src/cpp/flann/algorithms/kdtree_single_index.h @@ -60,7 +60,7 @@ struct KDTreeSingleIndexParams : public IndexParams /** - * Randomized kd-tree index + * Single kd-tree index * * Contains the k-d trees and other information for indexing a set of points * for nearest-neighbor matching. diff --git a/src/cpp/flann/flann.h b/src/cpp/flann/flann.h index 351ee0335c57e78289fc702c4f3c6b0a50a84192..d46e4443079b995fa76338ac9b59707c8a6f482f 100644 --- a/src/cpp/flann/flann.h +++ b/src/cpp/flann/flann.h @@ -211,7 +211,6 @@ FLANN_EXPORT flann_index_t flann_load_index_int(char* filename, indices = pointer to matrix for the indices of the nearest neighbors of the testset features in the dataset (must have trows number of rows and nn number of columns) nn = how many nearest neighbors to return - index_params = index related parameters flann_params = generic flann parameters Returns: zero or -1 for error @@ -276,6 +275,8 @@ FLANN_EXPORT int flann_find_nearest_neighbors_int(int* dataset, trows = number of rows (features) in the query dataset (same dimensionality as features in the dataset) indices = pointer to matrix for the indices of the nearest neighbors of the testset features in the dataset (must have trows number of rows and nn number of columns) + dists = pointer to matrix for the distances of the nearest neighbors of the testset features in the dataset + (must have trows number of rows and 1 column) nn = how many nearest neighbors to return flann_params = generic flann parameters @@ -330,11 +331,16 @@ FLANN_EXPORT int flann_find_nearest_neighbors_index_int(flann_index_t index_id, * search will return all the neighbours found within a search radius * of the query point. * - * The check parameter in the function below sets the level of approximation + * The check parameter in the FLANNParameters below sets the level of approximation * for the search by only visiting "checks" number of features in the index * (the same way as for the KNN search). A lower value for checks will give * a higher search speedup at the cost of potentially not returning all the * neighbours in the specified radius. + * + * The cores parameter in the FLANNParameters below sets the number of cores + * that will be used for the radius search, in case Intel TBB is present on + * the system and FLANN is built with multicore support on. Auto core selection + * can be achieved by setting the number of cores to -1. */ FLANN_EXPORT int flann_radius_search(flann_index_t index_ptr, /* the index */ float* query, /* query point */ diff --git a/src/cpp/flann/flann.hpp b/src/cpp/flann/flann.hpp index e83d278daf42f84f84d98fb2c48865b1ccf5859b..c5294116233c4e93a533f53e34aabe0fe03657ec 100644 --- a/src/cpp/flann/flann.hpp +++ b/src/cpp/flann/flann.hpp @@ -36,6 +36,14 @@ #include #include +#ifdef TBB + #include + #include + #include + #include +#endif + + #include "flann/general.h" #include "flann/util/matrix.h" #include "flann/util/params.h" @@ -43,6 +51,10 @@ #include "flann/algorithms/all_indices.h" +#ifdef TBB + #include "flann/tbb/bodies.hpp" +#endif + namespace flann { @@ -104,8 +116,13 @@ public: typedef typename Distance::ElementType ElementType; typedef typename Distance::ResultType DistanceType; +#ifdef TBB + Index(const Matrix& features, const IndexParams& params, Distance distance = Distance() ) + : index_params_(params), atomic_count_() +#else Index(const Matrix& features, const IndexParams& params, Distance distance = Distance() ) : index_params_(params) +#endif { flann_algorithm_t index_type = get_param(params,"algorithm"); loaded_ = false; @@ -213,12 +230,69 @@ public: * \param[in] params Search parameters */ int knnSearch(const Matrix& queries, - Matrix& indices, - Matrix& dists, - size_t knn, - const SearchParams& params) + Matrix& indices, + Matrix& dists, + size_t knn, + const SearchParams& params) { - return nnIndex_->knnSearch(queries, indices, dists, knn, params); + assert(queries.cols == veclen()); + assert(indices.rows >= queries.rows); + assert(dists.rows >= queries.rows); + assert(indices.cols >= knn); + assert(dists.cols >= knn); + bool sorted = get_param(params,"sorted",true); + bool use_heap = get_param(params,"use_heap",false); +#ifdef TBB + int cores = get_param(params,"cores",1); + assert(cores >= 1 || cores == -1); +#endif + + int count = 0; + +#ifdef TBB + // Check if we need to do multicore search or stick with singlecore FLANN (less overhead) + if(cores == 1) + { +#endif + if (use_heap) { + KNNResultSet2 resultSet(knn); + for (size_t i = 0; i < queries.rows; i++) { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries[i], params); + resultSet.copy(indices[i], dists[i], knn, sorted); + count += resultSet.size(); + } + } + else { + KNNSimpleResultSet resultSet(knn); + for (size_t i = 0; i < queries.rows; i++) { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries[i], params); + resultSet.copy(indices[i], dists[i], knn, sorted); + count += resultSet.size(); + } + } +#ifdef TBB + } + else + { + // Initialise the task scheduler for the use of Intel TBB parallel constructs + tbb::task_scheduler_init task_sched(cores); + + // Make an atomic integer count, such that we can keep track of amount of neighbors found + atomic_count_ = 0; + + // Use auto partitioner to choose the optimal grainsize for dividing the query points + flann::parallel_knnSearch parallel_knn(queries, indices, dists, knn, params, nnIndex_, atomic_count_); + tbb::parallel_for(tbb::blocked_range(0,queries.rows), + parallel_knn, + tbb::auto_partitioner()); + + count = atomic_count_; + } +#endif + + return count; } @@ -231,12 +305,74 @@ public: * \param[in] params Search parameters */ int knnSearch(const Matrix& queries, - std::vector< std::vector >& indices, - std::vector >& dists, - size_t knn, - const SearchParams& params) + std::vector< std::vector >& indices, + std::vector >& dists, + size_t knn, + const SearchParams& params) { - return nnIndex_->knnSearch(queries, indices, dists, knn, params); + assert(queries.cols == veclen()); + bool sorted = get_param(params,"sorted",true); + bool use_heap = get_param(params,"use_heap",false); +#ifdef TBB + int cores = get_param(params,"cores",1); + assert(cores >= 1 || cores == -1); +#endif + + if (indices.size() < queries.rows ) indices.resize(queries.rows); + if (dists.size() < queries.rows ) dists.resize(queries.rows); + + int count = 0; + +#ifdef TBB + // Check if we need to do multicore search or stick with singlecore FLANN (less overhead) + if(cores == 1) + { +#endif + if (use_heap) { + KNNResultSet2 resultSet(knn); + for (size_t i = 0; i < queries.rows; i++) { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries[i], params); + size_t n = std::min(resultSet.size(), knn); + indices[i].resize(n); + dists[i].resize(n); + resultSet.copy(&indices[i][0], &dists[i][0], n, sorted); + count += n; + } + } + else { + KNNSimpleResultSet resultSet(knn); + for (size_t i = 0; i < queries.rows; i++) { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries[i], params); + size_t n = std::min(resultSet.size(), knn); + indices[i].resize(n); + dists[i].resize(n); + resultSet.copy(&indices[i][0], &dists[i][0], n, sorted); + count += n; + } + } +#ifdef TBB + } + else + { + // Initialise the task scheduler for the use of Intel TBB parallel constructs + tbb::task_scheduler_init task_sched(cores); + + // Make an atomic integer count, such that we can keep track of amount of neighbors found + atomic_count_ = 0; + + // Use auto partitioner to choose the optimal grainsize for dividing the query points + flann::parallel_knnSearch2 parallel_knn(queries, indices, dists, knn, params, nnIndex_, atomic_count_); + tbb::parallel_for(tbb::blocked_range(0,queries.rows), + parallel_knn, + tbb::auto_partitioner()); + + count = atomic_count_; + } +#endif + + return count; } @@ -250,12 +386,97 @@ public: * \returns Number of neighbors found */ int radiusSearch(const Matrix& queries, - Matrix& indices, - Matrix& dists, - float radius, - const SearchParams& params) + Matrix& indices, + Matrix& dists, + float radius, + const SearchParams& params) { - return nnIndex_->radiusSearch(queries, indices, dists, radius, params); + assert(queries.cols == veclen()); +#ifdef TBB + int cores = get_param(params,"cores",1); + assert(cores >= 1 || cores == -1); +#endif + + int count = 0; + +#ifdef TBB + // Check if we need to do multicore search or stick with singlecore FLANN (less overhead) + if(cores == 1) + { +#endif + int max_neighbors = get_param(params, "max_neighbors", -1); + + // just count neighbors + if (max_neighbors==0) { + CountRadiusResultSet resultSet(radius); + for (size_t i = 0; i < queries.rows; i++) { + resultSet.clear(); + findNeighbors(resultSet, queries[i], params); + count += resultSet.size(); + } + } + else { + size_t num_neighbors = std::min(indices.cols, dists.cols); + bool sorted = get_param(params, "sorted", true); + bool has_max_neighbors = has_param(params,"max_neighbors"); + + // explicitly indicated to use unbounded radius result set + // or we know there'll be enough room for resulting indices and dists + if (max_neighbors<0 && (has_max_neighbors || num_neighbors>=size())) { + RadiusResultSet resultSet(radius); + for (size_t i = 0; i < queries.rows; i++) { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries[i], params); + size_t n = resultSet.size(); + count += n; + if (n>num_neighbors) n = num_neighbors; + resultSet.copy(indices[i], dists[i], n, sorted); + + // mark the next element in the output buffers as unused + if (n::infinity(); + } + } + else { + if (max_neighbors<0) max_neighbors = num_neighbors; + else max_neighbors = std::min(max_neighbors,(int)num_neighbors); + // number of neighbors limited to max_neighbors + KNNRadiusResultSet resultSet(radius, max_neighbors); + for (size_t i = 0; i < queries.rows; i++) { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries[i], params); + size_t n = resultSet.size(); + count += n; + if ((int)n>max_neighbors) n = max_neighbors; + resultSet.copy(indices[i], dists[i], n, sorted); + + // mark the next element in the output buffers as unused + if (n::infinity(); + } + } + } +#ifdef TBB + } + else + { + // Initialise the task scheduler for the use of Intel TBB parallel constructs + tbb::task_scheduler_init task_sched(cores); + + // Make an atomic integer count, such that we can keep track of amount of neighbors found + atomic_count_ = 0; + + // Use auto partitioner to choose the optimal grainsize for dividing the query points + flann::parallel_radiusSearch parallel_radius(queries, indices, dists, radius, params, nnIndex_, atomic_count_); + tbb::parallel_for(tbb::blocked_range(0,queries.rows), + parallel_radius, + tbb::auto_partitioner()); + + count = atomic_count_; + } +#endif + + return count; } @@ -269,12 +490,89 @@ public: * \returns Number of neighbors found */ int radiusSearch(const Matrix& queries, - std::vector< std::vector >& indices, - std::vector >& dists, - float radius, - const SearchParams& params) + std::vector< std::vector >& indices, + std::vector >& dists, + float radius, + const SearchParams& params) { - return nnIndex_->radiusSearch(queries, indices, dists, radius, params); + assert(queries.cols == veclen()); +#ifdef TBB + int cores = get_param(params,"cores",1); + assert(cores >= 1 || cores == -1); +#endif + + int count = 0; + +#ifdef TBB + // Check if we need to do multicore search or stick with singlecore FLANN (less overhead) + if(cores == 1) + { +#endif + int max_neighbors = get_param(params, "max_neighbors", -1); + + // just count neighbors + if (max_neighbors==0) { + CountRadiusResultSet resultSet(radius); + for (size_t i = 0; i < queries.rows; i++) { + resultSet.clear(); + findNeighbors(resultSet, queries[i], params); + count += resultSet.size(); + } + } + else { + bool sorted = get_param(params, "sorted", true); + if (indices.size() < queries.rows ) indices.resize(queries.rows); + if (dists.size() < queries.rows ) dists.resize(queries.rows); + + if (max_neighbors<0) { + // search for all neighbors + RadiusResultSet resultSet(radius); + for (size_t i = 0; i < queries.rows; i++) { + resultSet.clear(); + findNeighbors(resultSet, queries[i], params); + size_t n = resultSet.size(); + count += n; + indices[i].resize(n); + dists[i].resize(n); + resultSet.copy(&indices[i][0], &dists[i][0], n, sorted); + } + } + else { + // number of neighbors limited to max_neighbors + KNNRadiusResultSet resultSet(radius, max_neighbors); + for (size_t i = 0; i < queries.rows; i++) { + resultSet.clear(); + findNeighbors(resultSet, queries[i], params); + size_t n = resultSet.size(); + count += n; + if ((int)n>max_neighbors) n = max_neighbors; + indices[i].resize(n); + dists[i].resize(n); + resultSet.copy(&indices[i][0], &dists[i][0], n, sorted); + } + } + } +#ifdef TBB + } + else + { + // Initialise the task scheduler for the use of Intel TBB parallel constructs + tbb::task_scheduler_init task_sched(cores); + + // Reset atomic count before passing it on to the threads, such that we can keep track of amount of neighbors found + atomic_count_ = 0; + + // Use auto partitioner to choose the optimal grainsize for dividing the query points + flann::parallel_radiusSearch2 parallel_radius(queries, indices, dists, radius, params, nnIndex_, atomic_count_); + tbb::parallel_for(tbb::blocked_range(0,queries.rows), + parallel_radius, + tbb::auto_partitioner()); + + count = atomic_count_; + } +#endif + + return count; } /** @@ -309,6 +607,11 @@ private: bool loaded_; /** Parameters passed to the index */ IndexParams index_params_; +#ifdef TBB + /** Atomic count variable, passed to the different threads for keeping track of the amount of neighbors found. + \note Intel TBB 'catch': must be data member for correct initialization tbb::atomic has no declared constructors !! */ + tbb::atomic atomic_count_; +#endif }; /** diff --git a/src/cpp/flann/tbb/bodies.hpp b/src/cpp/flann/tbb/bodies.hpp new file mode 100644 index 0000000000000000000000000000000000000000..56f41d1e3619b66968a628c379dd08cab3b71602 --- /dev/null +++ b/src/cpp/flann/tbb/bodies.hpp @@ -0,0 +1,471 @@ +/*********************************************************************** + * Software License Agreement (BSD License) + * + * Copyright 2010-2011 Nick Vanbaelen (nickon@acm.org). All rights reserved. + * + * THE BSD LICENSE + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *************************************************************************/ + +#ifndef FLANN_TBB_BODIES_H +#define FLANN_TBB_BODIES_H + +#include +#include + +#include "flann/util/matrix.h" +#include "flann/util/params.h" +#include "flann/util/result_set.h" + +namespace flann +{ + +template +class parallel_knnSearch +{ +public: + typedef typename Distance::ElementType ElementType; + typedef typename Distance::ResultType DistanceType; + + parallel_knnSearch(const Matrix& queries, + Matrix& indices, + Matrix& distances, + size_t knn, + const SearchParams& params, + NNIndex* nnIndex, + tbb::atomic& count) + : queries_(queries), + indices_(indices), + distances_(distances), + knn_(knn), + params_(params), + nnIndex_(nnIndex), + count_(count) + + {} + + /* default destructor will do */ + + /* default copy constructor will do, + parallel for will use this to create a separate parallel_knnSearch object + for each worker thread (pointers will be copied, which is OK) */ + + /** + * Perform knnSearch for the query points assigned to this worker thread + * \param r query point range assigned for this worker thread to operate on + */ + void operator()( const tbb::blocked_range& r ) const + { + bool sorted = get_param(params_,"sorted",true); + bool use_heap = get_param(params_,"use_heap",false); + + if (use_heap) + { + KNNResultSet2 resultSet(knn_); + for (size_t i=r.begin(); i!=r.end(); ++i) + { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries_[i], params_); + resultSet.copy(indices_[i], distances_[i], knn_, sorted); + count_ += resultSet.size(); + } + } + else + { + KNNSimpleResultSet resultSet(knn_); + for (size_t i=r.begin(); i!=r.end(); ++i) + { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries_[i], params_); + resultSet.copy(indices_[i], distances_[i], knn_, sorted); + count_ += resultSet.size(); + } + } + } + +private: + //! All query points to perform search on + //! \note each worker thread only operates on a specified range + const Matrix& queries_; + + //! Matrix for storing the indices of the nearest neighbors + //! \note no need for this to be a parallel container, each worker thread + //! solely operates on its specified range! + Matrix& indices_; + + //! Matrix for storing the distances to the nearest neighbors + //! \note no need for this to be a parallel container, each worker thread + //! solely operates on its specified range! + Matrix& distances_; + + //! Number of nearest neighbors to search for + size_t knn_; + + //! The search parameters to take into account + const SearchParams& params_; + + //! The nearest neighbor index to perform the search with + NNIndex* nnIndex_; + + //! Atomic count variable to keep track of the number of neighbors found + //! \note must be mutable because body will be casted as const in parallel_for + mutable tbb::atomic& count_; +}; + + +template +class parallel_knnSearch2 +{ +public: + typedef typename Distance::ElementType ElementType; + typedef typename Distance::ResultType DistanceType; + + parallel_knnSearch2(const Matrix& queries, + std::vector< std::vector >& indices, + std::vector >& distances, + size_t knn, + const SearchParams& params, + NNIndex* nnIndex, + tbb::atomic& count) + : queries_(queries), + indices_(indices), + distances_(distances), + knn_(knn), + params_(params), + nnIndex_(nnIndex), + count_(count) + + {} + + /* default destructor will do */ + + /* default copy constructor will do, + parallel for will use this to create a separate parallel_knnSearch object + for each worker thread (pointers will be copied, which is OK) */ + + /** + * Perform knnSearch for the query points assigned to this worker thread + * (specified by the blocked_range parameter) + */ + void operator()( const tbb::blocked_range& r ) const + { + bool sorted = get_param(params_,"sorted",true); + bool use_heap = get_param(params_,"use_heap",false); + + if (use_heap) { + KNNResultSet2 resultSet(knn_); + for (size_t i=r.begin(); i!=r.end(); ++i) + { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries_[i], params_); + size_t n = std::min(resultSet.size(), knn_); + indices_[i].resize(n); + distances_[i].resize(n); + resultSet.copy(&indices_[i][0], &distances_[i][0], n, sorted); + count_ += n; + } + } + else { + KNNSimpleResultSet resultSet(knn_); + for (size_t i=r.begin(); i!=r.end(); ++i) + { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries_[i], params_); + size_t n = std::min(resultSet.size(), knn_); + indices_[i].resize(n); + distances_[i].resize(n); + resultSet.copy(&indices_[i][0], &distances_[i][0], n, sorted); + count_ += n; + } + } + } + +private: + //! All query points to perform search on + //! \note each worker thread only operates on a specified range + const Matrix& queries_; + + //! Vector for storing the indices of the nearest neighbors + //! \note no need for this to be a parallel container, each worker thread + //! solely operates on its specified range! + std::vector< std::vector >& indices_; + + //! Vector for storing the distances to the nearest neighbors + //! \note no need for this to be a parallel container, each worker thread + //! solely operates on its specified range! + std::vector< std::vector >& distances_; + + //! Number of nearest neighbors to search for + size_t knn_; + + //! The search parameters to take into account + const SearchParams& params_; + + //! The nearest neighbor index to perform the search with + NNIndex* nnIndex_; + + //! Atomic count variable to keep track of the number of neighbors found + //! \note must be mutable because body will be casted as const in parallel_for + mutable tbb::atomic& count_; +}; + + +template +class parallel_radiusSearch +{ +public: + typedef typename Distance::ElementType ElementType; + typedef typename Distance::ResultType DistanceType; + + /* default destructor will do */ + + /* default copy constructor will do, + parallel for will use this to create a separate parallel_knnSearch object + for each worker thread (pointers will be copied, which is OK) */ + + /** + * Perform radiusSearch for the query points assigned to this worker thread + * (specified by the blocked_range parameter) + */ + parallel_radiusSearch(const Matrix& queries, + Matrix& indices, + Matrix& distances, + float radius, + const SearchParams& params, + NNIndex* nnIndex, + tbb::atomic& count) + : queries_(queries), + indices_(indices), + distances_(distances), + radius_(radius), + params_(params), + nnIndex_(nnIndex), + count_(count) + + {} + + void operator()( const tbb::blocked_range& r ) const + { + int max_neighbors = get_param(params_, "max_neighbors", -1); + + if (max_neighbors==0) { + CountRadiusResultSet resultSet(radius_); + for (size_t i=r.begin(); i!=r.end(); ++i) + { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries_[i], params_); + count_ += resultSet.size(); + } + } + else { + size_t num_neighbors = std::min(indices_.cols, distances_.cols); + bool sorted = get_param(params_, "sorted", true); + bool has_max_neighbors = has_param(params_,"max_neighbors"); + + // explicitly indicated to use unbounded radius result set + // or we know there'll be enough room for resulting indices and dists + if (max_neighbors<0 && (has_max_neighbors || num_neighbors>=nnIndex_->size())) { + RadiusResultSet resultSet(radius_); + for (size_t i=r.begin(); i!=r.end(); ++i) + { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries_[i], params_); + size_t n = resultSet.size(); + count_ += n; + if (n>num_neighbors) n = num_neighbors; + resultSet.copy(indices_[i], distances_[i], n, sorted); + + // mark the next element in the output buffers as unused + if (n::infinity(); + } + } + else { + if (max_neighbors<0) max_neighbors = num_neighbors; + else max_neighbors = std::min(max_neighbors,(int)num_neighbors); + // number of neighbors limited to max_neighbors + KNNRadiusResultSet resultSet(radius_, max_neighbors); + for (size_t i=r.begin(); i!=r.end(); ++i) + { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries_[i], params_); + size_t n = resultSet.size(); + count_ += n ; + if ((int)n>max_neighbors) n = max_neighbors; + resultSet.copy(indices_[i], distances_[i], n, sorted); + + // mark the next element in the output buffers as unused + if (n::infinity(); + } + } + } + } + +private: + //! All query points to perform search on + //! \note each worker thread only operates on a specified range + const Matrix& queries_; + + //! Matrix for storing the indices of the nearest neighbors + //! \note no need for this to be a parallel container, each worker thread + //! solely operates on its specified range! + Matrix& indices_; + + //! Matrix for storing the distances to the nearest neighbors + //! \note no need for this to be a parallel container, each worker thread + //! solely operates on its specified range! + Matrix& distances_; + + //! Radius size bound on the search for nearest neighbors + float radius_; + + //! The search parameters to take into account + const SearchParams& params_; + + //! The nearest neighbor index to perform the search with + NNIndex* nnIndex_; + + //! Atomic count variable to keep track of the number of neighbors found + //! \note must be mutable because body will be casted as const in parallel_for + mutable tbb::atomic& count_; +}; + + +template +class parallel_radiusSearch2 +{ +public: + typedef typename Distance::ElementType ElementType; + typedef typename Distance::ResultType DistanceType; + + /* default destructor will do */ + + /* default copy constructor will do, + parallel for will use this to create a separate parallel_knnSearch object + for each worker thread (pointers will be copied, which is OK) */ + + /** + * Perform radiusSearch for the query points assigned to this worker thread + * (specified by the blocked_range parameter) + */ + parallel_radiusSearch2(const Matrix& queries, + std::vector< std::vector >& indices, + std::vector >& distances, + float radius, + const SearchParams& params, + NNIndex* nnIndex, + tbb::atomic& count) + : queries_(queries), + indices_(indices), + distances_(distances), + radius_(radius), + params_(params), + nnIndex_(nnIndex), + count_(count) + + {} + + void operator()( const tbb::blocked_range& r ) const + { + int max_neighbors = get_param(params_, "max_neighbors", -1); + + // just count neighbors + if (max_neighbors==0) { + CountRadiusResultSet resultSet(radius_); + for (size_t i=r.begin(); i!=r.end(); ++i) + { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries_[i], params_); + count_ += resultSet.size(); + } + } + else { + bool sorted = get_param(params_, "sorted", true); + if (indices_.size() < queries_.rows ) indices_.resize(queries_.rows); + if (distances_.size() < queries_.rows ) distances_.resize(queries_.rows); + + if (max_neighbors<0) { + // search for all neighbors + RadiusResultSet resultSet(radius_); + for (size_t i=r.begin(); i!=r.end(); ++i) + { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries_[i], params_); + size_t n = resultSet.size(); + count_ += n; + indices_[i].resize(n); + distances_[i].resize(n); + resultSet.copy(&indices_[i][0], &distances_[i][0], n, sorted); + } + } + else { + // number of neighbors limited to max_neighbors + KNNRadiusResultSet resultSet(radius_, max_neighbors); + for (size_t i=r.begin(); i!=r.end(); ++i) + { + resultSet.clear(); + nnIndex_->findNeighbors(resultSet, queries_[i], params_); + size_t n = resultSet.size(); + count_ += n; + if ((int)n>max_neighbors) n = max_neighbors; + indices_[i].resize(n); + distances_[i].resize(n); + resultSet.copy(&indices_[i][0], &distances_[i][0], n, sorted); + } + } + } + } + +private: + //! All query points to perform search on + //! \note each worker thread only operates on a specified range + const Matrix& queries_; + + //! Vector for storing the indices of the nearest neighbors + //! \note no need for this to be a parallel container, each worker thread + //! solely operates on its specified range! + std::vector< std::vector >& indices_; + + //! Vector for storing the distances to the nearest neighbors + //! \note no need for this to be a parallel container, each worker thread + //! solely operates on its specified range! + std::vector< std::vector >& distances_; + + //! Radius size bound on the search for nearest neighbors + float radius_; + + //! The search parameters to take into account + const SearchParams& params_; + + //! The nearest neighbor index to perform the search with + NNIndex* nnIndex_; + + //! Atomic count variable to keep track of the number of neighbors found + //! \note must be mutable because body will be casted as const in parallel_for + mutable tbb::atomic& count_; +}; + +} + +#endif //FLANN_TBB_BODIES_H diff --git a/src/cpp/flann/util/params.h b/src/cpp/flann/util/params.h index 67aa2d2ff72a58b404e7f2e3c3e0508d5ee5f8dd..7fc7967a793e32d51eac7b4731148f48caf0cd9b 100644 --- a/src/cpp/flann/util/params.h +++ b/src/cpp/flann/util/params.h @@ -44,7 +44,8 @@ typedef std::map IndexParams; struct SearchParams : public IndexParams { - SearchParams(int checks = 32, float eps = 0, bool sorted = true ) + + SearchParams(int checks = 32, float eps = 0, bool sorted = true, int cores = 1 ) { // how many leafs to visit when searching for neighbours (-1 for unlimited) (*this)["checks"] = checks; @@ -52,6 +53,9 @@ struct SearchParams : public IndexParams (*this)["eps"] = eps; // only for radius search, require neighbours sorted by distance (default: true) (*this)["sorted"] = sorted; + // how many cores to assign to the search + // this parameter will be ignored if Intel TBB isn't available on the system or no "TBB" macro is defined + (*this)["cores"] = cores; } }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b849c265e228013d5622d52cbac95c2332079f69..2cd5016637530998838223aaacbbf98ba57de392 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -21,9 +21,15 @@ endif() if (GTEST_FOUND AND HDF5_FOUND) include_directories(${HDF5_INCLUDE_DIR}) flann_add_gtest(flann_simple_test flann_simple_test.cpp) + if(TBB_FOUND) + add_definitions(-DTBB) + flann_add_gtest(flann_multithreaded_test flann_multithreaded_test.cpp) + endif() target_link_libraries(flann_simple_test flann_cpp ${HDF5_LIBRARIES}) + target_link_libraries(flann_multithreaded_test flann_cpp ${HDF5_LIBRARIES}) if (HDF5_IS_PARALLEL) target_link_libraries(flann_simple_test ${MPI_LIBRARIES}) + target_link_libraries(flann_multithreaded_test ${MPI_LIBRARIES}) endif() endif() diff --git a/test/flann_multithreaded_test.cpp b/test/flann_multithreaded_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e8b824931b2a43f392b50fadf147e6f08b37f6e8 --- /dev/null +++ b/test/flann_multithreaded_test.cpp @@ -0,0 +1,343 @@ +#include +#include + +#include +#include +#include + +using namespace flann; + +float compute_precision(const flann::Matrix& match, const flann::Matrix& indices) +{ + int count = 0; + + assert(match.rows == indices.rows); + size_t nn = std::min(match.cols, indices.cols); + + for (size_t i=0; i data; + flann::Matrix query; + flann::Matrix match; + flann::Matrix dists; + flann::Matrix indices; + + int nn; + + void SetUp() + { + nn = 5; + + printf("Reading test data..."); + fflush(stdout); + flann::load_from_file(data, "cloud.h5","dataset"); + flann::load_from_file(query,"cloud.h5","query"); + flann::load_from_file(match,"cloud.h5","match"); + + dists = flann::Matrix(new float[query.rows*nn], query.rows, nn); + indices = flann::Matrix(new int[query.rows*nn], query.rows, nn); + + printf("done\n"); + } + + void TearDown() + { + delete[] data.data; + delete[] query.data; + delete[] match.data; + delete[] dists.data; + delete[] indices.data; + } + + int GetNN() { return nn; } +}; + +TEST_F(FlannTest, HandlesSingleCoreSearch) +{ + flann::Index > index(data, flann::KDTreeSingleIndexParams(50, false)); + start_timer("Building kd-tree index..."); + index.buildIndex(); + printf("done (%g seconds)\n", stop_timer()); + + int checks = -1; + float eps = 0.0f; + bool sorted = true; + int cores = 1; + + start_timer("Searching KNN..."); + index.knnSearch(query, indices, dists, GetNN(), flann::SearchParams(checks,eps,sorted,cores)); + printf("done (%g seconds)\n", stop_timer()); + + float precision = compute_precision(match, indices); + EXPECT_GE(precision, 0.99); + printf("Precision: %g\n", precision); +} + +TEST_F(FlannTest, HandlesMultiCoreSearch) +{ + flann::Index > index(data, flann::KDTreeSingleIndexParams(50, false)); + start_timer("Building kd-tree index..."); + index.buildIndex(); + printf("done (%g seconds)\n", stop_timer()); + + int checks = -1; + float eps = 0.0f; + bool sorted = true; + int cores = -1; + + start_timer("Searching KNN..."); + index.knnSearch(query, indices, dists, GetNN(), flann::SearchParams(checks,eps,sorted,cores)); + printf("done (%g seconds)\n", stop_timer()); + + float precision = compute_precision(match, indices); + EXPECT_GE(precision, 0.99); + printf("Precision: %g\n", precision); +} + + +/* Test Fixture which loads the cloud.h5 cloud as data and query matrix and holds two dists + and indices matrices for comparing single and multi core KNN search */ +class FlannCompareKnnTest : public FLANNTestFixture { +protected: + flann::Matrix data; + flann::Matrix query; + flann::Matrix dists_single; + flann::Matrix indices_single; + flann::Matrix dists_multi; + flann::Matrix indices_multi; + + int nn; + + void SetUp() + { + nn = 5; + + printf("Reading test data..."); + fflush(stdout); + flann::load_from_file(data, "cloud.h5","dataset"); + flann::load_from_file(query,"cloud.h5","query"); + + dists_single = flann::Matrix(new float[query.rows*nn], query.rows, nn); + indices_single = flann::Matrix(new int[query.rows*nn], query.rows, nn); + dists_multi = flann::Matrix(new float[query.rows*nn], query.rows, nn); + indices_multi = flann::Matrix(new int[query.rows*nn], query.rows, nn); + + printf("done\n"); + } + + void TearDown() + { + delete[] data.data; + delete[] query.data; + delete[] dists_single.data; + delete[] indices_single.data; + delete[] dists_multi.data; + delete[] indices_multi.data; + } + + int GetNN() { return nn; } +}; + +TEST_F(FlannCompareKnnTest, CompareMultiSingleCoreKnnSearchSorted) +{ + flann::Index > index(data, flann::KDTreeSingleIndexParams(50, false)); + start_timer("Building kd-tree index..."); + index.buildIndex(); + printf("done (%g seconds)\n", stop_timer()); + + int checks = -1; + float eps = 0.0f; + bool sorted = true; + int single_core = 1; + int multi_core = -1; + + start_timer("Searching KNN (single core)..."); + int single_neighbor_count = index.knnSearch(query, indices_single, dists_single, GetNN(), flann::SearchParams(checks,eps,sorted,single_core)); + printf("done (%g seconds)\n", stop_timer()); + + start_timer("Searching KNN (multi core)..."); + int multi_neighbor_count = index.knnSearch(query, indices_multi, dists_multi, GetNN(), flann::SearchParams(checks,eps,sorted,multi_core)); + printf("done (%g seconds)\n", stop_timer()); + + EXPECT_EQ(single_neighbor_count, multi_neighbor_count); + + float precision = compute_precision(indices_single, indices_multi); + EXPECT_GE(precision, 0.99); + printf("Precision: %g\n", precision); +} + +TEST_F(FlannCompareKnnTest, CompareMultiSingleCoreKnnSearchUnsorted) +{ + flann::Index > index(data, flann::KDTreeSingleIndexParams(50, false)); + start_timer("Building kd-tree index..."); + index.buildIndex(); + printf("done (%g seconds)\n", stop_timer()); + + int checks = -1; + float eps = 0.0f; + bool sorted = false; + int single_core = 1; + int multi_core = -1; + + start_timer("Searching KNN (single core)..."); + int single_neighbor_count = index.knnSearch(query, indices_single, dists_single, GetNN(), flann::SearchParams(checks,eps,sorted,single_core)); + printf("done (%g seconds)\n", stop_timer()); + + start_timer("Searching KNN (multi core)..."); + int multi_neighbor_count = index.knnSearch(query, indices_multi, dists_multi, GetNN(), flann::SearchParams(checks,eps,sorted,multi_core)); + printf("done (%g seconds)\n", stop_timer()); + + EXPECT_EQ(single_neighbor_count, multi_neighbor_count); + + float precision = compute_precision(indices_single, indices_multi); + EXPECT_GE(precision, 0.99); + printf("Precision: %g\n", precision); +} + + +/* Test Fixture which loads the cloud.h5 cloud as data and query matrix and holds two dists + and indices matrices for comparing single and multi core radius search */ +class FlannCompareRadiusTest : public FLANNTestFixture { +protected: + flann::Matrix data; + flann::Matrix query; + flann::Matrix dists_single; + flann::Matrix indices_single; + flann::Matrix dists_multi; + flann::Matrix indices_multi; + + float radius; + + void SetUp() + { + radius = 0.1f; + + printf("Reading test data..."); + fflush(stdout); + flann::load_from_file(data, "cloud.h5","dataset"); + flann::load_from_file(query,"cloud.h5","query"); + + // If the indices / dists matrix cannot contain all points found in the radius, only the points + // that can be stored in the matrix will be returned and search is stopped. For each query point + // we reserve as many space as we think is needed. For large point clouds, reserving 'cloudsize' + // space for each query point might cause memory errors. + int reserve_size = data.rows / 1000; + + dists_single = flann::Matrix(new float[query.rows*reserve_size], query.rows, reserve_size); + indices_single = flann::Matrix(new int[query.rows*reserve_size], query.rows, reserve_size); + dists_multi = flann::Matrix(new float[query.rows*reserve_size], query.rows, reserve_size); + indices_multi = flann::Matrix(new int[query.rows*reserve_size], query.rows, reserve_size); + + printf("done\n"); + } + + void TearDown() + { + delete[] data.data; + delete[] query.data; + delete[] dists_single.data; + delete[] indices_single.data; + delete[] dists_multi.data; + delete[] indices_multi.data; + } + + float GetRadius() { return radius; } +}; + +TEST_F(FlannCompareRadiusTest, CompareMultiSingleCoreRadiusSearchSorted) +{ + flann::Index > index(data, flann::KDTreeSingleIndexParams(50, false)); + start_timer("Building kd-tree index..."); + index.buildIndex(); + printf("done (%g seconds)\n", stop_timer()); + + int checks = -1; + float eps = 0.0f; + bool sorted = true; + int single_core = 1; + int multi_core = -1; + + start_timer("Searching Radius (single core)..."); + int single_neighbor_count = index.radiusSearch(query, indices_single, dists_single, GetRadius(), flann::SearchParams(checks,eps,sorted,single_core)); + printf("done (%g seconds)\n", stop_timer()); + + start_timer("Searching Radius (multi core)..."); + int multi_neighbor_count = index.radiusSearch(query, indices_multi, dists_multi, GetRadius(), flann::SearchParams(checks,eps,sorted,multi_core)); + printf("done (%g seconds)\n", stop_timer()); + + EXPECT_EQ(single_neighbor_count, multi_neighbor_count); + + float precision = compute_precision(indices_single, indices_multi); + EXPECT_GE(precision, 0.99); + printf("Precision: %g\n", precision); +} + +TEST_F(FlannCompareRadiusTest, CompareMultiSingleCoreRadiusSearchUnsorted) +{ + flann::Index > index(data, flann::KDTreeSingleIndexParams(50, false)); + start_timer("Building kd-tree index..."); + index.buildIndex(); + printf("done (%g seconds)\n", stop_timer()); + + int checks = -1; + float eps = 0.0f; + bool sorted = false; + int single_core = 1; + int multi_core = -1; + + start_timer("Searching Radius (single core)..."); + int single_neighbor_count = index.radiusSearch(query, indices_single, dists_single, GetRadius(), flann::SearchParams(checks,eps,sorted,single_core)); + printf("done (%g seconds)\n", stop_timer()); + + start_timer("Searching Radius (multi core)..."); + int multi_neighbor_count = index.radiusSearch(query, indices_multi, dists_multi, GetRadius(), flann::SearchParams(checks,eps,sorted,multi_core)); + printf("done (%g seconds)\n", stop_timer()); + + EXPECT_EQ(single_neighbor_count, multi_neighbor_count); + + float precision = compute_precision(indices_single, indices_multi); + EXPECT_GE(precision, 0.99); + printf("Precision: %g\n", precision); +} + + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}