diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8b8ee27a7..20ea1498a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,19 +14,10 @@ jobs: - name: checkout submodules run: git submodule update --init --recursive - - name: install dependencies - run: | - sudo apt-get install g++-9 - name: install sdsl-lite - run: | - export CC=$(which gcc-9) - export CXX=$(which g++-9) - ./install.sh $(pwd) + run: ./install.sh $(pwd) - name: test sdsl-lite - run: | - export CC=$(which gcc-9) - export CXX=$(which g++-9) - mkdir -p build && cd build && make test-sdsl + run: mkdir -p build && cd build && make test-sdsl build-linux-clang: diff --git a/include/sdsl/memory_management.hpp b/include/sdsl/memory_management.hpp index 21979483b..562d7012c 100644 --- a/include/sdsl/memory_management.hpp +++ b/include/sdsl/memory_management.hpp @@ -475,22 +475,24 @@ class mmap_context { // open backend file depending on mode m_fd = memory_manager::open_file_for_mmap(m_file_name, std::ios_base::in); if (m_fd == -1) { - std::string open_error = "mmap_context: can't open file " - + m_file_name + " for mmap. " + std::string(util::str_from_errno()); - throw std::runtime_error(open_error); + throw std::runtime_error("mmap_context: can't open file " + + m_file_name + " for mmap. " + std::string(util::str_from_errno())); } // mmap data m_mapped_data = reinterpret_cast( memory_manager::mmap_file(m_fd, m_file_size_bytes, std::ios_base::in)); if (m_mapped_data == nullptr) { - std::string mmap_error = "mmap_context: mmap error. " - + std::string(util::str_from_errno()); - throw std::runtime_error(mmap_error); + memory_manager::close_file_for_mmap(m_fd); + m_fd = -1; + throw std::runtime_error("mmap_context: mmap error. " + + std::string(util::str_from_errno())); } - } + mmap_context(const mmap_context&) = delete; + mmap_context& operator=(const mmap_context&) = delete; + ~mmap_context() { if (m_mapped_data) { auto ret = memory_manager::mem_unmap(m_mapped_data, m_file_size_bytes); @@ -529,8 +531,12 @@ class mmap_ifstream : public std::ifstream { virtual ~mmap_ifstream() override {} - virtual std::shared_ptr get_mmap_context(); - const std::string& get_filename() const { return m_mmap_context->filename(); } + virtual std::shared_ptr get_mmap_context() { return m_mmap_context; } + const std::string& get_filename() const { + if (!m_mmap_context) + throw std::runtime_error("mmap_ifstream: stream not open"); + return m_mmap_context->filename(); + } private: std::shared_ptr m_mmap_context; diff --git a/include/sdsl/select_support_mcl.hpp b/include/sdsl/select_support_mcl.hpp index 4061cc504..b7b4a04cc 100644 --- a/include/sdsl/select_support_mcl.hpp +++ b/include/sdsl/select_support_mcl.hpp @@ -82,7 +82,12 @@ class select_support_mcl : public select_support int_vector<0> m_superblock; bit_vector m_miniblock; int_vector<0>* m_block = nullptr; + std::shared_ptr m_mmap_context; + int_vector<0> m_block_offset; + int_vector<8> m_block_width; size_type m_arg_cnt = 0; + inline size_type miniblock_size(size_type sb_idx) const; + inline uint64_t miniblock_value(size_type sb_idx, size_type idx) const; void copy(const select_support_mcl& ss); void initData(); void init_fast(const bit_vector* v=nullptr); @@ -148,6 +153,9 @@ select_support_mcl& select_support_mcl::operator= m_v = ss.m_v; // copy pointer to the supported bit vector m_miniblock = std::move(ss.m_miniblock); + m_mmap_context = std::move(ss.m_mmap_context); + m_block_offset = std::move(ss.m_block_offset); + m_block_width = std::move(ss.m_block_width); delete [] m_block; m_block = ss.m_block; @@ -165,6 +173,9 @@ void select_support_mcl::swap(select_support_mcl& ss) m_superblock.swap(ss.m_superblock); std::swap(m_miniblock, ss.m_miniblock); std::swap(m_block, ss.m_block); + std::swap(m_mmap_context, ss.m_mmap_context); + m_block_offset.swap(ss.m_block_offset); + m_block_width.swap(ss.m_block_width); std::swap(m_arg_cnt, ss.m_arg_cnt); } @@ -180,6 +191,9 @@ void select_support_mcl::copy(const select_support_mcl>12; m_miniblock = ss.m_miniblock; + m_mmap_context = ss.m_mmap_context; + m_block_offset = ss.m_block_offset; + m_block_width = ss.m_block_width; delete [] m_block; m_block = nullptr; @@ -191,6 +205,30 @@ void select_support_mcl::copy(const select_support_mcl +inline auto select_support_mcl::miniblock_size(size_type sb_idx) const -> size_type +{ + return (m_miniblock.size() and !m_miniblock[sb_idx]) ? 4096 : 64; +} + +template +inline uint64_t select_support_mcl::miniblock_value(size_type sb_idx, size_type idx) const +{ + if (m_block != nullptr) { + return m_block[sb_idx][idx]; + } + assert(m_mmap_context); + assert(sb_idx < m_block_offset.size()); + assert(sb_idx < m_block_width.size()); + const uint8_t width = m_block_width[sb_idx]; + assert(width > 0 and width <= 64); + assert(idx < miniblock_size(sb_idx)); + const size_type bit_idx = idx * static_cast(width); + const size_type byte_offset = m_block_offset[sb_idx]; + const uint64_t* data = reinterpret_cast(m_mmap_context->data() + byte_offset); + return bits::read_int(data + (bit_idx >> 6), static_cast(bit_idx & 0x3F), width); +} + template select_support_mcl::~select_support_mcl() { @@ -339,17 +377,16 @@ inline auto select_support_mcl::select(size_type i)const -> size_ size_type sb_idx = i>>12; // i/4096 size_type offset = i&0xFFF; // i%4096 if (m_miniblock.size() and !m_miniblock[sb_idx]) { - return m_block[sb_idx][offset]; // long superblock + return miniblock_value(sb_idx, offset); // long superblock } else { if ((offset&0x3F)==0) { assert(sb_idx < m_superblock.size()); - assert((offset>>6) < m_block[sb_idx].size()); - return m_superblock[sb_idx] + m_block[sb_idx][offset>>6/*/64*/]; + return m_superblock[sb_idx] + miniblock_value(sb_idx, offset>>6/*/64*/); } else { i = i-(sb_idx<<12)-((offset>>6)<<6); // now i > 0 and i <= 64 assert(i > 0); - size_type pos = m_superblock[sb_idx] + m_block[sb_idx][offset>>6] + 1; + size_type pos = m_superblock[sb_idx] + miniblock_value(sb_idx, offset>>6) + 1; // now pos is the position from where we search for the ith argument size_type word_pos = pos>>6; @@ -397,6 +434,9 @@ void select_support_mcl::initData() m_logn4 = m_logn2*m_logn2; } m_miniblock = bit_vector(); + m_mmap_context.reset(); + m_block_offset = int_vector<0>(); + m_block_width = int_vector<8>(); delete[] m_block; m_block = nullptr; } @@ -424,10 +464,25 @@ auto select_support_mcl::serialize(std::ostream& out, structure_t size_type written_bytes_long = 0; size_type written_bytes_mini = 0; for (size_type i=0; i < sb; ++i) { - if (m_miniblock.size() and !m_miniblock[i]) { - written_bytes_long += m_block[i].serialize(out); + const bool is_long = (m_miniblock.size() and !m_miniblock[i]); + if (m_block != nullptr) { + if (is_long) { + written_bytes_long += m_block[i].serialize(out); + } else { + written_bytes_mini += m_block[i].serialize(out); + } } else { - written_bytes_mini += m_block[i].serialize(out); + assert(m_mmap_context); + assert(i < m_block_offset.size()); + const size_type header_offset = m_block_offset[i] - 9; + const uint64_t size_bits = *reinterpret_cast(m_mmap_context->data() + header_offset); + const size_type bytes = 9 + (((size_bits + 63) >> 6) << 3); + out.write(reinterpret_cast(m_mmap_context->data() + header_offset), bytes); + if (is_long) { + written_bytes_long += bytes; + } else { + written_bytes_mini += bytes; + } } } written_bytes += written_bytes_long; @@ -455,24 +510,41 @@ void select_support_mcl::load(std::istream& in, const bit_vector* m_miniblock.load(in); // Load the mini_or_long helper vector - m_block = new int_vector<0>[sb]; // Create miniblock int_vector<0> - - std::shared_ptr m_mmap_context; - std::streamsize offset = 0; if (auto *mmap_in = dynamic_cast(&in)) { m_mmap_context = mmap_in->get_mmap_context(); - offset = in.tellg(); - } - - for (size_type i=0; i < sb; ++i) { - if (offset) { - offset += m_block[i].load(m_mmap_context, offset); - } else { + std::streampos stream_offset = in.tellg(); + if (stream_offset < 0) { + throw std::ios_base::failure("bad stream"); + } + size_type offset = static_cast(stream_offset); + const size_type file_size = m_mmap_context->file_size_bytes(); + m_block_offset = int_vector<0>(sb, 0, 64); + m_block_width = int_vector<8>(sb, 0); + for (size_type i=0; i < sb; ++i) { + if (file_size < 9 or offset > file_size - 9) { + throw std::runtime_error("trying reading beyond the mmap'ed file"); + } + m_block_width[i] = m_mmap_context->data()[offset + 8]; + if (m_block_width[i] == 0 or m_block_width[i] > 64) + throw std::runtime_error("select_support_mcl: invalid block width in mmap'd file"); + if (*reinterpret_cast(m_mmap_context->data() + offset) + != miniblock_size(i) * static_cast(m_block_width[i])) + throw std::runtime_error("select_support_mcl: block size mismatch in mmap'd file"); + m_block_offset[i] = offset + 9; + const size_type bytes = 9 + (((miniblock_size(i) + * static_cast(m_block_width[i]) + 63) >> 6) << 3); + if (bytes > file_size or offset > file_size - bytes) { + throw std::runtime_error("int_vector spans beyond the mmap'ed file"); + } + offset += bytes; + } + in.seekg(offset); + } else { + m_block = new int_vector<0>[sb]; // Create miniblock int_vector<0> + for (size_type i=0; i < sb; ++i) { m_block[i].load(in); } } - if (offset) - in.seekg(offset); } } diff --git a/lib/memory_management.cpp b/lib/memory_management.cpp index 9680ef120..6c8401bdb 100644 --- a/lib/memory_management.cpp +++ b/lib/memory_management.cpp @@ -645,6 +645,4 @@ hugepage_allocator::determine_available_hugepage_memory() } #endif -std::shared_ptr mmap_ifstream::get_mmap_context() { return m_mmap_context; } - } diff --git a/test/select_support_mcl_mmap_test.cpp b/test/select_support_mcl_mmap_test.cpp new file mode 100644 index 000000000..3ff2b1e01 --- /dev/null +++ b/test/select_support_mcl_mmap_test.cpp @@ -0,0 +1,203 @@ +#include "sdsl/bit_vectors.hpp" +#include "sdsl/select_support.hpp" +#include "sdsl/io.hpp" +#include "sdsl/memory_management.hpp" +#include "gtest/gtest.h" +#include +#include + +using namespace sdsl; +using namespace std; + +string temp_dir; + +namespace +{ + +template +class select_support_mcl_mmap_test : public ::testing::Test { }; + +using testing::Types; + +typedef Types, + select_support_mcl<0>, + select_support_mcl<01,2>, + select_support_mcl<10,2> + > Implementations; + +TYPED_TEST_CASE(select_support_mcl_mmap_test, Implementations); + +// Helper: count pattern matches and collect expected select() results +template +vector collect_select_answers(const bit_vector& bv) +{ + vector answers; + for (uint64_t j = 0; j < bv.size(); ++j) { + bool found = (j >= T::bit_pat_len - 1); + for (uint8_t k = 0; found && k < T::bit_pat_len; ++k) { + found &= bv[j - k] == ((T::bit_pat >> k) & 1); + } + if (found) { + answers.push_back(j); + } + } + return answers; +} + +// Build a bit_vector with enough structure to exercise both short and long superblocks +bit_vector make_test_bitvector() +{ + // ~100k bits — enough to produce several superblocks (4096 args each) + const uint64_t n = 100000; + bit_vector bv(n, 0); + // Dense region: every 3rd bit set + for (uint64_t i = 0; i < n / 2; i += 3) { + bv[i] = 1; + } + // Sparse region: every 1000th bit set + for (uint64_t i = n / 2; i < n; i += 1000) { + bv[i] = 1; + } + return bv; +} + +//! Load via mmap and verify all select() results match the original +TYPED_TEST(select_support_mcl_mmap_test, mmap_load_and_select) +{ + bit_vector bv = make_test_bitvector(); + typename TypeParam::bit_vector_type bv_orig(bv); + TypeParam ss_orig(&bv_orig); + + auto answers = collect_select_answers(bv); + if (answers.empty()) return; + + // Verify original works + for (uint64_t i = 0; i < answers.size(); ++i) { + ASSERT_EQ(answers[i], ss_orig.select(i + 1)); + } + + // Serialize bit_vector and select_support to the same file + string file = temp_dir + "/select_mcl_mmap_test.sdsl"; + { + osfstream out(file, ios::binary | ios::trunc | ios::out); + bv_orig.serialize(out); + ss_orig.serialize(out); + out.close(); + } + + // Load via mmap_ifstream + typename TypeParam::bit_vector_type bv_mmap; + TypeParam ss_mmap; + { + mmap_ifstream in(file, ios::binary | ios::in); + ASSERT_TRUE(in.good()); + bv_mmap.load(in); + ss_mmap.load(in, &bv_mmap); + } + + // Verify all select() results + for (uint64_t i = 0; i < answers.size(); ++i) { + ASSERT_EQ(answers[i], ss_mmap.select(i + 1)) + << "mismatch at select(" << (i + 1) << ")"; + } + + remove(file); +} + +//! Load via mmap, re-serialize, load via normal ifstream, verify +TYPED_TEST(select_support_mcl_mmap_test, mmap_serialize_roundtrip) +{ + bit_vector bv = make_test_bitvector(); + typename TypeParam::bit_vector_type bv_orig(bv); + TypeParam ss_orig(&bv_orig); + + auto answers = collect_select_answers(bv); + if (answers.empty()) return; + + string file1 = temp_dir + "/select_mcl_mmap_rt1.sdsl"; + string file2 = temp_dir + "/select_mcl_mmap_rt2.sdsl"; + + // Serialize original + { + osfstream out(file1, ios::binary | ios::trunc | ios::out); + bv_orig.serialize(out); + ss_orig.serialize(out); + } + + // Load via mmap + typename TypeParam::bit_vector_type bv_mmap; + TypeParam ss_mmap; + { + mmap_ifstream in(file1, ios::binary | ios::in); + ASSERT_TRUE(in.good()); + bv_mmap.load(in); + ss_mmap.load(in, &bv_mmap); + } + + // Re-serialize the mmap-loaded object + { + osfstream out(file2, ios::binary | ios::trunc | ios::out); + bv_mmap.serialize(out); + ss_mmap.serialize(out); + } + + // Load from the second file via normal ifstream + typename TypeParam::bit_vector_type bv_normal; + TypeParam ss_normal; + { + isfstream in(file2, ios::binary | ios::in); + ASSERT_TRUE(in.good()); + bv_normal.load(in); + ss_normal.load(in, &bv_normal); + } + + // Verify all select() results + for (uint64_t i = 0; i < answers.size(); ++i) { + ASSERT_EQ(answers[i], ss_normal.select(i + 1)) + << "roundtrip mismatch at select(" << (i + 1) << ")"; + } + + remove(file1); + remove(file2); +} + +//! Test with empty bit_vector (no arguments to select) +TYPED_TEST(select_support_mcl_mmap_test, mmap_load_empty) +{ + bit_vector bv(0); + typename TypeParam::bit_vector_type bv_orig(bv); + TypeParam ss_orig(&bv_orig); + + string file = temp_dir + "/select_mcl_mmap_empty.sdsl"; + { + osfstream out(file, ios::binary | ios::trunc | ios::out); + bv_orig.serialize(out); + ss_orig.serialize(out); + } + + typename TypeParam::bit_vector_type bv_mmap; + TypeParam ss_mmap; + { + mmap_ifstream in(file, ios::binary | ios::in); + ASSERT_TRUE(in.good()); + bv_mmap.load(in); + ss_mmap.load(in, &bv_mmap); + } + + ASSERT_EQ(bv_orig.size(), bv_mmap.size()); + + remove(file); +} + +}// end namespace + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + if (argc < 2) { + cout << "Usage: " << argv[0] << " tmp_dir" << endl; + return 1; + } + temp_dir = argv[1]; + return RUN_ALL_TESTS(); +}