Coverage Report

Created: 2026-04-10 05:08

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
16
            size_t list_no) {
35
16
        const OnDiskOneList& l = parent->lists.at(list_no);
36
16
        if (l.size == 0) {
37
0
            return;
38
0
        }
39
16
        list_size = l.size;
40
16
        code_size = parent->code_size;
41
16
        const auto* reader = &parent->reader();
42
43
16
        const size_t codes_offset = l.offset;
44
16
        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
16
        codes_ref_ = reader->borrow(codes_offset, list_size * code_size);
50
16
        ids_ref_ = reader->borrow(ids_offset, list_size * sizeof(idx_t));
51
16
    }
52
53
    // non-copyable
54
    PreadInvertedListsIterator(const PreadInvertedListsIterator&) = delete;
55
    PreadInvertedListsIterator& operator=(const PreadInvertedListsIterator&) =
56
            delete;
57
58
50
    bool is_available() const override {
59
50
        return i < list_size;
60
50
    }
61
62
34
    void next() override {
63
34
        ++i;
64
34
    }
65
66
34
    std::pair<idx_t, const uint8_t*> get_id_and_codes() override {
67
34
        auto* ids = reinterpret_cast<const idx_t*>(ids_ref_->data());
68
34
        return {ids[i], codes_ref_->data() + i * code_size};
69
34
    }
70
};
71
72
} // namespace
73
74
/*******************************************************
75
 * PreadInvertedLists
76
 *******************************************************/
77
78
PreadInvertedLists::PreadInvertedLists(const OnDiskInvertedLists& src)
79
5
        : ReadOnlyInvertedLists(src.nlist, src.code_size), lists(src.lists) {
80
5
    use_iterator = true;
81
5
}
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
5
        std::unique_ptr<RandomAccessReader> reader) {
94
5
    reader_ = std::move(reader);
95
5
}
96
97
16
const RandomAccessReader& PreadInvertedLists::reader() const {
98
16
    FAISS_THROW_IF_NOT_MSG(
99
16
            reader_,
100
16
            "PreadInvertedLists: reader not set, call set_reader() first");
101
16
    return *reader_;
102
16
}
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
16
bool PreadInvertedLists::is_empty(size_t list_no, void*) const {
148
16
    return lists.at(list_no).size == 0;
149
16
}
150
151
InvertedListsIterator* PreadInvertedLists::get_iterator(
152
        size_t list_no,
153
16
        void*) const {
154
16
    return new PreadInvertedListsIterator(this, list_no);
155
16
}
156
157
void PreadInvertedLists::prefetch_lists(
158
        const idx_t* /*list_nos*/,
159
5
        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
5
}
166
167
/*******************************************************
168
 * Helper: replace OnDiskInvertedLists with PreadInvertedLists
169
 *******************************************************/
170
171
5
PreadInvertedLists* replace_with_pread_invlists(Index* index) {
172
5
    auto* ivf = dynamic_cast<IndexIVF*>(index);
173
5
    FAISS_THROW_IF_NOT_MSG(
174
5
            ivf, "replace_with_pread_invlists expects an IndexIVF");
175
176
5
    auto* od = dynamic_cast<OnDiskInvertedLists*>(ivf->invlists);
177
5
    FAISS_THROW_IF_NOT_MSG(
178
5
            od,
179
5
            "replace_with_pread_invlists expects IndexIVF with "
180
5
            "OnDiskInvertedLists");
181
182
5
    auto* pread = new PreadInvertedLists(*od);
183
5
    ivf->replace_invlists(pread, true);
184
5
    return pread;
185
5
}
186
187
} // namespace faiss