Repository navigation
Refactor no-exceptions load/save after v0.10.0-rc.2 review #683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ilyajob05
wants to merge
13
commits into
develop
Choose a base branch
from
f/rc2-review-followups
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
abd9b4b
Drop StreamExceptionsOff from *NoExceptions stream I/O
ilyajob05 812fd76
Fix load/reload invariants for the no-exceptions index APIs
ilyajob05 7a52a44
Add BruteforceSearch::loadIndexNoExceptions
ilyajob05 c21fc7d
Drop leftover StreamExceptionsOff from BF loadIndexNoExceptions
ilyajob05 c1e24d4
Own Status messages and stop CMake from stripping /EHsc
ilyajob05 348edad
Reset max_elements_ in clear() so addPoint cannot memset nullptr
ilyajob05 08f7f9f
Reject HNSW files where cur_element_count exceeds max_elements
ilyajob05 8f624c6
Keep addPoint consistent if upper-level link malloc fails
ilyajob05 d12ff46
Return Status from *NoExceptions load when allocation throws
ilyajob05 89a72b7
Document that Status error construction may allocate
ilyajob05 0c7adad
Reject Bruteforce loads whose size_per_element does not match Space
ilyajob05 eea3673
Disable MSVC EH on no-exception targets without touching the cache
ilyajob05 369bcf8
Drop loadIndex comments that narrate the old seek-to--1 bug
ilyajob05 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,13 @@ class BruteforceSearch : public AlgorithmInterface<dist_t> { | |
| free(data_); | ||
| } | ||
|
|
||
| // Labels sit at a packed offset; load via memcpy (nmslib/hnswlib#665). | ||
| inline labeltype getExternalLabel(size_t internal_id) const { | ||
| labeltype return_label; | ||
| memcpy(&return_label, data_ + internal_id * size_per_element_ + data_size_, sizeof(labeltype)); | ||
| return return_label; | ||
| } | ||
|
|
||
|
|
||
| Status addPointNoExceptions(const void *datapoint, labeltype label, bool replace_deleted = false) override { | ||
| int idx; | ||
|
|
@@ -99,7 +106,7 @@ class BruteforceSearch : public AlgorithmInterface<dist_t> { | |
| dict_external_to_internal.erase(found); | ||
|
|
||
| size_t cur_c = found->second; | ||
| labeltype label = *((labeltype*)(data_ + size_per_element_ * (cur_element_count-1) + data_size_)); | ||
| labeltype label = getExternalLabel(cur_element_count - 1); | ||
| dict_external_to_internal[label] = cur_c; | ||
| memcpy(data_ + size_per_element_ * cur_c, | ||
| data_ + size_per_element_ * (cur_element_count-1), | ||
|
|
@@ -117,7 +124,7 @@ class BruteforceSearch : public AlgorithmInterface<dist_t> { | |
| for (int i = 0; i < cur_element_count; i++) { | ||
| dist_t dist = fstdistfunc_(query_data, data_ + size_per_element_ * i, dist_func_param_); | ||
| if (dist <= lastdist || topResults.size() < k) { | ||
| labeltype label = *((labeltype *) (data_ + size_per_element_ * i + data_size_)); | ||
| labeltype label = getExternalLabel(i); | ||
| if ((!isIdAllowed) || (*isIdAllowed)(label)) { | ||
| topResults.emplace(dist, label); | ||
| if (topResults.size() > k) | ||
|
|
@@ -132,24 +139,23 @@ class BruteforceSearch : public AlgorithmInterface<dist_t> { | |
|
|
||
|
|
||
| Status saveIndexNoExceptions(std::ostream &output) { | ||
| StreamExceptionsOff guard(output); | ||
| return invokeWithoutStreamThrow([&]() -> Status { | ||
| if (!output) { | ||
| return Status("Cannot save index: output stream is not open or in a failed state"); | ||
| } | ||
| writeBinaryPOD(output, maxelements_); | ||
| writeBinaryPOD(output, size_per_element_); | ||
| writeBinaryPOD(output, cur_element_count); | ||
| if (!output.good()) { | ||
| return Status("Failed writing index metadata"); | ||
| } | ||
| // *NoExceptions I/O checks stream state. Callers must leave the default | ||
| // iostream exception mask (goodbit); enabling failbit/badbit can throw. | ||
| if (!output) { | ||
| return Status("Cannot save index: output stream is not open or in a failed state"); | ||
| } | ||
| writeBinaryPOD(output, maxelements_); | ||
| writeBinaryPOD(output, size_per_element_); | ||
| writeBinaryPOD(output, cur_element_count); | ||
| if (!output.good()) { | ||
| return Status("Failed writing index metadata"); | ||
| } | ||
|
|
||
| output.write(data_, maxelements_ * size_per_element_); | ||
| if (!output.good()) { | ||
| return Status("Failed writing vector data"); | ||
| } | ||
| return OkStatus(); | ||
| }); | ||
| output.write(data_, maxelements_ * size_per_element_); | ||
| if (!output.good()) { | ||
| return Status("Failed writing vector data"); | ||
| } | ||
| return OkStatus(); | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -162,32 +168,86 @@ class BruteforceSearch : public AlgorithmInterface<dist_t> { | |
| } | ||
|
|
||
|
|
||
| void loadIndex(std::istream &input, SpaceInterface<dist_t> *s) { | ||
| Status loadIndexNoExceptions(std::istream &input, SpaceInterface<dist_t> *s) { | ||
| if (!input) { | ||
| return Status("Cannot load index: input stream is not open or not readable"); | ||
| } | ||
| size_t file_maxelements = 0; | ||
| size_t file_size_per_element = 0; | ||
| size_t file_cur_count = 0; | ||
| readBinaryPOD(input, file_maxelements); | ||
| readBinaryPOD(input, file_size_per_element); | ||
| readBinaryPOD(input, file_cur_count); | ||
| if (!input) { | ||
| HNSWLIB_THROW_RUNTIME_ERROR("Cannot load index: input stream is not open or not readable"); | ||
| return Status("Cannot load index: failed to read index header"); | ||
| } | ||
| if (file_cur_count > file_maxelements) { | ||
| return Status("Cannot load index: cur_element_count exceeds maxelements"); | ||
| } | ||
| readBinaryPOD(input, maxelements_); | ||
| readBinaryPOD(input, size_per_element_); | ||
| readBinaryPOD(input, cur_element_count); | ||
|
|
||
| data_size_ = s->get_data_size(); | ||
| size_t data_size = s->get_data_size(); | ||
| size_t size_per_element = data_size + sizeof(labeltype); | ||
| if (file_size_per_element != size_per_element) { | ||
| return Status("Cannot load index: size_per_element does not match space"); | ||
| } | ||
| char *new_data = (char *) malloc(file_maxelements * size_per_element); | ||
| if (new_data == nullptr) | ||
| return Status("Not enough memory: loadIndex failed to allocate data"); | ||
| input.read(new_data, file_maxelements * size_per_element); | ||
| if (!input) { | ||
| free(new_data); | ||
| return Status("Cannot load index: failed to read vector data"); | ||
| } | ||
|
|
||
| std::unordered_map<labeltype, size_t> new_dict; | ||
| #if defined(__EXCEPTIONS) || _HAS_EXCEPTIONS == 1 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of repeating In hnswlib.h: #if defined(__EXCEPTIONS) || _HAS_EXCEPTIONS == 1
#define HNSWLIB_EXCEPTIONS_ENABLED 1
#else
#undef HNSWLIB_EXCEPTIONS_ENABLED
#endifThen here #ifdef HNSWLIB_EXCEPTIONS_ENABLED
try {
#endifetc. |
||
| try { | ||
| #endif | ||
| for (size_t i = 0; i < file_cur_count; i++) { | ||
| labeltype lab; | ||
| memcpy(&lab, new_data + i * size_per_element + data_size, sizeof(lab)); | ||
| new_dict[lab] = i; | ||
| } | ||
| #if defined(__EXCEPTIONS) || _HAS_EXCEPTIONS == 1 | ||
| } catch (const std::bad_alloc&) { | ||
| free(new_data); | ||
| return Status("Not enough memory: loadIndex failed to rebuild label map"); | ||
| } | ||
| #endif | ||
|
|
||
| free(data_); | ||
| data_ = new_data; | ||
| maxelements_ = file_maxelements; | ||
| cur_element_count = file_cur_count; | ||
| data_size_ = data_size; | ||
| size_per_element_ = size_per_element; | ||
| fstdistfunc_ = s->get_dist_func(); | ||
| dist_func_param_ = s->get_dist_func_param(); | ||
| size_per_element_ = data_size_ + sizeof(labeltype); | ||
| data_ = (char *) malloc(maxelements_ * size_per_element_); | ||
| if (data_ == nullptr) | ||
| HNSWLIB_THROW_RUNTIME_ERROR("Not enough memory: loadIndex failed to allocate data"); | ||
|
|
||
| input.read(data_, maxelements_ * size_per_element_); | ||
| dict_external_to_internal.swap(new_dict); | ||
| return OkStatus(); | ||
| } | ||
|
|
||
|
|
||
| void loadIndex(const std::string &location, SpaceInterface<dist_t> *s) { | ||
| Status loadIndexNoExceptions(const std::string &location, SpaceInterface<dist_t> *s) { | ||
| std::ifstream input(location, std::ios::binary); | ||
| if (!input.is_open()) { | ||
| return Status("Cannot load index: input stream is not open or not readable"); | ||
| } | ||
| return loadIndexNoExceptions(input, s); | ||
| } | ||
|
|
||
| void loadIndex(std::istream &input, SpaceInterface<dist_t> *s) { | ||
| Status status = loadIndexNoExceptions(input, s); | ||
| if (!status.ok()) { | ||
| HNSWLIB_THROW_RUNTIME_ERROR(status.message()); | ||
| } | ||
| } | ||
|
|
||
| loadIndex(input, s); | ||
|
|
||
| input.close(); | ||
| void loadIndex(const std::string &location, SpaceInterface<dist_t> *s) { | ||
| Status status = loadIndexNoExceptions(location, s); | ||
| if (!status.ok()) { | ||
| HNSWLIB_THROW_RUNTIME_ERROR(status.message()); | ||
| } | ||
| } | ||
| }; | ||
| } // namespace hnswlib | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be preferable to use RAII to manage this allocation.