From ac53f364a718e1bead140524e9ee04f09be07ab9 Mon Sep 17 00:00:00 2001 From: Marcus Holland-Moritz Date: Thu, 22 Aug 2024 23:55:04 +0200 Subject: [PATCH] feat(internal): add packed_int_vector template --- CMakeLists.txt | 1 + include/dwarfs/internal/packed_int_vector.h | 177 ++++++++++++++++++++ test/packed_int_vector_test.cpp | 123 ++++++++++++++ 3 files changed, 301 insertions(+) create mode 100644 include/dwarfs/internal/packed_int_vector.h create mode 100644 test/packed_int_vector_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c4329b2c..3ecfd5a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -433,6 +433,7 @@ if(WITH_TESTS) lazy_value_test metadata_requirements_test options_test + packed_int_vector_test pcm_sample_transformer_test pcmaudio_categorizer_test speedometer_test diff --git a/include/dwarfs/internal/packed_int_vector.h b/include/dwarfs/internal/packed_int_vector.h new file mode 100644 index 00000000..2ac16909 --- /dev/null +++ b/include/dwarfs/internal/packed_int_vector.h @@ -0,0 +1,177 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/** + * \author Marcus Holland-Moritz (github@mhxnet.de) + * \copyright Copyright (c) Marcus Holland-Moritz + * + * This file is part of dwarfs. + * + * dwarfs is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * dwarfs is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with dwarfs. If not, see . + */ + +#pragma once + +#include +#include +#include + +#include + +namespace dwarfs::internal { + +template +class packed_int_vector { + public: + using value_type = T; + using bits_type = folly::Bits; + using underlying_type = typename bits_type::UnderlyingType; + using size_type = size_t; + + static constexpr size_type bits_per_block{bits_type::bitsPerBlock}; + + class value_proxy { + public: + value_proxy(packed_int_vector& vec, size_type i) + : vec_{vec} + , i_{i} {} + + operator T() const { return vec_.get(i_); } + + value_proxy& operator=(T value) { + vec_.set(i_, value); + return *this; + } + + private: + packed_int_vector& vec_; + size_type i_; + }; + + packed_int_vector() = default; + packed_int_vector(size_type bits) + : bits_{bits} {} + packed_int_vector(size_type bits, size_type size) + : size_{size} + , bits_{bits} + , data_{min_data_size(size, bits)} {} + + packed_int_vector(packed_int_vector const&) = default; + packed_int_vector(packed_int_vector&&) = default; + packed_int_vector& operator=(packed_int_vector const&) = default; + packed_int_vector& operator=(packed_int_vector&&) = default; + + void reset(size_type bits = 0, size_type size = 0) { + size_ = size; + bits_ = bits; + data_.clear(); + data_.resize(min_data_size(size, bits)); + } + + void resize(size_type size) { + size_ = size; + data_.resize(min_data_size(size, bits_)); + } + + void reserve(size_type size) { data_.reserve(min_data_size(size, bits_)); } + + void shrink_to_fit() { data_.shrink_to_fit(); } + + size_type capacity() const { + return bits_ > 0 ? (data_.capacity() * bits_per_block) / bits_ : 0; + } + + void clear() { + size_ = 0; + data_.clear(); + } + + size_type size() const { return size_; } + size_type bits() const { return bits_; } + + size_type size_in_bytes() const { + return data_.size() * sizeof(underlying_type); + } + + bool empty() const { return size_ == 0; } + + T operator[](size_type i) const { return this->get(i); } + + T at(size_type i) const { + if (i >= size_) { + throw std::out_of_range("packed_int_vector::at"); + } + return this->get(i); + } + + T get(size_type i) const { + return bits_ > 0 ? bits_type::get(data_.data(), i * bits_, bits_) : 0; + } + + value_proxy operator[](size_type i) { return value_proxy{*this, i}; } + + value_proxy at(size_type i) { + if (i >= size_) { + throw std::out_of_range("packed_int_vector::at"); + } + return this->operator[](i); + } + + void set(size_type i, T value) { + if (bits_ > 0) { + bits_type::set(data_.data(), i * bits_, bits_, value); + } + } + + void push_back(T value) { + if (min_data_size(size_ + 1, bits_) > data_.size()) { + data_.resize(data_.size() + 1); + } + set(size_++, value); + } + + void pop_back() { + if (size_ > 0) { + --size_; + } + if (min_data_size(size_, bits_) < data_.size()) { + data_.resize(data_.size() - 1); + } + } + + T back() const { return get(size_ - 1); } + + value_proxy back() { return this->operator[](size_ - 1); } + + T front() const { return get(0); } + + value_proxy front() { return this->operator[](0); } + + std::vector unpack() const { + std::vector result(size_); + for (size_type i = 0; i < size_; ++i) { + result[i] = get(i); + } + return result; + } + + private: + static constexpr size_type min_data_size(size_type size, size_type bits) { + return (size * bits + bits_per_block - 1) / bits_per_block; + } + + size_type size_{0}; + size_type bits_{0}; + std::vector data_; +}; + +} // namespace dwarfs::internal diff --git a/test/packed_int_vector_test.cpp b/test/packed_int_vector_test.cpp new file mode 100644 index 00000000..066f7d0a --- /dev/null +++ b/test/packed_int_vector_test.cpp @@ -0,0 +1,123 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/** + * \author Marcus Holland-Moritz (github@mhxnet.de) + * \copyright Copyright (c) Marcus Holland-Moritz + * + * This file is part of dwarfs. + * + * dwarfs is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * dwarfs is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with dwarfs. If not, see . + */ + +#include + +#include + +using namespace dwarfs::internal; + +TEST(packed_int_vector, basic) { + packed_int_vector vec(5); + + vec.push_back(1); + vec.push_back(31); + vec.push_back(0); + vec.push_back(5); + vec.push_back(3); + vec.push_back(25); + + EXPECT_EQ(vec.size(), 6); + EXPECT_EQ(vec.size_in_bytes(), 4); + + EXPECT_EQ(vec[0], 1); + EXPECT_EQ(vec[1], 31); + EXPECT_EQ(vec[2], 0); + EXPECT_EQ(vec[3], 5); + EXPECT_EQ(vec[4], 3); + EXPECT_EQ(vec[5], 25); + + vec[0] = 11; + EXPECT_EQ(vec[0], 11); + + vec.at(5) = 0; + EXPECT_EQ(vec[5], 0); + + vec.resize(10); + EXPECT_EQ(vec[1], 31); + + EXPECT_THROW(vec.at(10), std::out_of_range); + EXPECT_THROW(vec.at(10) = 17, std::out_of_range); + + auto const& cvec = vec; + + EXPECT_EQ(cvec[0], 11); + EXPECT_EQ(cvec[5], 0); + + EXPECT_THROW(cvec.at(10), std::out_of_range); + + vec.resize(4); + vec.shrink_to_fit(); + + EXPECT_EQ(vec.capacity(), 6); + + EXPECT_EQ(vec[0], 11); + EXPECT_FALSE(vec.empty()); + + vec.clear(); + + EXPECT_EQ(vec.size(), 0); + EXPECT_TRUE(vec.empty()); + vec.shrink_to_fit(); + EXPECT_EQ(vec.capacity(), 0); + EXPECT_EQ(vec.size_in_bytes(), 0); +} + +TEST(packed_int_vector, signed_int) { + packed_int_vector vec(13); + + for (int64_t i = -4096; i < 4096; ++i) { + vec.push_back(i); + } + + EXPECT_EQ(vec.size(), 8192); + EXPECT_EQ(vec.size_in_bytes(), 13312); + + EXPECT_EQ(vec.front(), -4096); + EXPECT_EQ(vec.back(), 4095); + + vec.resize(4096); + + for (int64_t i = 0; i < 4096; ++i) { + EXPECT_EQ(vec[i], i - 4096); + } + + auto unpacked = vec.unpack(); + + for (int64_t i = 0; i < 4096; ++i) { + EXPECT_EQ(unpacked[i], i - 4096); + } +} + +TEST(packed_int_vector, zero_bits) { + packed_int_vector vec(0); + + for (uint32_t i = 0; i < 100; ++i) { + vec.push_back(0); + } + + EXPECT_EQ(vec.size(), 100); + EXPECT_EQ(vec.size_in_bytes(), 0); + + for (uint32_t i = 0; i < 100; ++i) { + EXPECT_EQ(vec[i], 0); + } +}