contrib/faiss/faiss/invlists/PreadInvertedLists.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #include <faiss/invlists/PreadInvertedLists.h> |
9 | | |
10 | | #include <faiss/impl/FaissAssert.h> |
11 | | |
12 | | namespace faiss { |
13 | | |
14 | | /******************************************************* |
15 | | * PreadInvertedListsIterator |
16 | | *******************************************************/ |
17 | | |
18 | | namespace { |
19 | | |
20 | | struct PreadInvertedListsIterator : InvertedListsIterator { |
21 | | // --- list-level invariants (set once in ctor) --- |
22 | | size_t list_size = 0; |
23 | | size_t code_size = 0; |
24 | | |
25 | | // --- borrowed data for the entire list --- |
26 | | std::unique_ptr<ReadRef> codes_ref_; |
27 | | std::unique_ptr<ReadRef> ids_ref_; |
28 | | |
29 | | // --- iteration state --- |
30 | | size_t i = 0; |
31 | | |
32 | | PreadInvertedListsIterator( |
33 | | const PreadInvertedLists* parent, |
34 | 0 | size_t list_no) { |
35 | 0 | const OnDiskOneList& l = parent->lists.at(list_no); |
36 | 0 | if (l.size == 0) { |
37 | 0 | return; |
38 | 0 | } |
39 | 0 | list_size = l.size; |
40 | 0 | code_size = parent->code_size; |
41 | 0 | const auto* reader = &parent->reader(); |
42 | |
|
43 | 0 | const size_t codes_offset = l.offset; |
44 | 0 | const size_t ids_offset = l.offset + l.capacity * code_size; |
45 | | |
46 | | // Borrow the entire list's codes and ids in one shot. |
47 | | // A cache-backed reader can return pinned references here, |
48 | | // giving zero-copy access on repeated queries. |
49 | 0 | codes_ref_ = reader->borrow(codes_offset, list_size * code_size); |
50 | 0 | ids_ref_ = reader->borrow(ids_offset, list_size * sizeof(idx_t)); |
51 | 0 | } |
52 | | |
53 | | // non-copyable |
54 | | PreadInvertedListsIterator(const PreadInvertedListsIterator&) = delete; |
55 | | PreadInvertedListsIterator& operator=(const PreadInvertedListsIterator&) = |
56 | | delete; |
57 | | |
58 | 0 | bool is_available() const override { |
59 | 0 | return i < list_size; |
60 | 0 | } |
61 | | |
62 | 0 | void next() override { |
63 | 0 | ++i; |
64 | 0 | } |
65 | | |
66 | 0 | std::pair<idx_t, const uint8_t*> get_id_and_codes() override { |
67 | 0 | auto* ids = reinterpret_cast<const idx_t*>(ids_ref_->data()); |
68 | 0 | return {ids[i], codes_ref_->data() + i * code_size}; |
69 | 0 | } |
70 | | }; |
71 | | |
72 | | } // namespace |
73 | | |
74 | | /******************************************************* |
75 | | * PreadInvertedLists |
76 | | *******************************************************/ |
77 | | |
78 | | PreadInvertedLists::PreadInvertedLists(const OnDiskInvertedLists& src) |
79 | 0 | : ReadOnlyInvertedLists(src.nlist, src.code_size), lists(src.lists) { |
80 | 0 | use_iterator = true; |
81 | 0 | } |
82 | | |
83 | | PreadInvertedLists::PreadInvertedLists( |
84 | | size_t nlist, |
85 | | size_t code_size, |
86 | | std::vector<OnDiskOneList> lists) |
87 | 0 | : ReadOnlyInvertedLists(nlist, code_size), |
88 | 0 | lists(std::move(lists)) { |
89 | 0 | use_iterator = true; |
90 | 0 | } |
91 | | |
92 | | void PreadInvertedLists::set_reader( |
93 | 0 | std::unique_ptr<RandomAccessReader> reader) { |
94 | 0 | reader_ = std::move(reader); |
95 | 0 | } |
96 | | |
97 | 0 | const RandomAccessReader& PreadInvertedLists::reader() const { |
98 | 0 | FAISS_THROW_IF_NOT_MSG( |
99 | 0 | reader_, |
100 | 0 | "PreadInvertedLists: reader not set, call set_reader() first"); |
101 | 0 | return *reader_; |
102 | 0 | } |
103 | | |
104 | 0 | size_t PreadInvertedLists::list_size(size_t list_no) const { |
105 | 0 | return lists.at(list_no).size; |
106 | 0 | } |
107 | | |
108 | 0 | const uint8_t* PreadInvertedLists::get_codes(size_t list_no) const { |
109 | 0 | const OnDiskOneList& l = lists.at(list_no); |
110 | 0 | if (l.size == 0) { |
111 | 0 | return nullptr; |
112 | 0 | } |
113 | 0 | auto* out = new uint8_t[l.size * code_size]; |
114 | 0 | FAISS_THROW_IF_NOT_MSG( |
115 | 0 | reader_, |
116 | 0 | "PreadInvertedLists: reader not set, call set_reader() first"); |
117 | 0 | reader_->read_at(l.offset, out, l.size * code_size); |
118 | 0 | return out; |
119 | 0 | } |
120 | | |
121 | 0 | const idx_t* PreadInvertedLists::get_ids(size_t list_no) const { |
122 | 0 | const OnDiskOneList& l = lists.at(list_no); |
123 | 0 | if (l.size == 0) { |
124 | 0 | return nullptr; |
125 | 0 | } |
126 | 0 | auto* out = new idx_t[l.size]; |
127 | 0 | FAISS_THROW_IF_NOT_MSG( |
128 | 0 | reader_, |
129 | 0 | "PreadInvertedLists: reader not set, call set_reader() first"); |
130 | 0 | reader_->read_at( |
131 | 0 | l.offset + l.capacity * code_size, out, l.size * sizeof(idx_t)); |
132 | 0 | return out; |
133 | 0 | } |
134 | | |
135 | | void PreadInvertedLists::release_codes( |
136 | | size_t /*list_no*/, |
137 | 0 | const uint8_t* codes) const { |
138 | 0 | delete[] codes; |
139 | 0 | } |
140 | | |
141 | | void PreadInvertedLists::release_ids( |
142 | | size_t /*list_no*/, |
143 | 0 | const idx_t* ids) const { |
144 | 0 | delete[] ids; |
145 | 0 | } |
146 | | |
147 | 0 | bool PreadInvertedLists::is_empty(size_t list_no, void*) const { |
148 | 0 | return lists.at(list_no).size == 0; |
149 | 0 | } |
150 | | |
151 | | InvertedListsIterator* PreadInvertedLists::get_iterator( |
152 | | size_t list_no, |
153 | 0 | void*) const { |
154 | 0 | return new PreadInvertedListsIterator(this, list_no); |
155 | 0 | } |
156 | | |
157 | | void PreadInvertedLists::prefetch_lists( |
158 | | const idx_t* /*list_nos*/, |
159 | 0 | int /*nlist*/) const { |
160 | | // Intentionally empty. The iterator constructor already borrows |
161 | | // the entire list in one shot, so a synchronous prefetch here |
162 | | // would just duplicate the same I/O. If an asynchronous prefetch |
163 | | // mechanism becomes available in the future, this is the place to |
164 | | // add it. |
165 | 0 | } |
166 | | |
167 | | /******************************************************* |
168 | | * Helper: replace OnDiskInvertedLists with PreadInvertedLists |
169 | | *******************************************************/ |
170 | | |
171 | 0 | PreadInvertedLists* replace_with_pread_invlists(Index* index) { |
172 | 0 | auto* ivf = dynamic_cast<IndexIVF*>(index); |
173 | 0 | FAISS_THROW_IF_NOT_MSG( |
174 | 0 | ivf, "replace_with_pread_invlists expects an IndexIVF"); |
175 | | |
176 | 0 | auto* od = dynamic_cast<OnDiskInvertedLists*>(ivf->invlists); |
177 | 0 | FAISS_THROW_IF_NOT_MSG( |
178 | 0 | od, |
179 | 0 | "replace_with_pread_invlists expects IndexIVF with " |
180 | 0 | "OnDiskInvertedLists"); |
181 | | |
182 | 0 | auto* pread = new PreadInvertedLists(*od); |
183 | 0 | ivf->replace_invlists(pread, true); |
184 | 0 | return pread; |
185 | 0 | } |
186 | | |
187 | | } // namespace faiss |