Coverage Report

Created: 2026-04-03 12:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
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