X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=plocate-build.cpp;h=53cefde64c2149064d43a41fba3639143ec32b2b;hb=994138463a9df08adbd7759049e8da415cba3e59;hp=e6789d0ab8084586166ac3e50bd7e4138731eda7;hpb=184cfc93ecb5a17b3d5360d5953d7bf4126b5f5c;p=plocate diff --git a/plocate-build.cpp b/plocate-build.cpp index e6789d0..53cefde 100644 --- a/plocate-build.cpp +++ b/plocate-build.cpp @@ -1,29 +1,32 @@ -#include -#include +#include "vp4.h" + #include -#include -#include -#include +#include +#include #include -#include +#include #include +#include +#include +#include #include -#include -#include -#include #include +#include +#include +#include +#include #include -#include "vp4.h" - -#define P4NENC_BOUND(n) ((n+127)/128+(n+32)*sizeof(uint32_t)) +#define P4NENC_BOUND(n) ((n + 127) / 128 + (n + 32) * sizeof(uint32_t)) #define dprintf(...) //#define dprintf(...) fprintf(stderr, __VA_ARGS__); using namespace std; using namespace std::chrono; - -static inline uint32_t read_unigram(const string &s, size_t idx) + +string zstd_compress(const string &src, string *tempbuf); + +static inline uint32_t read_unigram(const string_view s, size_t idx) { if (idx < s.size()) { return (unsigned char)s[idx]; @@ -32,23 +35,21 @@ static inline uint32_t read_unigram(const string &s, size_t idx) } } -static inline uint32_t read_trigram(const string &s, size_t start) +static inline uint32_t read_trigram(const string_view s, size_t start) { return read_unigram(s, start) | (read_unigram(s, start + 1) << 8) | (read_unigram(s, start + 2) << 16); } -enum -{ - DBE_NORMAL = 0, /* A non-directory file */ - DBE_DIRECTORY = 1, /* A directory */ - DBE_END = 2 /* End of directory contents; contains no name */ +enum { + DBE_NORMAL = 0, /* A non-directory file */ + DBE_DIRECTORY = 1, /* A directory */ + DBE_END = 2 /* End of directory contents; contains no name */ }; // From mlocate. -struct db_header -{ +struct db_header { uint8_t magic[8]; uint32_t conf_size; uint8_t version; @@ -57,14 +58,145 @@ struct db_header }; // From mlocate. -struct db_directory -{ +struct db_directory { uint64_t time_sec; uint32_t time_nsec; uint8_t pad[4]; }; -const char *handle_directory(const char *ptr, vector *files) +class PostingListBuilder { +public: + void add_docid(uint32_t docid); + void finish(); + + string encoded; + size_t num_docids = 0; + +private: + void write_header(uint32_t docid); + void append_block(); + + vector pending_docids; + + uint32_t last_block_end; +}; + +void PostingListBuilder::add_docid(uint32_t docid) +{ + // Deduplicate against the last inserted value, if any. + if (pending_docids.empty()) { + if (encoded.empty()) { + // Very first docid. + write_header(docid); + ++num_docids; + last_block_end = docid; + return; + } else if (docid == last_block_end) { + return; + } + } else { + if (docid == pending_docids.back()) { + return; + } + } + + pending_docids.push_back(docid); + if (pending_docids.size() == 128) { + append_block(); + pending_docids.clear(); + last_block_end = docid; + } + ++num_docids; +} + +void PostingListBuilder::finish() +{ + if (pending_docids.empty()) { + return; + } + + assert(!encoded.empty()); // write_header() should already have run. + + // No interleaving for partial blocks. + unsigned char buf[P4NENC_BOUND(128)]; + unsigned char *end = p4d1enc32(pending_docids.data(), pending_docids.size(), buf, last_block_end); + encoded.append(reinterpret_cast(buf), reinterpret_cast(end)); +} + +void PostingListBuilder::append_block() +{ + unsigned char buf[P4NENC_BOUND(128)]; + assert(pending_docids.size() == 128); + unsigned char *end = p4d1enc128v32(pending_docids.data(), 128, buf, last_block_end); + encoded.append(reinterpret_cast(buf), reinterpret_cast(end)); +} + +void PostingListBuilder::write_header(uint32_t docid) +{ + unsigned char buf[P4NENC_BOUND(1)]; + size_t bytes = p4nd1enc128v32(&docid, 1, buf); + encoded.append(reinterpret_cast(buf), bytes); +} + +class Corpus { +public: + Corpus(size_t block_size) + : block_size(block_size) {} + void add_file(string filename); + void flush_block(); + + vector filename_blocks; + unordered_map invindex; + size_t num_files = 0, num_files_in_block = 0, num_blocks = 0; + +private: + string current_block; + string tempbuf; + const size_t block_size; +}; + +void Corpus::add_file(string filename) +{ + ++num_files; + if (!current_block.empty()) { + current_block.push_back('\0'); + } + current_block += filename; + if (++num_files_in_block == block_size) { + flush_block(); + } +} + +void Corpus::flush_block() +{ + if (current_block.empty()) { + return; + } + + uint32_t docid = num_blocks; + + // Create trigrams. + const char *ptr = current_block.c_str(); + while (ptr < current_block.c_str() + current_block.size()) { + string_view s(ptr); + if (s.size() >= 3) { + for (size_t j = 0; j < s.size() - 2; ++j) { + uint32_t trgm = read_trigram(s, j); + invindex[trgm].add_docid(docid); + } + } + ptr += s.size() + 1; + } + + // Compress and add the filename block. + filename_blocks.push_back(zstd_compress(current_block, &tempbuf)); + + current_block.clear(); + num_files_in_block = 0; + ++num_blocks; +} + +const char *handle_directory(const char *ptr, Corpus *corpus) { ptr += sizeof(db_directory); @@ -73,16 +205,16 @@ const char *handle_directory(const char *ptr, vector *files) if (dir_path == "/") { dir_path = ""; } - - for ( ;; ) { + + for (;;) { uint8_t type = *ptr++; if (type == DBE_NORMAL) { string filename = ptr; - files->push_back(dir_path + "/" + filename); + corpus->add_file(dir_path + "/" + filename); ptr += filename.size() + 1; } else if (type == DBE_DIRECTORY) { string dirname = ptr; - files->push_back(dir_path + "/" + dirname); + corpus->add_file(dir_path + "/" + dirname); ptr += dirname.size() + 1; } else { return ptr; @@ -90,7 +222,7 @@ const char *handle_directory(const char *ptr, vector *files) } } -void read_mlocate(const char *filename, vector *files) +void read_mlocate(const char *filename, Corpus *corpus) { int fd = open(filename, O_RDONLY); if (fd == -1) { @@ -116,29 +248,30 @@ void read_mlocate(const char *filename, vector *files) const char *ptr = data + offset; while (ptr < data + len) { - ptr = handle_directory(ptr, files); + ptr = handle_directory(ptr, corpus); } munmap((void *)data, len); close(fd); } -string zstd_compress(const string &src) +string zstd_compress(const string &src, string *tempbuf) { size_t max_size = ZSTD_compressBound(src.size()); - string dst; - dst.resize(max_size); - size_t size = ZSTD_compress(&dst[0], max_size, src.data(), src.size(), /*level=*/6); - dst.resize(size); - return dst; + if (tempbuf->size() < max_size) { + tempbuf->resize(max_size); + } + size_t size = ZSTD_compress(&(*tempbuf)[0], max_size, src.data(), src.size(), /*level=*/6); + return string(tempbuf->data(), size); } void do_build(const char *infile, const char *outfile, int block_size) { - //steady_clock::time_point start = steady_clock::now(); + steady_clock::time_point start __attribute__((unused)) = steady_clock::now(); + + Corpus corpus(block_size); - vector files; - read_mlocate(infile, &files); + read_mlocate(infile, &corpus); if (false) { // To read a plain text file. FILE *fp = fopen(infile, "r"); while (!feof(fp)) { @@ -147,48 +280,31 @@ void do_build(const char *infile, const char *outfile, int block_size) break; } string s(buf); - if (s.back() == '\n') s.pop_back(); - files.push_back(move(s)); + if (s.back() == '\n') + s.pop_back(); + corpus.add_file(move(s)); } fclose(fp); } - dprintf("Read %zu files from %s\n", files.size(), infile); - - unordered_map pl; + corpus.flush_block(); + dprintf("Read %zu files from %s\n", corpus.num_files, infile); + size_t trigrams = 0, longest_posting_list = 0; - unordered_map> invindex; - for (size_t i = 0; i < files.size(); ++i) { - const string &s = files[i]; - if (s.size() >= 3) { - for (size_t j = 0; j < s.size() - 2; ++j) { - uint32_t trgm = read_trigram(s, j); - invindex[trgm].push_back(i / block_size); - } - } - } - string buf; size_t bytes_used = 0; - for (auto &[trigram, docids] : invindex) { - auto last = unique(docids.begin(), docids.end()); - docids.erase(last, docids.end()); - longest_posting_list = max(longest_posting_list, docids.size()); - trigrams += docids.size(); - - size_t bytes_needed = P4NENC_BOUND(docids.size()); - if (buf.size() < bytes_needed) buf.resize(bytes_needed); - size_t bytes = p4nd1enc128v32(&docids[0], docids.size(), reinterpret_cast(&buf[0])); - pl[trigram] = string(buf.data(), bytes); - bytes_used += bytes; + for (auto &[trigram, pl_builder] : corpus.invindex) { + pl_builder.finish(); + longest_posting_list = max(longest_posting_list, pl_builder.num_docids); + trigrams += pl_builder.num_docids; + bytes_used += pl_builder.encoded.size(); } dprintf("%zu files, %zu different trigrams, %zu entries, avg len %.2f, longest %zu\n", - files.size(), invindex.size(), trigrams, double(trigrams) / invindex.size(), longest_posting_list); - + corpus.num_files, corpus.invindex.size(), trigrams, double(trigrams) / corpus.invindex.size(), longest_posting_list); dprintf("%zu bytes used for posting lists (%.2f bits/entry)\n", bytes_used, 8 * bytes_used / double(trigrams)); - //steady_clock::time_point end = steady_clock::now(); - dprintf("Building posting lists took %.1f ms.\n\n", 1e3 * duration(end - start).count()); + + dprintf("Building posting lists took %.1f ms.\n\n", 1e3 * duration(steady_clock::now() - start).count()); vector all_trigrams; - for (auto &[trigram, docids] : invindex) { + for (auto &[trigram, pl_builder] : corpus.invindex) { all_trigrams.push_back(trigram); } sort(all_trigrams.begin(), all_trigrams.end()); @@ -197,78 +313,56 @@ void do_build(const char *infile, const char *outfile, int block_size) FILE *outfp = fopen(outfile, "wb"); // Write the header. - uint64_t num_trigrams = invindex.size(); // 64 to get alignment. + uint64_t num_trigrams = corpus.invindex.size(); // 64 to get alignment. fwrite(&num_trigrams, sizeof(num_trigrams), 1, outfp); // Find out where the posting lists will end. uint64_t offset = sizeof(uint64_t) * 2 + 16 * all_trigrams.size(); uint64_t filename_index_offset = offset; - for (uint32_t trgm : all_trigrams) { - filename_index_offset += pl[trgm].size(); + for (auto &[trigram, pl_builder] : corpus.invindex) { + filename_index_offset += pl_builder.encoded.size(); } fwrite(&filename_index_offset, sizeof(filename_index_offset), 1, outfp); size_t bytes_for_trigrams = 0, bytes_for_posting_lists = 0, bytes_for_filename_index = 0, bytes_for_filenames = 0; - for (uint32_t trgm : all_trigrams) { // 16 bytes: trgm (4), number of docids (4), byte offset (8) fwrite(&trgm, sizeof(trgm), 1, outfp); // One byte wasted, but OK. - uint32_t num_docids = invindex[trgm].size(); + const PostingListBuilder &pl_builder = corpus.invindex[trgm]; + uint32_t num_docids = pl_builder.num_docids; fwrite(&num_docids, sizeof(num_docids), 1, outfp); fwrite(&offset, sizeof(offset), 1, outfp); - offset += pl[trgm].size(); + offset += pl_builder.encoded.size(); bytes_for_trigrams += 16; } // Write the actual posting lists. for (uint32_t trgm : all_trigrams) { - fwrite(pl[trgm].data(), pl[trgm].size(), 1, outfp); - bytes_for_posting_lists += pl[trgm].size(); - } - - // Compress the filenames into blocks. - vector filename_blocks; - string uncompressed_filenames; - int num_files_this_block = 0; - - string dst; - - for (string &filename : files) { - uncompressed_filenames += filename; - filename.clear(); - if (++num_files_this_block == block_size) { - filename_blocks.push_back(zstd_compress(uncompressed_filenames)); - uncompressed_filenames.clear(); - num_files_this_block = 0; - } else { - uncompressed_filenames.push_back('\0'); - } - } - if (num_files_this_block > 0) { - filename_blocks.push_back(zstd_compress(uncompressed_filenames)); + const string &encoded = corpus.invindex[trgm].encoded; + fwrite(encoded.data(), encoded.size(), 1, outfp); + bytes_for_posting_lists += encoded.size(); } - files.clear(); // Stick an empty block at the end as sentinel. - filename_blocks.push_back(""); + corpus.filename_blocks.push_back(""); // Write the offsets to the filenames. - offset = filename_index_offset + filename_blocks.size() * sizeof(offset); - for (const string &filename : filename_blocks) { + offset = filename_index_offset + corpus.filename_blocks.size() * sizeof(offset); + for (const string &filename : corpus.filename_blocks) { fwrite(&offset, sizeof(offset), 1, outfp); offset += filename.size(); bytes_for_filename_index += sizeof(offset); bytes_for_filenames += filename.size(); } - + // Write the actual filenames. - for (const string &filename : filename_blocks) { + for (const string &filename : corpus.filename_blocks) { fwrite(filename.data(), filename.size(), 1, outfp); } fclose(outfp); - //size_t total_bytes = (bytes_for_trigrams + bytes_for_posting_lists + bytes_for_filename_index + bytes_for_filenames); + size_t total_bytes __attribute__((unused)) = (bytes_for_trigrams + bytes_for_posting_lists + bytes_for_filename_index + bytes_for_filenames); dprintf("Block size: %7d files\n", block_size); dprintf("Trigrams: %'7.1f MB\n", bytes_for_trigrams / 1048576.0);