diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c2144e..6785c19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,25 +10,25 @@ set(CMAKE_CXX_FLAGS "-march=${MARCH}") message(STATUS "MARCH is '${MARCH}'") option(DISABLE_AVX512 "Disable AVX512 instructions" OFF) -if(DISABLE_AVX512) +if (DISABLE_AVX512) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-avx512f") message(STATUS "DISABLE_AVX512 is ON") -endif() +endif () option(ENABLE_ADDRESS_SANITIZER "Enable AddressSanitizer" OFF) -if(ENABLE_ADDRESS_SANITIZER) +if (ENABLE_ADDRESS_SANITIZER) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} -fsanitize=address") message(STATUS "AddressSanitizer is ON") -endif() +endif () option(PIXIE_COVERAGE "Enable coverage instrumentation" OFF) -if(PIXIE_COVERAGE) +if (PIXIE_COVERAGE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g --coverage") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") message(STATUS "Coverage instrumentation is ON") -endif() +endif () # --------------------------------------------------------------------------- # Build options @@ -39,53 +39,53 @@ option(PIXIE_THIRD_PARTY_BACKENDS "Build optional third-party backend integratio option(PIXIE_DIAGNOSTICS "Include diagnostic logs" OFF) option(PIXIE_DOCS "Build Doxygen documentation" OFF) -if(PIXIE_THIRD_PARTY_BACKENDS) +if (PIXIE_THIRD_PARTY_BACKENDS) add_compile_definitions(SDSL_SUPPORT) -endif() +endif () -if(PIXIE_DIAGNOSTICS) +if (PIXIE_DIAGNOSTICS) add_compile_definitions(PIXIE_DIAGNOSTICS) set(PIXIE_DIAGNOSTICS_LIBS spdlog::spdlog_header_only) -endif() +endif () # --------------------------------------------------------------------------- # Dependencies (fetched only when needed) # --------------------------------------------------------------------------- include(FetchContent) -if(PIXIE_DIAGNOSTICS) +if (PIXIE_DIAGNOSTICS) set(SPDLOG_BUILD_SHARED OFF CACHE BOOL "" FORCE) set(SPDLOG_BUILD_EXAMPLE OFF CACHE BOOL "" FORCE) set(SPDLOG_BUILD_TESTING OFF CACHE BOOL "" FORCE) set(SPDLOG_INSTALL OFF CACHE BOOL "" FORCE) FetchContent_Declare( - spdlog - GIT_REPOSITORY https://github.com/gabime/spdlog.git - GIT_TAG v1.14.1 + spdlog + GIT_REPOSITORY https://github.com/gabime/spdlog.git + GIT_TAG v1.14.1 ) FetchContent_MakeAvailable(spdlog) -endif() +endif () -if(PIXIE_BENCHMARKS) +if (PIXIE_BENCHMARKS) FetchContent_Declare( - googlebenchmark - GIT_REPOSITORY https://github.com/google/benchmark.git - GIT_TAG v1.9.4 + googlebenchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG v1.9.4 ) set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "Disable Google Benchmark tests" FORCE) set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "Disable Google Benchmark tests" FORCE) set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "Disable Google Benchmark install targets" FORCE) set(BENCHMARK_INSTALL_DOCS OFF CACHE BOOL "Disable Google Benchmark install docs" FORCE) FetchContent_MakeAvailable(googlebenchmark) -endif() +endif () -if(PIXIE_THIRD_PARTY_BACKENDS) +if (PIXIE_THIRD_PARTY_BACKENDS) set(PASTA_BIT_VECTOR_BUILD_TESTS OFF CACHE BOOL "Disable pasta::bit_vector tests" FORCE) set(PASTA_BIT_VECTOR_BUILD_BENCHMARKS OFF CACHE BOOL "Disable pasta::bit_vector benchmarks" FORCE) FetchContent_Declare( - pasta_bit_vector - GIT_REPOSITORY https://github.com/pasta-toolbox/bit_vector.git - GIT_TAG origin/main + pasta_bit_vector + GIT_REPOSITORY https://github.com/pasta-toolbox/bit_vector.git + GIT_TAG origin/main ) FetchContent_MakeAvailable(pasta_bit_vector) @@ -94,95 +94,108 @@ if(PIXIE_THIRD_PARTY_BACKENDS) set(SDSL_BUILD_TUTORIAL OFF CACHE BOOL "" FORCE) set(SDSL_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) FetchContent_Declare( - sdsl_lite - GIT_REPOSITORY https://github.com/xxsds/sdsl-lite.git - GIT_TAG v3.0.3 - PATCH_COMMAND ${CMAKE_COMMAND} + sdsl_lite + GIT_REPOSITORY https://github.com/xxsds/sdsl-lite.git + GIT_TAG v3.0.3 + PATCH_COMMAND ${CMAKE_COMMAND} -DROOT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} -DSDSL_SOURCE_DIR= -P ${CMAKE_CURRENT_SOURCE_DIR}/third_party/sdsl-lite/patch.cmake ) FetchContent_MakeAvailable(sdsl_lite) -endif() +endif () -if(PIXIE_TESTS) - if(NOT TARGET gtest_main) +if (PIXIE_TESTS) + if (NOT TARGET gtest_main) set(BUILD_GMOCK OFF CACHE BOOL "Disable GoogleMock" FORCE) set(INSTALL_GTEST OFF CACHE BOOL "Disable GoogleTest install targets" FORCE) FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.17.0 + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.17.0 ) FetchContent_MakeAvailable(googletest) - endif() + endif () include(GoogleTest) -endif() +endif () + +FetchContent_Declare( + mio + GIT_REPOSITORY https://github.com/vimpunk/mio.git + GIT_TAG master +) +FetchContent_MakeAvailable(mio) # --------------------------------------------------------------------------- # Unit tests # --------------------------------------------------------------------------- -if(PIXIE_TESTS) +if (PIXIE_TESTS) enable_testing() add_executable(unittests - src/tests/unittests.cpp) + src/tests/unittests.cpp) target_include_directories(unittests - PUBLIC include) + PUBLIC include) target_link_libraries(unittests - gtest - gtest_main - ${PIXIE_DIAGNOSTICS_LIBS}) + gtest + gtest_main + mio + ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(benchmark_tests - src/tests/benchmark_tests.cpp) + src/tests/benchmark_tests.cpp) target_include_directories(benchmark_tests - PUBLIC include) + PUBLIC include) target_link_libraries(benchmark_tests - gtest - gtest_main - ${PIXIE_DIAGNOSTICS_LIBS}) + gtest + gtest_main + mio + ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(test_rmm - src/tests/test_rmm.cpp) + src/tests/test_rmm.cpp) target_include_directories(test_rmm - PUBLIC include - PUBLIC include/misc) + PUBLIC include + PUBLIC include/misc) target_link_libraries(test_rmm - gtest - gtest_main - ${PIXIE_DIAGNOSTICS_LIBS}) - if(PIXIE_THIRD_PARTY_BACKENDS) + gtest + gtest_main + mio + ${PIXIE_DIAGNOSTICS_LIBS}) + if (PIXIE_THIRD_PARTY_BACKENDS) target_include_directories(test_rmm - PRIVATE ${sdsl_lite_SOURCE_DIR}/include) - endif() + PRIVATE ${sdsl_lite_SOURCE_DIR}/include) + endif () add_executable(louds_tree_tests - src/tests/louds_tree_tests.cpp) + src/tests/louds_tree_tests.cpp) target_include_directories(louds_tree_tests - PUBLIC include) + PUBLIC include) target_link_libraries(louds_tree_tests - gtest - gtest_main - ${PIXIE_DIAGNOSTICS_LIBS}) + gtest + gtest_main + mio + ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(wavelet_tree_tests src/tests/wavelet_tree_tests.cpp) target_include_directories(wavelet_tree_tests PUBLIC include) target_link_libraries(wavelet_tree_tests - gtest - gtest_main - ${PIXIE_DIAGNOSTICS_LIBS}) + gtest + gtest_main + mio + ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(excess_positions_tests - src/tests/excess_positions_tests.cpp) + src/tests/excess_positions_tests.cpp) target_include_directories(excess_positions_tests - PUBLIC include) + PUBLIC include) target_link_libraries(excess_positions_tests - gtest - gtest_main - ${PIXIE_DIAGNOSTICS_LIBS}) + gtest + gtest_main + mio + ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(bp_tree_tests src/tests/bp_tree_tests.cpp) @@ -191,6 +204,7 @@ if(PIXIE_TESTS) target_link_libraries(bp_tree_tests gtest gtest_main + mio ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(dfuds_tree_tests @@ -200,98 +214,107 @@ if(PIXIE_TESTS) target_link_libraries(dfuds_tree_tests gtest gtest_main + mio ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(excess_record_lows_tests - src/tests/excess_record_lows_tests.cpp) + src/tests/excess_record_lows_tests.cpp) target_include_directories(excess_record_lows_tests - PUBLIC include) + PUBLIC include) target_link_libraries(excess_record_lows_tests - gtest - gtest_main - ${PIXIE_DIAGNOSTICS_LIBS}) + gtest + gtest_main + ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(rmq_tests - src/tests/rmq_tests.cpp) + src/tests/rmq_tests.cpp) target_include_directories(rmq_tests - PUBLIC include) + PUBLIC include) target_link_libraries(rmq_tests - gtest - gtest_main - ${PIXIE_DIAGNOSTICS_LIBS}) - if(PIXIE_THIRD_PARTY_BACKENDS) + gtest + gtest_main + mio + ${PIXIE_DIAGNOSTICS_LIBS}) + if (PIXIE_THIRD_PARTY_BACKENDS) target_include_directories(rmq_tests - PRIVATE ${sdsl_lite_SOURCE_DIR}/include) - endif() -endif() + PRIVATE ${sdsl_lite_SOURCE_DIR}/include) + endif () +endif () # --------------------------------------------------------------------------- # Benchmarks (Pixie-only and comparison benchmarks) # --------------------------------------------------------------------------- -if(PIXIE_BENCHMARKS) +if (PIXIE_BENCHMARKS) add_executable(benchmarks - src/benchmarks/benchmarks.cpp) + src/benchmarks/benchmarks.cpp) target_include_directories(benchmarks - PUBLIC include) + PUBLIC include) target_link_libraries(benchmarks - benchmark - benchmark_main - ${PIXIE_DIAGNOSTICS_LIBS}) - if(PIXIE_THIRD_PARTY_BACKENDS) + benchmark + benchmark_main + mio + ${PIXIE_DIAGNOSTICS_LIBS}) + if (PIXIE_THIRD_PARTY_BACKENDS) target_link_libraries(benchmarks pasta_bit_vector) target_compile_definitions(benchmarks PRIVATE PIXIE_THIRD_PARTY_BENCHMARKS) - endif() + else () + target_link_libraries(mio) + endif () add_executable(bench_rmm - src/benchmarks/bench_rmm.cpp) + src/benchmarks/bench_rmm.cpp) target_include_directories(bench_rmm - PUBLIC include) + PUBLIC include) target_link_libraries(bench_rmm - benchmark - ${PIXIE_DIAGNOSTICS_LIBS}) + benchmark + mio + ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(bench_rmm_btree - src/benchmarks/bench_rmm_btree.cpp) + src/benchmarks/bench_rmm_btree.cpp) target_include_directories(bench_rmm_btree - PUBLIC include) + PUBLIC include) target_link_libraries(bench_rmm_btree - benchmark - ${PIXIE_DIAGNOSTICS_LIBS}) + benchmark + mio + ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(bench_rmq - src/benchmarks/bench_rmq.cpp) + src/benchmarks/bench_rmq.cpp) target_include_directories(bench_rmq - PUBLIC include) + PUBLIC include) target_link_libraries(bench_rmq - benchmark - ${PIXIE_DIAGNOSTICS_LIBS}) - if(PIXIE_THIRD_PARTY_BACKENDS) + benchmark + mio + ${PIXIE_DIAGNOSTICS_LIBS}) + if (PIXIE_THIRD_PARTY_BACKENDS) target_include_directories(bench_rmq - PRIVATE ${sdsl_lite_SOURCE_DIR}/include) + PRIVATE ${sdsl_lite_SOURCE_DIR}/include) target_compile_definitions(bench_rmq - PRIVATE PIXIE_THIRD_PARTY_BENCHMARKS) - endif() + PRIVATE PIXIE_THIRD_PARTY_BENCHMARKS) + endif () - if(PIXIE_THIRD_PARTY_BACKENDS) + if (PIXIE_THIRD_PARTY_BACKENDS) add_executable(bench_rmm_sdsl - src/benchmarks/bench_rmm_sdsl.cpp) + src/benchmarks/bench_rmm_sdsl.cpp) target_include_directories(bench_rmm_sdsl - PUBLIC include - PRIVATE ${sdsl_lite_SOURCE_DIR}/include) + PUBLIC include + PRIVATE ${sdsl_lite_SOURCE_DIR}/include) target_link_libraries(bench_rmm_sdsl - PRIVATE + PRIVATE benchmark ${PIXIE_DIAGNOSTICS_LIBS}) - endif() + endif () add_executable(louds_tree_benchmarks - src/benchmarks/louds_tree_benchmarks.cpp) + src/benchmarks/louds_tree_benchmarks.cpp) target_include_directories(louds_tree_benchmarks - PUBLIC include) + PUBLIC include) target_link_libraries(louds_tree_benchmarks - benchmark - benchmark_main - ${PIXIE_DIAGNOSTICS_LIBS}) + benchmark + benchmark_main + mio + ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(wavelet_tree_benchmarks src/benchmarks/wavelet_tree_benchmarks.cpp) @@ -300,6 +323,7 @@ if(PIXIE_BENCHMARKS) target_link_libraries(wavelet_tree_benchmarks benchmark benchmark_main + mio ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(bp_tree_benchmarks @@ -309,11 +333,12 @@ if(PIXIE_BENCHMARKS) target_link_libraries(bp_tree_benchmarks benchmark benchmark_main + mio ${PIXIE_DIAGNOSTICS_LIBS}) - if(PIXIE_THIRD_PARTY_BACKENDS) + if (PIXIE_THIRD_PARTY_BACKENDS) target_include_directories(bp_tree_benchmarks PRIVATE ${sdsl_lite_SOURCE_DIR}/include) - endif() + endif () add_executable(dfuds_tree_benchmarks src/benchmarks/dfuds_tree_benchmarks.cpp) @@ -322,62 +347,65 @@ if(PIXIE_BENCHMARKS) target_link_libraries(dfuds_tree_benchmarks benchmark benchmark_main + mio ${PIXIE_DIAGNOSTICS_LIBS}) - if(PIXIE_THIRD_PARTY_BACKENDS) + if (PIXIE_THIRD_PARTY_BACKENDS) target_include_directories(dfuds_tree_benchmarks PRIVATE ${sdsl_lite_SOURCE_DIR}/include) - endif() + endif () add_executable(alignment_comparison - src/benchmarks/alignment_comparison.cpp) + src/benchmarks/alignment_comparison.cpp) target_include_directories(alignment_comparison - PUBLIC include) + PUBLIC include) target_link_libraries(alignment_comparison - benchmark - benchmark_main - ${PIXIE_DIAGNOSTICS_LIBS}) + benchmark + benchmark_main + mio + ${PIXIE_DIAGNOSTICS_LIBS}) add_executable(excess_positions_benchmarks - src/benchmarks/excess_positions_benchmarks.cpp) + src/benchmarks/excess_positions_benchmarks.cpp) target_include_directories(excess_positions_benchmarks - PUBLIC include) + PUBLIC include) target_link_libraries(excess_positions_benchmarks - benchmark - benchmark_main - ${PIXIE_DIAGNOSTICS_LIBS}) -endif() + benchmark + benchmark_main + mio + ${PIXIE_DIAGNOSTICS_LIBS}) +endif () # --------------------------------------------------------------------------- # Documentation (Doxygen) # --------------------------------------------------------------------------- -if(PIXIE_DOCS) +if (PIXIE_DOCS) find_package(Doxygen REQUIRED) FetchContent_Declare( - doxygen-awesome-css - URL https://github.com/jothepro/doxygen-awesome-css/archive/refs/heads/main.zip + doxygen-awesome-css + URL https://github.com/jothepro/doxygen-awesome-css/archive/refs/heads/main.zip ) FetchContent_MakeAvailable(doxygen-awesome-css) FetchContent_GetProperties(doxygen-awesome-css SOURCE_DIR AWESOME_CSS_DIR) set(PIXIE_DOC_INPUT - "${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_SOURCE_DIR}/README.md") - if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/docs/benchmark_results.md") + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/docs/benchmark_results.md") list(APPEND PIXIE_DOC_INPUT - "${CMAKE_CURRENT_SOURCE_DIR}/src/docs/benchmark_results.md") - endif() + "${CMAKE_CURRENT_SOURCE_DIR}/src/docs/benchmark_results.md") + endif () string(JOIN " \\\n " PIXIE_DOC_INPUT - ${PIXIE_DOC_INPUT}) + ${PIXIE_DOC_INPUT}) set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/src/docs/Doxyfile.in) set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/docs/Doxyfile) configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY) add_custom_target(docs - COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating API documentation with Doxygen" - VERBATIM) -endif() + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" + VERBATIM) +endif () diff --git a/CMakePresets.json b/CMakePresets.json index 5bec034..0e4b162 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -118,7 +118,8 @@ "louds_tree_tests", "bp_tree_tests", "dfuds_tree_tests", - "excess_positions_tests" + "excess_positions_tests", + "wavelet_tree_tests" ] }, { @@ -133,7 +134,8 @@ "louds_tree_tests", "bp_tree_tests", "dfuds_tree_tests", - "excess_positions_tests" + "excess_positions_tests", + "wavelet_tree_tests" ] }, { @@ -148,7 +150,8 @@ "bp_tree_benchmarks", "dfuds_tree_benchmarks", "alignment_comparison", - "excess_positions_benchmarks" + "excess_positions_benchmarks", + "wavelet_tree_benchmarks" ] }, { @@ -164,7 +167,8 @@ "bp_tree_benchmarks", "dfuds_tree_benchmarks", "alignment_comparison", - "excess_positions_benchmarks" + "excess_positions_benchmarks", + "wavelet_tree_benchmarks" ] }, { @@ -179,7 +183,8 @@ "bp_tree_benchmarks", "dfuds_tree_benchmarks", "alignment_comparison", - "excess_positions_benchmarks" + "excess_positions_benchmarks", + "wavelet_tree_benchmarks" ] }, { @@ -202,7 +207,8 @@ "louds_tree_tests", "bp_tree_tests", "dfuds_tree_tests", - "excess_positions_tests" + "excess_positions_tests", + "wavelet_tree_tests" ] }, { @@ -217,7 +223,8 @@ "louds_tree_tests", "bp_tree_tests", "dfuds_tree_tests", - "excess_positions_tests" + "excess_positions_tests", + "wavelet_tree_tests" ] } ] diff --git a/include/pixie/bit_stream.h b/include/pixie/bit_stream.h new file mode 100644 index 0000000..f5fa72e --- /dev/null +++ b/include/pixie/bit_stream.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace pixie { + +class OutputBitStream { + private: + size_t size_; + std::vector data_; + + public: + OutputBitStream() : size_(0) {} + + /** + * @brief Writes one bit to the stream + */ + OutputBitStream& operator<<(bool bit) { + if (size_ % 64 == 0) { + data_.push_back(bit); + } else if (bit) { + data_.back() |= 1ull << (size_ % 64); + } + size_++; + return *this; + } + + /** + * @brief Writes bits of the integral number to the stream in little-endian + */ + template + OutputBitStream& operator<<(T bits) { + using UT = std::make_unsigned_t; + UT ubits = static_cast(bits); + constexpr size_t length = sizeof(T) * 8; + static_assert(length <= 64); + if (size_ % 64 == 0) { + data_.push_back(ubits); + } else { + const size_t prefix = std::min(length, 64 - (size_ % 64)); + data_.back() |= static_cast(ubits & ((1ull << prefix) - 1)) + << (size_ % 64); + if (prefix < length) { + data_.push_back(ubits >> prefix); + } + } + size_ += length; + return *this; + } + + /** + * @brief Returns the number of written bits + * + */ + size_t size() const { return size_; } + + /** + * @brief Reserves memory for "size" bits + * + */ + void reserve(size_t size) { data_.reserve((size + 63) / 64); } + + /** + * @brief Moves vector containing written bits. There must be no operator<< + * after extract() + */ + std::vector extract() { return std::move(data_); } +}; + +} // namespace pixie diff --git a/include/pixie/bitvector.h b/include/pixie/bitvector.h index ab6427f..bbe3cd8 100644 --- a/include/pixie/bitvector.h +++ b/include/pixie/bitvector.h @@ -1,7 +1,9 @@ #pragma once +#include #include #include +#include #include #include @@ -64,7 +66,8 @@ namespace pixie { * not interleave data and index, favoring simpler scans. Rank metadata and * enabled select samples are built in one scan over the source words. */ -class BitVector { +template +class BitVectorBase { public: /** * @brief Select directions to index during construction. @@ -86,12 +89,12 @@ class BitVector { constexpr static size_t kBlocksPerSuperBlock = 128; constexpr static size_t kSelectSampleFrequency = 16384; - alignas(64) uint64_t delta_super[8]; - alignas(64) uint16_t delta_basic[32]; + alignas(64) uint64_t delta_super[8]{}; + alignas(64) uint16_t delta_basic[32]{}; - AlignedStorage super_block_rank_; // 64-bit global prefix sums - AlignedStorage basic_block_rank_; // 16-bit local prefix sums - AlignedStorage select_samples_; // 64-bit global positions + Storage super_block_rank_; // 64-bit global prefix sums + Storage basic_block_rank_; // 16-bit local prefix sums + Storage select_samples_; // 64-bit global positions size_t num_bits_{}; size_t padded_size_{}; size_t max_rank_{}; @@ -628,11 +631,11 @@ class BitVector { } public: - BitVector() = default; - BitVector(const BitVector&) = default; - BitVector(BitVector&&) noexcept = default; - BitVector& operator=(const BitVector&) = default; - BitVector& operator=(BitVector&&) noexcept = default; + BitVectorBase() = default; + BitVectorBase(const BitVectorBase&) = default; + BitVectorBase(BitVectorBase&&) noexcept = default; + BitVectorBase& operator=(const BitVectorBase&) = default; + BitVectorBase& operator=(BitVectorBase&&) noexcept = default; #ifdef PIXIE_DIAGNOSTICS struct DiagnosticsBytes { @@ -693,10 +696,10 @@ class BitVector { * @param one_count Optional exact number of 1-bits. When supplied, * construction can allocate select sample storage exactly. */ - explicit BitVector(std::span bit_vector, - size_t num_bits, - SelectSupport select_support = SelectSupport::kBoth, - std::optional one_count = std::nullopt) + explicit BitVectorBase(std::span bit_vector, + size_t num_bits, + SelectSupport select_support = SelectSupport::kBoth, + std::optional one_count = std::nullopt) : num_bits_(std::min(num_bits, bit_vector.size() * kWordSize)), padded_size_(((num_bits_ + kWordSize - 1) / kWordSize) * kWordSize), bits_(bit_vector) { @@ -846,8 +849,60 @@ class BitVector { return result; } + + void serialize(pixie::OutputBitStream& bs) const { + bs << num_bits_ << padded_size_ << max_rank_ << select1_sample_begin_ + << select1_sample_count_ << select0_sample_begin_ + << select0_sample_count_ << static_cast(select_support_) + << static_cast(select0_samples_reversed_); + for (const uint64_t delta : delta_super) { + bs << delta; + } + for (const uint16_t delta : delta_basic) { + bs << delta; + } + super_block_rank_.serialize(bs); + basic_block_rank_.serialize(bs); + select_samples_.serialize(bs); + } + + static BitVectorBase deserialize( + std::span source_bits, + std::span& data) { + BitVectorBase result; + result.bits_ = source_bits; + auto read = [&data](auto& value) { + constexpr size_t length = sizeof(value); + std::memcpy(&value, data.data(), length); + data = data.subspan(length); + }; + read(result.num_bits_); + read(result.padded_size_); + read(result.max_rank_); + read(result.select1_sample_begin_); + read(result.select1_sample_count_); + read(result.select0_sample_begin_); + read(result.select0_sample_count_); + uint32_t buf; + read(buf); + result.select_support_ = static_cast(buf); + read(buf); + result.select0_samples_reversed_ = static_cast(buf); + for (uint64_t& delta : result.delta_super) { + read(delta); + } + for (uint16_t& delta : result.delta_basic) { + read(delta); + } + result.super_block_rank_ = MmapViewStorage::deserialize(data); + result.basic_block_rank_ = MmapViewStorage::deserialize(data); + result.select_samples_ = MmapViewStorage::deserialize(data); + return result; + } }; +typedef BitVectorBase BitVector; + /** * @brief Interleaved, owning bit vector with rank and select. * diff --git a/include/pixie/cache_line.h b/include/pixie/cache_line.h index f027df4..596342e 100644 --- a/include/pixie/cache_line.h +++ b/include/pixie/cache_line.h @@ -6,6 +6,8 @@ #include #include +#include "bit_stream.h" + namespace pixie { inline constexpr std::size_t kAlignedStorageLineBytes = 64; @@ -137,6 +139,16 @@ class AlignedStorage { reinterpret_cast(data_.data()), data_.size() * kAlignedStorageLineWords16); } + + /** @brief Writes bit representation of data to OutputBitStream */ + void serialize(pixie::OutputBitStream& bs) const { + bs << data_.size() * sizeof(CacheLine); + for (const CacheLine& line : data_) { + for (const std::byte& byte : line.data) { + bs << static_cast(byte); + } + } + } }; } // namespace pixie diff --git a/include/pixie/mmap_storage.h b/include/pixie/mmap_storage.h new file mode 100644 index 0000000..67f7c52 --- /dev/null +++ b/include/pixie/mmap_storage.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "bit_stream.h" + +namespace pixie { + +class MmapFile { + private: + mio::mmap_source mmap_; + + public: + explicit MmapFile(const std::filesystem::path& path) : mmap_(path.string()) { + if (!mmap_.is_open()) { + throw std::runtime_error("Failed to mmap file"); + } + } + + MmapFile(const MmapFile&) = delete; + MmapFile& operator=(const MmapFile&) = delete; + + MmapFile(MmapFile&&) noexcept = default; + MmapFile& operator=(MmapFile&&) noexcept = default; + + std::span AsBytes() const { + return {reinterpret_cast(mmap_.data()), mmap_.size()}; + } + + size_t Size() const noexcept { return mmap_.size(); } +}; + +class MmapViewStorage { + private: + std::span data_; + + public: + MmapViewStorage() = default; + explicit MmapViewStorage(std::span data) : data_(data) {} + + void resize(size_t) { + throw std::logic_error("Cannot resize read-only mmap storage"); + } + + std::span AsConst64BitInts() const { + return {reinterpret_cast(data_.data()), data_.size() / 8}; + } + + std::span AsConst16BitInts() const { + return {reinterpret_cast(data_.data()), data_.size() / 2}; + } + + std::span AsConstBytes() const { return data_; } + + std::span As64BitInts() { + throw std::logic_error("Read-only storage"); + } + std::span As16BitInts() { + throw std::logic_error("Read-only storage"); + } + + /** @brief Writes bit representation of data to OutputBitStream */ + void serialize(pixie::OutputBitStream& bs) const { + bs << data_.size(); + for (const std::byte byte : data_) { + bs << static_cast(byte); + } + } + + static MmapViewStorage deserialize(std::span& data) { + MmapViewStorage result; + constexpr size_t length = sizeof(size_t); + size_t size; + std::memcpy(&size, data.data(), length); + result.data_ = data.subspan(length, size); + data = data.subspan(length + size); + return result; + }; +}; + +} // namespace pixie diff --git a/include/pixie/wavelet_tree.h b/include/pixie/wavelet_tree.h index f9d2bda..9b17ce2 100644 --- a/include/pixie/wavelet_tree.h +++ b/include/pixie/wavelet_tree.h @@ -1,28 +1,94 @@ #pragma once -#include - +#include +#include +#include #include +#include +#include +#include +#include + +#include "pixie/bitvector.h" namespace pixie { -enum WaveletTreeBuildType { Standard, Huffman }; +enum class WaveletTreeBuildType { Standard, Huffman }; -class WaveletTree { +template +class WaveletTreeBase { + private: using node_index_t = size_t; + static constexpr node_index_t npos = std::numeric_limits::max(); + + struct PreWaveletNode { + node_index_t parent = npos; + node_index_t left_child = npos; + node_index_t right_child = npos; + uint64_t middle; + OutputBitStream stream; + explicit PreWaveletNode(uint64_t middle) : middle(middle) {} + }; + + /** + * @brief Node of the wavelet tree + * @details + * Node with its bitvector representing division of characters corresponding + * to this node: 0 - left, 1 - right, in their original order. It also has + * indices of its children and parent for routing both up-down and bottom-up. + * + */ struct WaveletNode { - static constexpr node_index_t kNil = - std::numeric_limits::max(); - node_index_t parent = kNil, left_child = kNil, right_child = kNil; + node_index_t parent, left_child, right_child; uint64_t middle; - std::vector bit_vector_; - BitVector data; - WaveletNode(uint64_t middle, - std::vector&& bit_vector, - size_t num_bits) - : middle(middle), - bit_vector_(std::move(bit_vector)), - data(std::span{bit_vector_}, num_bits) {} + Storage bit_vector_data; + BitVectorBase data; + + /** @brief Manually turns std::vector into AlignedStorage */ + static AlignedStorage align(std::vector&& data) { + AlignedStorage result(data.size() * 64); + auto view = result.As64BitInts(); + std::copy(data.begin(), data.end(), view.begin()); + return std::move(result); + } + + WaveletNode() = default; + + WaveletNode(PreWaveletNode&& node) + requires(std::same_as) + : parent(node.parent), + left_child(node.left_child), + right_child(node.right_child), + middle(node.middle), + bit_vector_data(std::move(align(node.stream.extract()))), + data(bit_vector_data.AsConst64BitInts(), node.stream.size()) {} + + /** @brief Writes a node serialization to the bit stream */ + void serialize(pixie::OutputBitStream& bs) const { + bs << parent << left_child << right_child << middle; + bit_vector_data.serialize(bs); + data.serialize(bs); + } + + /** @brief Constructs wavelet node out of the raw bytes */ + static WaveletNode deserialize(std::span& data) + requires(std::same_as) + { + WaveletNode result; + auto read = [&data](auto& value) { + constexpr size_t length = sizeof(value); + std::memcpy(&value, data.data(), length); + data = data.subspan(length); + }; + read(result.parent); + read(result.left_child); + read(result.right_child); + read(result.middle); + result.bit_vector_data = MmapViewStorage::deserialize(data); + result.data = BitVectorBase::deserialize( + result.bit_vector_data.AsConst64BitInts(), data); + return result; + } }; size_t alphabet_size_, data_size_; @@ -31,78 +97,100 @@ class WaveletTree { std::vector leaves_; std::vector permutation_, inverse_permutation_; - node_index_t BuildNode( - size_t begin, - size_t end, - std::span data, - node_index_t parent, - const std::function& get_middle = [](auto) { - return -1ull; - }) { + /** + * @brief Recursive building of the nodes + * + * @tparam F get_middle function typename + * @param begin Begin of the characters segment + * @param end End of the characters segment + * @param parent Node index of the parent node + * @param get_middle Offset of separating cut from the segment beginning if + * there is precomputed tree structure and npos otherwise + * @param prefix_sum Span with i-th value equal to number of characters less + * than i in the original data + * @param nodes Resulting vector of PreWaveletNodes + * @return Index of the node built + * + */ + template + node_index_t build_node(size_t begin, + size_t end, + node_index_t parent, + const F& get_middle, + std::span prefix_sum, + std::vector& nodes) + requires(std::same_as) + { if (end - begin == 1) { leaves_[begin] = parent; - return WaveletNode::kNil; + return npos; } - if (data.empty()) { - for (size_t symb = begin; symb < end; symb++) { - leaves_[symb] = parent; + if (prefix_sum[end] == prefix_sum[begin]) { + for (size_t symbol = begin; symbol < end; symbol++) { + leaves_[symbol] = parent; } - return WaveletNode::kNil; + return npos; } - node_index_t result = nodes_.size(); + node_index_t result = nodes.size(); size_t middle = get_middle(result); - middle = begin + (middle == -1ull ? (end - begin) / 2 : middle); - std::vector bit_vector; - bit_vector.resize((data.size() + 63) / 64); - for (size_t i = 0; i < data.size(); i++) { - if (permutation_[data[i]] >= middle) { - bit_vector[i / 64] |= 1ull << (i % 64); - } - } + middle = begin + (middle == npos ? (end - begin) / 2 : middle); - nodes_.emplace_back(middle, std::move(bit_vector), data.size()); - auto cut = std::stable_partition( - data.begin(), data.end(), - [middle, this](uint64_t x) { return permutation_[x] < middle; }); - nodes_[result].parent = parent; - nodes_[result].left_child = BuildNode( - begin, middle, std::span{data.begin(), cut}, result, get_middle); - nodes_[result].right_child = - BuildNode(middle, end, std::span{cut, data.end()}, result, get_middle); + nodes.emplace_back(middle); + nodes[result].stream.reserve(prefix_sum[end] - prefix_sum[begin]); + nodes[result].parent = parent; + nodes[result].left_child = + build_node(begin, middle, result, get_middle, prefix_sum, nodes); + nodes[result].right_child = + build_node(middle, end, result, get_middle, prefix_sum, nodes); return result; } - void copySegmentContent(node_index_t node, - size_t begin, - size_t end, - std::span dst, - std::span tmp) const { + /** + * @brief Recursively copies segment of the original data corresponding to the + * node + * + * @param node Index of the node + * @param begin Begin of the segment + * @param end End of the segment + * @param dst Destination of the copy + * @param tmp Temporary memory buffer + * + * @details + * Copies left and right childs segments to the tmp and then builds up target + * segment in the dst + * + */ + void copy_segment_content(node_index_t node, + size_t begin, + size_t end, + std::span dst, + std::span tmp) const { if (begin == end) { return; } - size_t rank = nodes_[node].data.rank(begin), rank0 = begin - rank; - size_t right = nodes_[node].data.rank(end) - rank, - left = (end - begin) - right; + const size_t rank = nodes_[node].data.rank(begin), rank0 = begin - rank; + const size_t right = nodes_[node].data.rank(end) - rank, + left = (end - begin) - right; - if (nodes_[node].left_child == WaveletNode::kNil) { - std::fill(tmp.begin(), tmp.begin() + static_cast(left), - inverse_permutation_[nodes_[node].middle - 1]); + if (nodes_[node].left_child == npos) { + std::fill_n(tmp.begin(), static_cast(left), + inverse_permutation_[nodes_[node].middle - 1]); } else { - copySegmentContent(nodes_[node].left_child, rank0, rank0 + left, - tmp.subspan(0, left), dst.subspan(0, left)); + copy_segment_content(nodes_[node].left_child, rank0, rank0 + left, + tmp.subspan(0, left), dst.subspan(0, left)); } - if (nodes_[node].right_child == WaveletNode::kNil) { + if (nodes_[node].right_child == npos) { std::fill(tmp.begin() + static_cast(left), tmp.end(), inverse_permutation_[nodes_[node].middle]); } else { - copySegmentContent(nodes_[node].right_child, rank, rank + right, - tmp.subspan(left, right), dst.subspan(left, right)); + copy_segment_content(nodes_[node].right_child, rank, rank + right, + tmp.subspan(left, right), dst.subspan(left, right)); } size_t j = 0, k = left; - const auto& bit_vector = nodes_[node].bit_vector_; + const auto& bit_vector = nodes_[node].bit_vector_data.AsConst64BitInts(); for (size_t i = begin; i < end; i++) { if ((bit_vector[i / 64] >> (i % 64)) & 1) { dst[i - begin] = tmp[k++]; @@ -112,26 +200,46 @@ class WaveletTree { } } + WaveletTreeBase() = default; + public: - WaveletTree(size_t alphabet_size, - std::span data, - WaveletTreeBuildType build_type = WaveletTreeBuildType::Standard) + /** + * @param alphabet_size Size of the alphabet + * @param data Original text. Its characters are from the + * range [0, alphabet_size) + * @param build_type Either Standard or Huffman. This effects on how the + * wavelet tree builds: like segment tree on trivially sorted characters or + * like in Huffman algorithm + * + * @details + * Standard: Just calls build_node + * Huffman: Reorders characters with respect to Huffman algorithm and then + * calls build_node with specific get_middle function + * + */ + WaveletTreeBase( + size_t alphabet_size, + std::span data, + const WaveletTreeBuildType build_type = WaveletTreeBuildType::Standard) + requires(std::same_as) : alphabet_size_(alphabet_size), data_size_(data.size()), - leaves_(alphabet_size_, WaveletNode::kNil) { + leaves_(alphabet_size_, npos) { if (alphabet_size == 0) { - root_ = WaveletNode::kNil; + root_ = npos; return; } - nodes_.reserve(alphabet_size_); + std::vector nodes; + nodes.reserve(alphabet_size_); + std::vector nodes_structure; + nodes_structure.reserve(alphabet_size_); + if (build_type == WaveletTreeBuildType::Standard) { permutation_.resize(alphabet_size); inverse_permutation_.resize(alphabet_size); std::iota(permutation_.begin(), permutation_.end(), 0); std::iota(inverse_permutation_.begin(), inverse_permutation_.end(), 0); - - std::vector data_copy(data.begin(), data.end()); - root_ = BuildNode(0, alphabet_size_, data_copy, WaveletNode::kNil); + nodes_structure.resize(alphabet_size_, npos); } else { struct Node { size_t size, left, right; @@ -157,7 +265,6 @@ class WaveletTree { queue.emplace(huffman_nodes.back().size, huffman_nodes.size() - 1); } - std::vector nodes_structure; std::function enumerate = [&](size_t index) -> size_t { const auto& [size, left, right] = huffman_nodes[index]; if (left == 0 || right == 0) { @@ -178,23 +285,55 @@ class WaveletTree { }; permutation_.resize(alphabet_size_); - nodes_structure.reserve(alphabet_size_); inverse_permutation_.reserve(alphabet_size_); enumerate(huffman_nodes.size() - 1); + } + + std::vector prefix_sum(alphabet_size + 1); + for (auto symbol : data) { + prefix_sum[permutation_[symbol] + 1]++; + } + for (size_t i = 0; i < alphabet_size_; i++) { + prefix_sum[i + 1] += prefix_sum[i]; + } - std::vector data_copy(data.begin(), data.end()); - root_ = - BuildNode(0, alphabet_size_, data_copy, WaveletNode::kNil, - [&](node_index_t node) { return nodes_structure[node]; }); + root_ = build_node( + 0, alphabet_size_, npos, + [&](node_index_t node) { return nodes_structure[node]; }, prefix_sum, + nodes); + for (auto symbol : data) { + auto index = permutation_[symbol]; + for (node_index_t current = root_; current != npos;) { + auto& node = nodes[current]; + bool go_right = index >= node.middle; + node.stream << go_right; + if (go_right) { + current = node.right_child; + } else { + current = node.left_child; + } + } + } + nodes_.reserve(nodes.size()); + for (auto& node : nodes) { + nodes_.emplace_back(std::move(node)); } } + /** + * @brief Rank of specified symbol up to position pos (exclusive) + * + * @param symbol The character that the query is about + * @param pos Character index in [0, size()] + * @return Number of specified symbols in [0, pos) + * + */ size_t rank(uint64_t symbol, size_t pos) const { if (symbol >= alphabet_size_) [[unlikely]] { return 0; } symbol = permutation_[symbol]; - for (node_index_t current = root_; current != WaveletNode::kNil;) { + for (node_index_t current = root_; current != npos;) { const WaveletNode& node = nodes_[current]; if (symbol < node.middle) { pos = node.data.rank0(pos); @@ -207,13 +346,21 @@ class WaveletTree { return pos; } + /** + * @brief Select the position of the rank-th specified symbol (1-indexed) + * + * @param symbol The character that the query is about + * @param rank 1-indexed rank of specified symbol to select + * @return Symbol index, or size() if rank is out of range + * + */ size_t select(uint64_t symbol, size_t rank) const { if (symbol >= alphabet_size_ || data_size_ == 0) [[unlikely]] { return data_size_; } symbol = permutation_[symbol]; node_index_t current = leaves_[symbol]; - for (; current != WaveletNode::kNil; current = nodes_[current].parent) { + for (; current != npos; current = nodes_[current].parent) { const WaveletNode& node = nodes_[current]; if (symbol < node.middle) { rank = node.data.select0(rank) + 1; @@ -224,18 +371,83 @@ class WaveletTree { return rank - 1; } - std::vector getSegment(size_t begin, size_t end) const { - if (alphabet_size_ == 0 || data_size_ == 0) [[unlikely]] { + /** + * @brief Accumulates the original data segment + * + * @param begin Begin of the segment + * @param end End of the segment + * @return Queried segment of data + * + */ + std::vector get_segment(size_t begin, size_t end) const { + if (alphabet_size_ == 0 || data_size_ == 0 || begin >= end) [[unlikely]] { return {}; } auto length = static_cast(end - begin); std::vector result(2 * length); - copySegmentContent(root_, begin, end, - std::span{result.begin(), result.begin() + length}, - std::span{result.begin() + length, result.end()}); + copy_segment_content(root_, begin, end, + std::span{result.begin(), result.begin() + length}, + std::span{result.begin() + length, result.end()}); result.resize(length); return result; } + + /** + * @return Returns the number of characters in data + * + */ + size_t size() { return data_size_; } + + /** + * @brief Writes a wavelet tree serialization to the bit stream + * + */ + void serialize(pixie::OutputBitStream& bs) const { + bs << alphabet_size_ << data_size_ << root_ << nodes_.size(); + for (const WaveletNode& node : nodes_) { + node.serialize(bs); + } + for (const node_index_t leaf : leaves_) { + bs << leaf; + } + for (const size_t idx : permutation_) { + bs << idx; + } + } + + static WaveletTreeBase deserialize( + std::span& data) { + WaveletTreeBase result; + auto read = [&data](auto& value) { + constexpr size_t length = sizeof(value); + std::memcpy(&value, data.data(), length); + data = data.subspan(length); + }; + read(result.alphabet_size_); + read(result.data_size_); + read(result.root_); + size_t size; + read(size); + result.nodes_.resize(size); + for (auto& node : result.nodes_) { + node = WaveletNode::deserialize(data); + } + result.leaves_.resize(result.alphabet_size_); + for (node_index_t& leaf : result.leaves_) { + read(leaf); + } + result.permutation_.resize(result.alphabet_size_); + for (size_t& index : result.permutation_) { + read(index); + } + result.inverse_permutation_.resize(result.alphabet_size_); + for (size_t i = 0; i < result.alphabet_size_; i++) { + result.inverse_permutation_[result.permutation_[i]] = i; + } + return result; + } }; +typedef WaveletTreeBase WaveletTree; + } // namespace pixie diff --git a/src/benchmarks/wavelet_tree_benchmarks.cpp b/src/benchmarks/wavelet_tree_benchmarks.cpp index b25d175..1a35142 100644 --- a/src/benchmarks/wavelet_tree_benchmarks.cpp +++ b/src/benchmarks/wavelet_tree_benchmarks.cpp @@ -64,6 +64,116 @@ static void BM_WaveletTreeRank(benchmark::State& state) { } } +static void BM_WaveletTreeMmapSelect(benchmark::State& state) { + size_t data_size = state.range(0), alphabet_size = 1024, query = data_size; + std::mt19937_64 rng(239); + + for (auto _ : state) { + state.PauseTiming(); + + std::vector data = + generate_random_data(data_size, alphabet_size, rng); + std::vector query_symbol = + generate_random_data(query, alphabet_size, rng); + std::vector count(alphabet_size), query_pos(query); + for (auto symb : data) { + count[symb]++; + } + for (size_t i = 0; i < query; i++) { + query_pos[i] = 1 + std::uniform_int_distribution( + 0, count[query_symbol[i]])(rng); + } + + WaveletTree orig_tree(alphabet_size, data); + pixie::OutputBitStream bs; + orig_tree.serialize(bs); + std::vector serialized_data = bs.extract(); + std::span byte_span( + reinterpret_cast(serialized_data.data()), + serialized_data.size() * sizeof(uint64_t)); + + state.ResumeTiming(); + + auto mmap_tree = + pixie::WaveletTreeBase::deserialize(byte_span); + benchmark::DoNotOptimize(mmap_tree); + + for (size_t i = 0; i < query; i++) { + size_t select = mmap_tree.select(query_symbol[i], query_pos[i]); + benchmark::DoNotOptimize(select); + } + } +} + +static void BM_WaveletTreeMmapRank(benchmark::State& state) { + size_t data_size = state.range(0), alphabet_size = 1024, query = data_size; + std::mt19937_64 rng(239); + + for (auto _ : state) { + state.PauseTiming(); + + std::vector data = + generate_random_data(data_size, alphabet_size, rng); + std::vector query_symbol = + generate_random_data(query, alphabet_size, rng), + query_pos = + generate_random_data(query, data_size + 1, rng); + + WaveletTree orig_tree(alphabet_size, data); + pixie::OutputBitStream bs; + orig_tree.serialize(bs); + std::vector serialized_data = bs.extract(); + std::span byte_span( + reinterpret_cast(serialized_data.data()), + serialized_data.size() * sizeof(uint64_t)); + + state.ResumeTiming(); + + auto mmap_tree = + pixie::WaveletTreeBase::deserialize(byte_span); + benchmark::DoNotOptimize(mmap_tree); + + for (size_t i = 0; i < query; i++) { + size_t rank = mmap_tree.rank(query_symbol[i], query_pos[i]); + benchmark::DoNotOptimize(rank); + } + } +} + +static void BM_WaveletTreeMmapSegment(benchmark::State& state) { + size_t data_size = state.range(0), alphabet_size = 1024, + query = data_size / 128, length = 128; + std::mt19937_64 rng(239); + + for (auto _ : state) { + state.PauseTiming(); + + std::vector data = + generate_random_data(data_size, alphabet_size, rng); + std::vector query_pos = + generate_random_data(query, data_size + 1 - length, rng); + + WaveletTree orig_tree(alphabet_size, data); + pixie::OutputBitStream bs; + orig_tree.serialize(bs); + std::vector serialized_data = bs.extract(); + std::span byte_span( + reinterpret_cast(serialized_data.data()), + serialized_data.size() * sizeof(uint64_t)); + + state.ResumeTiming(); + + auto mmap_tree = + pixie::WaveletTreeBase::deserialize(byte_span); + benchmark::DoNotOptimize(mmap_tree); + + for (size_t i = 0; i < query; i++) { + auto segment = mmap_tree.get_segment(query_pos[i], query_pos[i] + length); + benchmark::DoNotOptimize(segment); + } + } +} + BENCHMARK(BM_WaveletTreeSelect) ->ArgNames({"data_size"}) ->RangeMultiplier(2) @@ -87,3 +197,39 @@ BENCHMARK(BM_WaveletTreeRank) ->RangeMultiplier(2) ->Range(1ull << 18, 1ull << 26) ->Iterations(10); + +BENCHMARK(BM_WaveletTreeMmapSelect) + ->ArgNames({"data_size"}) + ->RangeMultiplier(2) + ->Range(1ull << 8, 1ull << 18) + ->Iterations(100); + +BENCHMARK(BM_WaveletTreeMmapSelect) + ->ArgNames({"data_size"}) + ->RangeMultiplier(2) + ->Range(1ull << 18, 1ull << 26) + ->Iterations(10); + +BENCHMARK(BM_WaveletTreeMmapRank) + ->ArgNames({"data_size"}) + ->RangeMultiplier(2) + ->Range(1ull << 8, 1ull << 18) + ->Iterations(100); + +BENCHMARK(BM_WaveletTreeMmapRank) + ->ArgNames({"data_size"}) + ->RangeMultiplier(2) + ->Range(1ull << 18, 1ull << 26) + ->Iterations(10); + +BENCHMARK(BM_WaveletTreeMmapSegment) + ->ArgNames({"data_size"}) + ->RangeMultiplier(2) + ->Range(1ull << 8, 1ull << 18) + ->Iterations(100); + +BENCHMARK(BM_WaveletTreeMmapSegment) + ->ArgNames({"data_size"}) + ->RangeMultiplier(2) + ->Range(1ull << 18, 1ull << 26) + ->Iterations(10); diff --git a/src/tests/wavelet_tree_tests.cpp b/src/tests/wavelet_tree_tests.cpp index d578c3b..e8a0fa1 100644 --- a/src/tests/wavelet_tree_tests.cpp +++ b/src/tests/wavelet_tree_tests.cpp @@ -55,7 +55,7 @@ TEST(WaveletTreeTest, BasicSegment) { for (size_t begin = 0; begin <= data_size; begin++) { for (size_t end = begin; end <= data_size; end++) { - auto segment = wavelet_tree.getSegment(begin, end); + auto segment = wavelet_tree.get_segment(begin, end); EXPECT_EQ(segment.size(), end - begin); for (size_t i = 0; i < end - begin; i++) { EXPECT_EQ(segment[i], data[begin + i]); @@ -135,7 +135,7 @@ TEST(WaveletTreeTest, SmokeSegment) { for (size_t begin = 0; begin <= data_size; begin++) { for (size_t end = begin; end <= data_size; end++) { - auto segment = wavelet_tree.getSegment(begin, end); + auto segment = wavelet_tree.get_segment(begin, end); EXPECT_EQ(segment.size(), end - begin); for (size_t i = 0; i < end - begin; i++) { EXPECT_EQ(segment[i], data[begin + i]); @@ -144,3 +144,47 @@ TEST(WaveletTreeTest, SmokeSegment) { } } } + +TEST(WaveletTreeTest, SerializationSmoke) { + size_t data_size = 4096, alphabet_size = 100; + + std::mt19937_64 rng(239); + std::vector data = + generate_random_data(data_size, alphabet_size, rng); + + for (auto build_type : {pixie::WaveletTreeBuildType::Standard, + pixie::WaveletTreeBuildType::Huffman}) { + WaveletTree orig_tree(alphabet_size, data, build_type); + + pixie::OutputBitStream bs; + orig_tree.serialize(bs); + std::vector serialized_data = bs.extract(); + + std::span byte_span( + reinterpret_cast(serialized_data.data()), + serialized_data.size() * sizeof(uint64_t)); + + auto mmap_tree = + pixie::WaveletTreeBase::deserialize(byte_span); + + for (size_t i = 0; i <= data_size; i += 16) { + uint64_t symb = data[i == data_size ? 0 : i]; + EXPECT_EQ(orig_tree.rank(symb, i), mmap_tree.rank(symb, i)); + } + + std::vector count(alphabet_size, 0); + for (auto symb : data) { + count[symb]++; + } + + for (uint64_t symb = 0; symb < alphabet_size; symb++) { + for (uint64_t rank = 1; rank <= count[symb]; rank++) { + EXPECT_EQ(orig_tree.select(symb, rank), mmap_tree.select(symb, rank)); + } + } + + auto orig_segment = orig_tree.get_segment(0, data_size); + auto mmap_segment = mmap_tree.get_segment(0, data_size); + EXPECT_EQ(orig_segment, mmap_segment); + } +}