|
7 | 7 | #include <cstddef> |
8 | 8 | #include <cstring> |
9 | 9 | #include <stdexcept> |
| 10 | +#include <limits> |
10 | 11 |
|
11 | 12 | #include "knncolle/knncolle.hpp" |
12 | 13 | #include "annoy/annoylib.h" |
@@ -68,18 +69,28 @@ class AnnoySearcher final : public knncolle::Searcher<Index_, Data_, Distance_> |
68 | 69 | static constexpr bool same_internal_distance = std::is_same<Distance_, AnnoyData_>::value; |
69 | 70 | typename std::conditional<!same_internal_distance, std::vector<AnnoyData_>, bool>::type my_distances; |
70 | 71 |
|
71 | | - int get_search_k(int k) const { |
| 72 | + typedef int SearchKType; |
| 73 | + |
| 74 | + Index_ my_capped_k = 0; |
| 75 | + |
| 76 | + SearchKType get_search_k(Index_ k) const { |
72 | 77 | if (my_parent.my_search_mult < 1) { |
73 | 78 | return -1; // instructs Annoy to use k * num_trees. |
74 | | - } else { |
| 79 | + } else if (k <= my_capped_k) { |
75 | 80 | return my_parent.my_search_mult * static_cast<double>(k) + 0.5; // rounded. |
| 81 | + } else { |
| 82 | + return std::numeric_limits<SearchKType>::max(); // cap to avoid overflow. |
76 | 83 | } |
77 | 84 | } |
78 | 85 |
|
79 | 86 | public: |
80 | 87 | AnnoySearcher(const AnnoyPrebuilt<Index_, Data_, Distance_, AnnoyDistance_, AnnoyIndex_, AnnoyData_, AnnoyRng_, AnnoyThreadPolicy_>& parent) : my_parent(parent) { |
81 | 88 | if constexpr(!same_internal_data) { |
82 | | - my_buffer.resize(my_parent.my_dim); |
| 89 | + sanisizer::resize(my_buffer, my_parent.my_dim); |
| 90 | + } |
| 91 | + |
| 92 | + if (my_parent.my_search_mult >= 1) { |
| 93 | + my_capped_k = static_cast<double>(std::numeric_limits<SearchKType>::max()) / my_parent.my_search_mult; |
83 | 94 | } |
84 | 95 | } |
85 | 96 |
|
@@ -139,18 +150,26 @@ class AnnoySearcher final : public knncolle::Searcher<Index_, Data_, Distance_> |
139 | 150 |
|
140 | 151 | public: |
141 | 152 | void search(Index_ i, Index_ k, std::vector<Index_>* output_indices, std::vector<Distance_>* output_distances) { |
142 | | - Index_ kp1 = k + 1; // +1, as it forgets to discard 'self'. |
| 153 | + // +1, as it forgets to discard 'self'. This should not overflow as k < num_obs. |
| 154 | + const auto kp1 = k + 1; |
| 155 | + |
143 | 156 | auto ptrs = obtain_pointers(output_indices, output_distances, kp1); |
144 | 157 | auto icopy_ptr = ptrs.first; |
145 | 158 | auto dcopy_ptr = ptrs.second; |
146 | 159 |
|
147 | | - my_parent.my_index.get_nns_by_item(i, kp1, get_search_k(kp1), icopy_ptr, dcopy_ptr); |
| 160 | + my_parent.my_index.get_nns_by_item( |
| 161 | + i, |
| 162 | + sanisizer::cast<std::size_t>(sanisizer::attest_gez(kp1)), |
| 163 | + get_search_k(kp1), |
| 164 | + icopy_ptr, |
| 165 | + dcopy_ptr |
| 166 | + ); |
148 | 167 |
|
149 | 168 | std::size_t at; |
150 | 169 | { |
151 | 170 | const auto& cur_i = *icopy_ptr; |
152 | 171 | at = cur_i.size(); |
153 | | - AnnoyIndex_ icopy = i; |
| 172 | + const AnnoyIndex_ icopy = i; // cast to AnnoyIndex_ to avoid signed/unsigned comparisons. |
154 | 173 | for (std::size_t x = 0, end = cur_i.size(); x < end; ++x) { |
155 | 174 | if (cur_i[x] == icopy) { |
156 | 175 | at = x; |
@@ -182,7 +201,13 @@ class AnnoySearcher final : public knncolle::Searcher<Index_, Data_, Distance_> |
182 | 201 | auto icopy_ptr = ptrs.first; |
183 | 202 | auto dcopy_ptr = ptrs.second; |
184 | 203 |
|
185 | | - my_parent.my_index.get_nns_by_vector(query, k, get_search_k(k), icopy_ptr, dcopy_ptr); |
| 204 | + my_parent.my_index.get_nns_by_vector( |
| 205 | + query, |
| 206 | + sanisizer::cast<std::size_t>(sanisizer::attest_gez(k)), |
| 207 | + get_search_k(k), |
| 208 | + icopy_ptr, |
| 209 | + dcopy_ptr |
| 210 | + ); |
186 | 211 |
|
187 | 212 | if (output_indices) { |
188 | 213 | if constexpr(!same_internal_index) { |
@@ -229,14 +254,17 @@ class AnnoyPrebuilt final : public knncolle::Prebuilt<Index_, Data_, Distance_> |
229 | 254 | my_search_mult(options.search_mult), |
230 | 255 | my_index(my_dim) |
231 | 256 | { |
| 257 | + // check that we can, in fact, represent the number of observations in the specified AnnoyIndex_ type. |
| 258 | + sanisizer::cast<AnnoyIndex_>(sanisizer::attest_gez(my_obs)); |
| 259 | + |
232 | 260 | auto work = data.new_known_extractor(); |
233 | 261 | if constexpr(std::is_same<Data_, AnnoyData_>::value) { |
234 | 262 | for (Index_ i = 0; i < my_obs; ++i) { |
235 | 263 | auto ptr = work->next(); |
236 | 264 | my_index.add_item(i, ptr); |
237 | 265 | } |
238 | 266 | } else { |
239 | | - std::vector<AnnoyData_> incoming(my_dim); |
| 267 | + auto incoming = sanisizer::create<std::vector<AnnoyData_> >(my_dim); |
240 | 268 | for (Index_ i = 0; i < my_obs; ++i) { |
241 | 269 | auto ptr = work->next(); |
242 | 270 | std::copy_n(ptr, my_dim, incoming.begin()); |
@@ -310,8 +338,11 @@ class AnnoyPrebuilt final : public knncolle::Prebuilt<Index_, Data_, Distance_> |
310 | 338 |
|
311 | 339 | // Not bothering to save anything else; the RNG and thread policy |
312 | 340 | // should not be relevant once the index is built. |
| 341 | + |
| 342 | + // For reasons unknown to us, AnnoyIndex::save() is not const, so we have to do it manually. |
| 343 | + // Hopefully this will be fixed in the future. |
313 | 344 | const auto idxpath = prefix + "index"; |
314 | | - knncolle::quick_save(idxpath, reinterpret_cast<char*>(my_index.get_nodes()), static_cast<std::size_t>(my_index.get_s()) * my_index.get_n_nodes()); |
| 345 | + knncolle::quick_save(idxpath, reinterpret_cast<char*>(my_index.get_nodes()), sanisizer::product<std::streamsize>(my_index.get_s(), my_index.get_n_nodes())); |
315 | 346 | } |
316 | 347 |
|
317 | 348 | AnnoyPrebuilt(const std::string prefix, std::size_t ndim) : my_dim(ndim), my_index(ndim) { |
|
0 commit comments