+size_t scan_docids(const vector<string> &needles, const vector<uint32_t> &docids, const Corpus &corpus, IOUringEngine *engine)
+{
+ Serializer docids_in_order;
+ unordered_map<string, bool> access_rx_cache;
+ size_t matched = 0;
+ for (size_t i = 0; i < docids.size(); ++i) {
+ uint32_t docid = docids[i];
+ corpus.get_compressed_filename_block(docid, [i, &matched, &needles, &access_rx_cache, &docids_in_order](string compressed) {
+ matched += scan_file_block(needles, compressed, &access_rx_cache, i, &docids_in_order);
+ });
+ }
+ engine->finish();
+ return matched;
+}
+
+// We do this sequentially, as it's faster than scattering
+// a lot of I/O through io_uring and hoping the kernel will
+// coalesce it plus readahead for us.
+void scan_all_docids(const vector<string> &needles, int fd, const Corpus &corpus, IOUringEngine *engine)
+{
+ unordered_map<string, bool> access_rx_cache;
+ uint32_t num_blocks = corpus.get_num_filename_blocks();
+ unique_ptr<uint64_t[]> offsets(new uint64_t[num_blocks + 1]);
+ complete_pread(fd, offsets.get(), (num_blocks + 1) * sizeof(uint64_t), corpus.offset_for_block(0));
+ string compressed;
+ for (uint32_t io_docid = 0; io_docid < num_blocks; io_docid += 32) {
+ uint32_t last_docid = std::min(io_docid + 32, num_blocks);
+ size_t io_len = offsets[last_docid] - offsets[io_docid];
+ if (compressed.size() < io_len) {
+ compressed.resize(io_len);
+ }
+ complete_pread(fd, &compressed[0], io_len, offsets[io_docid]);
+
+ for (uint32_t docid = io_docid; docid < last_docid; ++docid) {
+ size_t relative_offset = offsets[docid] - offsets[io_docid];
+ size_t len = offsets[docid + 1] - offsets[docid];
+ scan_file_block(needles, { &compressed[relative_offset], len }, &access_rx_cache, 0, nullptr);
+ }
+ }
+}
+
+void do_search_file(const vector<string> &needles, const char *filename)