Skip to content

Commit 137581c

Browse files
committed
Add a mode of error handling without exceptions and ctest support
Adding a mode of error handling that does not rely on C++ exceptions, with Abseil-like Status and StatusOr return types (but not adding a dependency on Abseil). The old exceptions-based API is still available, and the new functions are suffixed with "NoExceptions". Refactoring the CMake build system. Organizing examples and tests and reducing repetition. Adding the HNSWLIB_ENABLE_EXCEPTIONS option (ON by default). Also adding ctest support.
1 parent 39fd33b commit 137581c

15 files changed

+403
-170
lines changed

CMakeLists.txt

Lines changed: 93 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,56 @@ project(hnswlib
66
include(GNUInstallDirs)
77
include(CheckCXXCompilerFlag)
88

9+
# These example/test targets catch exceptions, so exceptions should always be
10+
# enabled building these files even if they are disabled in other targets.
11+
# We check that each target included in this list is a real target.
12+
set(HNSWLIB_TARGETS_REQUIRING_EXCEPTIONS
13+
example_mt_filter
14+
example_mt_replace_deleted
15+
example_mt_search
16+
multiThread_replace_test
17+
test_updates)
18+
19+
# Adds an example or test target. The target name parameter is followed by
20+
# the list of source files. Automatically links with the hnswlib library.
21+
# Also decides whether to enable exceptions when building the target.
22+
# If HNSWLIB_ENABLE_EXCEPTIONS is ON, exceptions are always enabled.
23+
# If HNSWLIB_ENABLE_EXCEPTIONS is OFF, exceptions are only enabled for the
24+
# specific targets listed in HNSWLIB_TARGETS_REQUIRING_EXCEPTIONS.
25+
function(add_example_or_test TARGET_NAME ...)
26+
add_executable(${ARGV})
27+
target_link_libraries(${TARGET_NAME} hnswlib)
28+
list(FIND HNSWLIB_TARGETS_REQUIRING_EXCEPTIONS "${TARGET_NAME}" found_at_index)
29+
if(found_at_index GREATER -1)
30+
if(NOT HNSWLIB_ENABLE_EXCEPTIONS)
31+
message("Enabling exceptions for target ${TARGET_NAME} as a special case")
32+
endif()
33+
set(should_enable_exceptions ON)
34+
else()
35+
set(should_enable_exceptions "${HNSWLIB_ENABLE_EXCEPTIONS}")
36+
endif()
37+
if(should_enable_exceptions)
38+
target_compile_options("${TARGET_NAME}" PUBLIC -fexceptions)
39+
else()
40+
target_compile_options("${TARGET_NAME}" PUBLIC -fno-exceptions)
41+
endif()
42+
if(NOT ${TARGET_NAME} MATCHES "^(main|test_updates)$")
43+
add_test(
44+
NAME ${TARGET_NAME}
45+
COMMAND ${TARGET_NAME}
46+
)
47+
endif()
48+
endfunction()
49+
50+
option(HNSWLIB_ENABLE_EXCEPTIONS "Whether to enable exceptions in hnswlib" ON)
51+
if(HNSWLIB_ENABLE_EXCEPTIONS)
52+
message("Exceptions are enabled using HNSWLIB_ENABLE_EXCEPTIONS=ON (default)")
53+
else()
54+
message("Exceptions are disabled using HNSWLIB_ENABLE_EXCEPTIONS=OFF")
55+
endif()
56+
57+
set(CMAKE_CXX_STANDARD 11)
58+
959
add_library(hnswlib INTERFACE)
1060
add_library(hnswlib::hnswlib ALIAS hnswlib)
1161

@@ -28,14 +78,14 @@ install(EXPORT hnswlibTargets
2878
# Examples and tests
2979
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
3080
option(HNSWLIB_EXAMPLES "Build examples and tests." ON)
81+
message("Building examples and tests")
3182
else()
3283
option(HNSWLIB_EXAMPLES "Build examples and tests." OFF)
3384
endif()
3485
if(HNSWLIB_EXAMPLES)
35-
set(CMAKE_CXX_STANDARD 11)
36-
86+
enable_testing()
3787
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
38-
SET( CMAKE_CXX_FLAGS "-Ofast -std=c++11 -DHAVE_CXX0X -openmp -fpic -ftree-vectorize" )
88+
SET( CMAKE_CXX_FLAGS "-Ofast -DHAVE_CXX0X -openmp -fpic -ftree-vectorize" )
3989
check_cxx_compiler_flag("-march=native" COMPILER_SUPPORT_NATIVE_FLAG)
4090
if(COMPILER_SUPPORT_NATIVE_FLAG)
4191
SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native" )
@@ -48,58 +98,48 @@ if(HNSWLIB_EXAMPLES)
4898
endif()
4999
endif()
50100
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
51-
SET( CMAKE_CXX_FLAGS "-Ofast -lrt -std=c++11 -DHAVE_CXX0X -march=native -fpic -w -fopenmp -ftree-vectorize -ftree-vectorizer-verbose=0" )
101+
SET( CMAKE_CXX_FLAGS "-Ofast -lrt -DHAVE_CXX0X -march=native -fpic -w -fopenmp -ftree-vectorize -ftree-vectorizer-verbose=0" )
52102
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
53103
SET( CMAKE_CXX_FLAGS "/O2 -DHAVE_CXX0X /W1 /openmp /EHsc" )
54104
endif()
55105

56-
# examples
57-
add_executable(example_search examples/cpp/example_search.cpp)
58-
target_link_libraries(example_search hnswlib)
59-
60-
add_executable(example_epsilon_search examples/cpp/example_epsilon_search.cpp)
61-
target_link_libraries(example_epsilon_search hnswlib)
62-
63-
add_executable(example_multivector_search examples/cpp/example_multivector_search.cpp)
64-
target_link_libraries(example_multivector_search hnswlib)
65-
66-
add_executable(example_filter examples/cpp/example_filter.cpp)
67-
target_link_libraries(example_filter hnswlib)
68-
69-
add_executable(example_replace_deleted examples/cpp/example_replace_deleted.cpp)
70-
target_link_libraries(example_replace_deleted hnswlib)
71-
72-
add_executable(example_mt_search examples/cpp/example_mt_search.cpp)
73-
target_link_libraries(example_mt_search hnswlib)
74-
75-
add_executable(example_mt_filter examples/cpp/example_mt_filter.cpp)
76-
target_link_libraries(example_mt_filter hnswlib)
77-
78-
add_executable(example_mt_replace_deleted examples/cpp/example_mt_replace_deleted.cpp)
79-
target_link_libraries(example_mt_replace_deleted hnswlib)
80-
81-
# tests
82-
add_executable(multivector_search_test tests/cpp/multivector_search_test.cpp)
83-
target_link_libraries(multivector_search_test hnswlib)
84-
85-
add_executable(epsilon_search_test tests/cpp/epsilon_search_test.cpp)
86-
target_link_libraries(epsilon_search_test hnswlib)
87-
88-
add_executable(test_updates tests/cpp/updates_test.cpp)
89-
target_link_libraries(test_updates hnswlib)
90-
91-
add_executable(searchKnnCloserFirst_test tests/cpp/searchKnnCloserFirst_test.cpp)
92-
target_link_libraries(searchKnnCloserFirst_test hnswlib)
93-
94-
add_executable(searchKnnWithFilter_test tests/cpp/searchKnnWithFilter_test.cpp)
95-
target_link_libraries(searchKnnWithFilter_test hnswlib)
96-
97-
add_executable(multiThreadLoad_test tests/cpp/multiThreadLoad_test.cpp)
98-
target_link_libraries(multiThreadLoad_test hnswlib)
99-
100-
add_executable(multiThread_replace_test tests/cpp/multiThread_replace_test.cpp)
101-
target_link_libraries(multiThread_replace_test hnswlib)
102-
103-
add_executable(main tests/cpp/main.cpp tests/cpp/sift_1b.cpp)
104-
target_link_libraries(main hnswlib)
106+
set(EXAMPLE_NAMES
107+
example_epsilon_search
108+
example_filter
109+
example_mt_filter
110+
example_mt_replace_deleted
111+
example_mt_search
112+
example_multivector_search
113+
example_replace_deleted
114+
example_search)
115+
116+
foreach(example_name IN LISTS EXAMPLE_NAMES)
117+
add_example_or_test("${example_name}" "examples/cpp/${example_name}.cpp")
118+
endforeach()
119+
120+
set(TEST_NAMES
121+
multivector_search_test
122+
epsilon_search_test
123+
searchKnnCloserFirst_test
124+
searchKnnWithFilter_test
125+
multiThreadLoad_test
126+
multiThread_replace_test)
127+
foreach(test_name IN LISTS TEST_NAMES)
128+
add_example_or_test("${test_name}" "tests/cpp/${test_name}.cpp")
129+
endforeach()
130+
131+
# This test deviates from the above pattern of naming test executables.
132+
add_example_or_test(test_updates tests/cpp/updates_test.cpp)
133+
134+
# For historical reasons, the "main" program links with sift_1b.cpp.
135+
add_example_or_test(main tests/cpp/main.cpp tests/cpp/sift_1b.cpp)
136+
137+
foreach(target_name IN LISTS HNSWLIB_TARGETS_REQUIRING_EXCEPTIONS)
138+
if(NOT TARGET ${target_name})
139+
message(FATAL_ERROR
140+
"Target '${target_name}' included in "
141+
"HNSWLIB_TARGETS_REQUIRING_EXCEPTIONS does not exist. "
142+
"Please check if this is a typo.")
143+
endif()
144+
endforeach()
105145
endif()

examples/cpp/example_epsilon_search.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ int main() {
5353
}
5454
std::cout << "Query #" << i << "\n";
5555
hnswlib::EpsilonSearchStopCondition<dist_t> stop_condition(epsilon2, min_num_candidates, max_elements);
56-
std::vector<std::pair<float, hnswlib::labeltype>> result =
56+
std::vector<std::pair<float, hnswlib::labeltype>> result =
5757
alg_hnsw->searchStopConditionClosest(query_data, stop_condition);
5858
size_t num_vectors = result.size();
5959
std::cout << "Found " << num_vectors << " vectors\n";

examples/cpp/example_mt_replace_deleted.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ int main() {
6969
int num_threads = 20; // Number of threads for operations with index
7070

7171
// Initing index with allow_replace_deleted=true
72-
int seed = 100;
72+
int seed = 100;
7373
hnswlib::L2Space space(dim);
7474
hnswlib::HierarchicalNSW<float>* alg_hnsw = new hnswlib::HierarchicalNSW<float>(&space, max_elements, M, ef_construction, seed, true);
7575

examples/cpp/example_multivector_search.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ int main() {
6363
}
6464
std::cout << "Query #" << i << "\n";
6565
hnswlib::MultiVectorSearchStopCondition<docidtype, dist_t> stop_condition(space, num_docs, ef_collection);
66-
std::vector<std::pair<float, hnswlib::labeltype>> result =
66+
std::vector<std::pair<float, hnswlib::labeltype>> result =
6767
alg_hnsw->searchStopConditionClosest(query_data, stop_condition);
6868
size_t num_vectors = result.size();
6969

examples/cpp/example_replace_deleted.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ int main() {
99
int ef_construction = 200; // Controls index search speed/build speed tradeoff
1010

1111
// Initing index with allow_replace_deleted=true
12-
int seed = 100;
12+
int seed = 100;
1313
hnswlib::L2Space space(dim);
1414
hnswlib::HierarchicalNSW<float>* alg_hnsw = new hnswlib::HierarchicalNSW<float>(&space, max_elements, M, ef_construction, seed, true);
1515

hnswlib/bruteforce.h

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#pragma once
2+
3+
#include "hnswlib.h"
4+
25
#include <unordered_map>
36
#include <fstream>
47
#include <mutex>
@@ -51,7 +54,7 @@ class BruteforceSearch : public AlgorithmInterface<dist_t> {
5154
size_per_element_ = data_size_ + sizeof(labeltype);
5255
data_ = (char *) malloc(maxElements * size_per_element_);
5356
if (data_ == nullptr)
54-
throw std::runtime_error("Not enough memory: BruteforceSearch failed to allocate data");
57+
HNSWLIB_THROW_RUNTIME_ERROR("Not enough memory: BruteforceSearch failed to allocate data");
5558
cur_element_count = 0;
5659
}
5760

@@ -61,7 +64,7 @@ class BruteforceSearch : public AlgorithmInterface<dist_t> {
6164
}
6265

6366

64-
void addPoint(const void *datapoint, labeltype label, bool replace_deleted = false) {
67+
Status addPointNoExceptions(const void *datapoint, labeltype label, bool replace_deleted = false) override {
6568
int idx;
6669
{
6770
std::unique_lock<std::mutex> lock(index_lock);
@@ -71,7 +74,7 @@ class BruteforceSearch : public AlgorithmInterface<dist_t> {
7174
idx = search->second;
7275
} else {
7376
if (cur_element_count >= maxelements_) {
74-
throw std::runtime_error("The number of elements exceeds the specified limit\n");
77+
return Status("The number of elements exceeds the specified limit");
7578
}
7679
idx = cur_element_count;
7780
dict_external_to_internal[label] = idx;
@@ -80,6 +83,7 @@ class BruteforceSearch : public AlgorithmInterface<dist_t> {
8083
}
8184
memcpy(data_ + size_per_element_ * idx + data_size_, &label, sizeof(labeltype));
8285
memcpy(data_ + size_per_element_ * idx, datapoint, data_size_);
86+
return OkStatus();
8387
}
8488

8589

@@ -103,8 +107,9 @@ class BruteforceSearch : public AlgorithmInterface<dist_t> {
103107
}
104108

105109

106-
std::priority_queue<std::pair<dist_t, labeltype >>
107-
searchKnn(const void *query_data, size_t k, BaseFilterFunctor* isIdAllowed = nullptr) const {
110+
using DistanceLabelPriorityQueue = typename AlgorithmInterface<dist_t>::DistanceLabelPriorityQueue;
111+
StatusOr<DistanceLabelPriorityQueue>
112+
searchKnnNoExceptions(const void *query_data, size_t k, BaseFilterFunctor* isIdAllowed = nullptr) const override {
108113
assert(k <= cur_element_count);
109114
std::priority_queue<std::pair<dist_t, labeltype >> topResults;
110115
dist_t lastdist = std::numeric_limits<dist_t>::max();
@@ -153,7 +158,7 @@ class BruteforceSearch : public AlgorithmInterface<dist_t> {
153158
size_per_element_ = data_size_ + sizeof(labeltype);
154159
data_ = (char *) malloc(maxelements_ * size_per_element_);
155160
if (data_ == nullptr)
156-
throw std::runtime_error("Not enough memory: loadIndex failed to allocate data");
161+
HNSWLIB_THROW_RUNTIME_ERROR("Not enough memory: loadIndex failed to allocate data");
157162

158163
input.read(data_, maxelements_ * size_per_element_);
159164

0 commit comments

Comments
 (0)