diff --git a/.github/workflows/benchmarks-test.yml b/.github/workflows/benchmarks-test.yml new file mode 100644 index 0000000..c957c0c --- /dev/null +++ b/.github/workflows/benchmarks-test.yml @@ -0,0 +1,56 @@ +name: CMake Build and Run Benchmark Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Directory + run: mkdir build + + - name: Configure CMake + working-directory: ./build + run: cmake -DDISABLE_AVX512=ON -DENABLE_ADDRESS_SANITIZER=ON .. + + - name: Build Project + working-directory: ./build + run: make -j + + - name: Run Benchmark Tests + working-directory: ./build + run: ./benchmark_tests + + build-and-test-with-SDE: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Directory + run: mkdir build + + - name: Download and Unpack SDE + working-directory: ./build + run: | + wget https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz + tar xf sde-external-9.58.0-2025-06-16-lin.tar.xz + + - name: Configure CMake + working-directory: ./build + run: cmake -DENABLE_ADDRESS_SANITIZER=ON -DMARCH=icelake-client -DHAVE_STD_REGEX=ON .. + + - name: Build Project + working-directory: ./build + run: make -j + + - name: Run Benchmark Tests + working-directory: ./build + run: sde-external-9.58.0-2025-06-16-lin/sde64 -icl -emu-xinuse 0 -- ./benchmark_tests \ No newline at end of file diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 48fdabc..e05a98c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -1,4 +1,4 @@ -name: CMake Build and Test +name: CMake Build and Run Unittests on: push: @@ -18,7 +18,7 @@ jobs: - name: Configure CMake working-directory: ./build - run: cmake .. + run: cmake -DDISABLE_AVX512=ON -DENABLE_ADDRESS_SANITIZER=ON .. - name: Build Project working-directory: ./build @@ -45,7 +45,7 @@ jobs: - name: Configure CMake working-directory: ./build - run: cmake .. -DMARCH=icelake-client + run: cmake -DENABLE_ADDRESS_SANITIZER=ON -DMARCH=icelake-client -DHAVE_STD_REGEX=ON .. - name: Build Project working-directory: ./build diff --git a/CMakeLists.txt b/CMakeLists.txt index d24fde2..935f6cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,24 @@ cmake_minimum_required(VERSION 3.12) project(pixie) + set(MARCH "native" CACHE STRING "march compilier flag") set(CMAKE_CXX_FLAGS "-march=${MARCH}") +message(STATUS "MARCH is '${MARCH}'") + +option(DISABLE_AVX512 "Disable AVX512 instructions" OFF) +if(DISABLE_AVX512) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-avx512f") + message(STATUS "DISABLE_AVX512 is ON") +endif() + +option(ENABLE_ADDRESS_SANITIZER "Enable AddressSanitizer" OFF) +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() + include(FetchContent) FetchContent_Declare( googletest @@ -45,6 +61,17 @@ target_link_libraries(unittests gtest gtest_main) +add_executable(benchmark_tests + src/benchmark_tests.cpp) +target_include_directories(benchmark_tests + PUBLIC include + PUBLIC ${GOOGLETEST_SOURCE_DIR}/include +) +set_property(TARGET benchmark_tests PROPERTY CXX_STANDARD 20) +target_link_libraries(benchmark_tests + gtest + gtest_main) + add_executable(bench_rmm src/bench_rmm.cpp) target_include_directories(bench_rmm diff --git a/include/bits.h b/include/bits.h index 4f0bb25..8537e0b 100644 --- a/include/bits.h +++ b/include/bits.h @@ -84,7 +84,7 @@ uint64_t rank_512(const uint64_t* x, uint64_t count) { #else - uint64_t last_uint = count >> 6; + uint64_t last_uint = count < 512 ? count >> 6 : 8; uint64_t pop_val = 0; @@ -92,9 +92,9 @@ uint64_t rank_512(const uint64_t* x, uint64_t count) { pop_val += std::popcount(x[i]); } - uint64_t final = x[last_uint] & first_bits_mask(count & 63); - - pop_val += std::popcount(final); + pop_val += count < 512 + ? std::popcount(x[last_uint] & first_bits_mask(count & 63)) + : 0; return pop_val; #endif diff --git a/include/bitvector.h b/include/bitvector.h index 8abf686..117e00a 100644 --- a/include/bitvector.h +++ b/include/bitvector.h @@ -355,7 +355,7 @@ class BitVectorInterleaved { offset_size_ -= num_bits; return result; } - uint64_t next = bits_[iterator_64_++]; + uint64_t next = iterator_64_ < bits_.size() ? bits_[iterator_64_++] : 0; result ^= (next & first_bits_mask(num_bits - offset_size_)) << offset_size_; offset_bits_ = (num_bits - offset_size_ == 64) diff --git a/src/benchmark_tests.cpp b/src/benchmark_tests.cpp new file mode 100644 index 0000000..1233a3c --- /dev/null +++ b/src/benchmark_tests.cpp @@ -0,0 +1,71 @@ +#include + +#include +#include + +#include "bits.h" +#include "bitvector.h" + +using pixie::BitVector; +using pixie::BitVectorInterleaved; + +TEST(BitVectorBenchmarkTest, SelectNonInterleaved10PercentFill) { + for (size_t n = 4; n <= (1ull << 34); n <<= 2) { + std::mt19937_64 rng(42); + + std::vector bits(((8 + n / 64) / 8) * 8); + size_t num_ones = n * 0.1; + for (int i = 0; i < num_ones; i++) { + uint64_t pos = rng() % n; + bits[pos / 64] |= (1ULL << pos % 64); + } + + pixie::BitVector bv(bits, n); + + auto max_rank = bv.rank(bv.size()) + 1; + for (int i = 0; i < 100000; i++) { + uint64_t rank = rng() % max_rank; + bv.select(rank); + } + } +} + +TEST(BitVectorBenchmarkTest, SelectNonInterleaved90PercentFill) { + for (size_t n = 4; n <= (1ull << 34); n <<= 2) { + std::mt19937_64 rng(42); + + std::vector bits(((8 + n / 64) / 8) * 8); + size_t num_ones = n * 0.9; + for (int i = 0; i < num_ones; i++) { + uint64_t pos = rng() % n; + bits[pos / 64] |= (1ULL << pos % 64); + } + + pixie::BitVector bv(bits, n); + + auto max_rank = bv.rank(bv.size()) + 1; + for (int i = 0; i < 100000; i++) { + uint64_t rank = rng() % max_rank; + bv.select(rank); + } + } +} + +TEST(BitVectorBenchmarkTest, SelectNonInterleaved) { + for (size_t n = 4; n <= (1ull << 34); n <<= 2) { + std::mt19937_64 rng(42); + + std::vector bits(((8 + n / 64) / 8) * 8); + for (auto& x : bits) { + x = rng(); + } + pixie::BitVector bv(bits, n); + + auto max_rank = bv.rank(bv.size()) + 1; + + for (int i = 0; i < 100000; i++) { + uint64_t rank = rng() % max_rank; + bv.select(rank); + } + } +} diff --git a/src/benchmarks.cpp b/src/benchmarks.cpp index 25daaa8..3bf3b66 100644 --- a/src/benchmarks.cpp +++ b/src/benchmarks.cpp @@ -8,7 +8,7 @@ static void BM_RankNonInterleaved(benchmark::State& state) { size_t n = state.range(0); std::mt19937_64 rng(42); - std::vector bits(1 + n / 64); + std::vector bits(((8 + n / 64) / 8) * 8); for (auto& x : bits) { x = rng(); } @@ -24,7 +24,7 @@ static void BM_RankInterleaved(benchmark::State& state) { size_t n = state.range(0); std::mt19937_64 rng(42); - std::vector bits(1 + n / 64); + std::vector bits(((8 + n / 64) / 8) * 8); for (auto& x : bits) { x = rng(); } @@ -40,13 +40,93 @@ static void BM_SelectNonInterleaved(benchmark::State& state) { size_t n = state.range(0); std::mt19937_64 rng(42); - std::vector bits(1 + n / 64); + std::vector bits(((8 + n / 64) / 8) * 8); for (auto& x : bits) { x = rng(); } pixie::BitVector bv(bits, n); - auto max_rank = bv.rank(bv.size()); + auto max_rank = bv.rank(bv.size()) + 1; + + for (auto _ : state) { + uint64_t rank = rng() % max_rank; + benchmark::DoNotOptimize(bv.select(rank)); + } +} + +static void BM_RankNonInterleaved10PercentFill(benchmark::State& state) { + size_t n = state.range(0); + std::mt19937_64 rng(42); + + std::vector bits(((8 + n / 64) / 8) * 8); + size_t num_ones = n * 0.1; + for (int i = 0; i < num_ones; i++) { + uint64_t pos = rng() % n; + bits[pos / 64] |= (1ULL << pos % 64); + } + + pixie::BitVector bv(bits, n); + + for (auto _ : state) { + uint64_t pos = rng() % n; + benchmark::DoNotOptimize(bv.rank(pos)); + } +} + +static void BM_SelectNonInterleaved10PercentFill(benchmark::State& state) { + size_t n = state.range(0); + std::mt19937_64 rng(42); + + std::vector bits(((8 + n / 64) / 8) * 8); + size_t num_ones = n * 0.1; + for (int i = 0; i < num_ones; i++) { + uint64_t pos = rng() % n; + bits[pos / 64] |= (1ULL << pos % 64); + } + + pixie::BitVector bv(bits, n); + + auto max_rank = bv.rank(bv.size()) + 1; + + for (auto _ : state) { + uint64_t rank = rng() % max_rank; + benchmark::DoNotOptimize(bv.select(rank)); + } +} + +static void BM_RankNonInterleaved90PercentFill(benchmark::State& state) { + size_t n = state.range(0); + std::mt19937_64 rng(42); + + std::vector bits(((8 + n / 64) / 8) * 8); + size_t num_ones = n * 0.9; + for (int i = 0; i < num_ones; i++) { + uint64_t pos = rng() % n; + bits[pos / 64] |= (1ULL << pos % 64); + } + + pixie::BitVector bv(bits, n); + + for (auto _ : state) { + uint64_t pos = rng() % n; + benchmark::DoNotOptimize(bv.rank(pos)); + } +} + +static void BM_SelectNonInterleaved90PercentFill(benchmark::State& state) { + size_t n = state.range(0); + std::mt19937_64 rng(42); + + std::vector bits(((8 + n / 64) / 8) * 8); + size_t num_ones = n * 0.9; + for (int i = 0; i < num_ones; i++) { + uint64_t pos = rng() % n; + bits[pos / 64] |= (1ULL << pos % 64); + } + + pixie::BitVector bv(bits, n); + + auto max_rank = bv.rank(bv.size()) + 1; for (auto _ : state) { uint64_t rank = rng() % max_rank; @@ -57,14 +137,41 @@ static void BM_SelectNonInterleaved(benchmark::State& state) { BENCHMARK(BM_RankNonInterleaved) ->ArgNames({"n"}) ->RangeMultiplier(4) - ->Range(8, 1ull << 34); + ->Range(8, 1ull << 34) + ->Iterations(100000); BENCHMARK(BM_RankInterleaved) ->ArgNames({"n"}) ->RangeMultiplier(4) - ->Range(8, 1ull << 34); + ->Range(8, 1ull << 34) + ->Iterations(100000); BENCHMARK(BM_SelectNonInterleaved) ->ArgNames({"n"}) ->RangeMultiplier(4) - ->Range(8, 1ull << 34); + ->Range(8, 1ull << 34) + ->Iterations(100000); + +BENCHMARK(BM_RankNonInterleaved10PercentFill) + ->ArgNames({"n"}) + ->RangeMultiplier(4) + ->Range(8, 1ull << 34) + ->Iterations(100000); + +BENCHMARK(BM_SelectNonInterleaved10PercentFill) + ->ArgNames({"n"}) + ->RangeMultiplier(4) + ->Range(8, 1ull << 34) + ->Iterations(100000); + +BENCHMARK(BM_RankNonInterleaved90PercentFill) + ->ArgNames({"n"}) + ->RangeMultiplier(4) + ->Range(8, 1ull << 34) + ->Iterations(100000); + +BENCHMARK(BM_SelectNonInterleaved90PercentFill) + ->ArgNames({"n"}) + ->RangeMultiplier(4) + ->Range(8, 1ull << 34) + ->Iterations(100000); diff --git a/src/unittests.cpp b/src/unittests.cpp index bc0e038..dddb0b7 100644 --- a/src/unittests.cpp +++ b/src/unittests.cpp @@ -40,11 +40,13 @@ TEST(Rank512, Random) { a[i] = rng(); } size_t rank = 0; - for (size_t i = 0; i <= 512; ++i) { + for (size_t i = 0; i < 512; ++i) { auto p = rank_512(a.data(), i); ASSERT_EQ(p, rank); rank += 1 & (a[i >> 6] >> (i & 63)); } + auto p = rank_512(a.data(), 512); + ASSERT_EQ(p, rank); } } @@ -142,7 +144,8 @@ TEST(Select512, RankCompativility) { } TEST(BitVectorTest, Basic) { - std::vector bits = {0b101010101}; + std::vector bits(8, 0); + bits[0] = 0b101010101; BitVector bv(bits, 9); EXPECT_EQ(bv.size(), 9); EXPECT_EQ(bv[0], 1); @@ -157,7 +160,9 @@ TEST(BitVectorTest, Basic) { } TEST(BitVectorTest, MultipleWords) { - std::vector bits = {0xAAAAAAAAAAAAAAAA, 0x5555555555555555}; + std::vector bits(8, 0); + bits[0] = 0xAAAAAAAAAAAAAAAA; + bits[1] = 0x5555555555555555; BitVector bv(bits, 128); EXPECT_EQ(bv.size(), 128); @@ -173,13 +178,15 @@ TEST(BitVectorTest, MultipleWords) { } TEST(BitVectorTest, ToString) { - std::vector bits = {0b10101010101}; + std::vector bits(8, 0); + bits[0] = 0b10101010101; BitVector bv(bits, 11); EXPECT_EQ(bv.to_string(), "10101010101"); } TEST(BitVectorTest, RankBasic) { - std::vector bits = {0b10110}; + std::vector bits(8, 0); + bits[0] = 0b10110; BitVector bv(bits, 5); EXPECT_EQ(bv.rank(0), 0); // No bits @@ -191,7 +198,7 @@ TEST(BitVectorTest, RankBasic) { } TEST(BitVectorTest, RankWithZeros) { - std::vector bits = {0}; + std::vector bits(8, 0); BitVector bv(bits, 5); for (size_t i = 0; i <= 5; i++) { @@ -200,7 +207,8 @@ TEST(BitVectorTest, RankWithZeros) { } TEST(BitVectorTest, SelectBasic) { - std::vector bits = {0b1100010110010110}; + std::vector bits(8, 0); + bits[0] = 0b1100010110010110; BitVector bv(bits, 5); EXPECT_EQ(bv.select(1), 1); @@ -283,7 +291,7 @@ TEST(BitVectorInterleavedTest, MainTest) { } TEST(LowerBound4x64, Random) { - std::vector x(4); + std::vector x(8); std::mt19937_64 rng(42); for (size_t i = 0; i < 1000; i++) { uint64_t y = rng();