From 0e6fe3f42c6ffa60111a77d750023a8199c60f46 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Fri, 23 Jul 2021 08:53:45 -0700 Subject: [PATCH] Bitset (#26716) A simple bitset type -- to replace `std::bitset` usage in #26698 and #26254 `std::bitset` uses at least 64 bits even to store two bits, and the usages I'm looking at would benefit from having something smaller in those circumstances --- BUILD | 9 ++ CMakeLists.txt | 35 ++++++ build_autogenerated.yaml | 10 ++ src/core/lib/gprpp/bitset.h | 156 +++++++++++++++++++++++++++ test/core/gprpp/BUILD | 12 +++ test/core/gprpp/bitset_test.cc | 82 ++++++++++++++ tools/run_tests/generated/tests.json | 24 +++++ 7 files changed, 328 insertions(+) create mode 100644 src/core/lib/gprpp/bitset.h create mode 100644 test/core/gprpp/bitset_test.cc diff --git a/BUILD b/BUILD index 9cca6f14814..623f240969a 100644 --- a/BUILD +++ b/BUILD @@ -800,6 +800,15 @@ grpc_cc_library( ], ) +grpc_cc_library( + name = "bitset", + language = "c++", + public_hdrs = ["src/core/lib/gprpp/bitset.h"], + deps = [ + "gpr_platform", + ], +) + grpc_cc_library( name = "orphanable", language = "c++", diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bdc3287379..29b75d444a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -750,6 +750,7 @@ if(gRPC_BUILD_TESTS) add_dependencies(buildtests_cxx bdp_estimator_test) endif() add_dependencies(buildtests_cxx binder_smoke_test) + add_dependencies(buildtests_cxx bitset_test) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_POSIX) add_dependencies(buildtests_cxx bm_alarm) endif() @@ -8338,6 +8339,40 @@ target_link_libraries(binder_smoke_test ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(bitset_test + test/core/gprpp/bitset_test.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(bitset_test + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} + ${_gRPC_RE2_INCLUDE_DIR} + ${_gRPC_SSL_INCLUDE_DIR} + ${_gRPC_UPB_GENERATED_DIR} + ${_gRPC_UPB_GRPC_GENERATED_DIR} + ${_gRPC_UPB_INCLUDE_DIR} + ${_gRPC_XXHASH_INCLUDE_DIR} + ${_gRPC_ZLIB_INCLUDE_DIR} + third_party/googletest/googletest/include + third_party/googletest/googletest + third_party/googletest/googlemock/include + third_party/googletest/googlemock + ${_gRPC_PROTO_GENS_DIR} +) + +target_link_libraries(bitset_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} +) + + endif() if(gRPC_BUILD_TESTS) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_POSIX) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index b27d56511d4..711bdb1cc12 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -4397,6 +4397,16 @@ targets: deps: - grpc_test_util uses_polling: false +- name: bitset_test + gtest: true + build: test + language: c++ + headers: + - src/core/lib/gprpp/bitset.h + src: + - test/core/gprpp/bitset_test.cc + deps: [] + uses_polling: false - name: bm_alarm build: test language: c++ diff --git a/src/core/lib/gprpp/bitset.h b/src/core/lib/gprpp/bitset.h new file mode 100644 index 00000000000..8e225bd56ad --- /dev/null +++ b/src/core/lib/gprpp/bitset.h @@ -0,0 +1,156 @@ +// Copyright 2021 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GRPC_CORE_LIB_GPRPP_BITSET_H +#define GRPC_CORE_LIB_GPRPP_BITSET_H + +#include + +#include + +namespace grpc_core { + +// Given a bit count as an integer, vend as member type `Type` a type with +// exactly that number of bits. Undefined if that bit count is not available. +template +struct UintSelector; +template <> +struct UintSelector<8> { + typedef uint8_t Type; +}; +template <> +struct UintSelector<16> { + typedef uint16_t Type; +}; +template <> +struct UintSelector<32> { + typedef uint32_t Type; +}; +template <> +struct UintSelector<64> { + typedef uint64_t Type; +}; + +// An unsigned integer of some number of bits. +template +using Uint = typename UintSelector::Type; + +// Given the total number of bits that need to be stored, choose the size of +// 'unit' for a BitSet... We'll use an array of units to store the total set. +// For small bit counts we are selective in the type to try and balance byte +// size and performance +// - the details will likely be tweaked into the future. +// Once we get over 96 bits, we just use uint64_t for everything. +constexpr std::size_t ChooseUnitBitsForBitSet(std::size_t total_bits) { + return total_bits <= 8 ? 8 + : total_bits <= 16 ? 16 + : total_bits <= 24 ? 8 + : total_bits <= 32 ? 32 + : total_bits <= 48 ? 16 + : total_bits <= 64 ? 64 + : total_bits <= 96 ? 32 + : 64; +} + +// A BitSet that's configurable. +// Contains storage for kTotalBits, stored as an array of integers of size +// kUnitBits. e.g. to store 72 bits in 8 bit chunks, we'd say BitSet<72, 8>. +// Since most users shouldn't care about the size of unit used, we default +// kUnitBits to whatever is selected by ChooseUnitBitsForBitSet +template +class BitSet { + static constexpr std::size_t kUnits = + (kTotalBits + kUnitBits - 1) / kUnitBits; + + public: + // Initialize to all bits false + constexpr BitSet() : units_{} {} + + // Set bit i to true + void set(int i) { units_[unit_for(i)] |= mask_for(i); } + + // Set bit i to is_set + void set(int i, bool is_set) { + if (is_set) { + set(i); + } else { + clear(i); + } + } + + // Set bit i to false + void clear(int i) { units_[unit_for(i)] &= ~mask_for(i); } + + // Return true if bit i is set + constexpr bool is_set(int i) const { + return (units_[unit_for(i)] & mask_for(i)) != 0; + } + + // Return true if all bits are set + bool all() const { + if (kTotalBits % kUnitBits == 0) { + // kTotalBits is a multiple of kUnitBits ==> we can just check for all + // ones in each unit. + for (std::size_t i = 0; i < kUnits; i++) { + if (units_[i] != all_ones()) return false; + } + return true; + } else { + // kTotalBits is not a multiple of kUnitBits ==> we need special handling + // for checking partial filling of the last unit (since not all of its + // bits are used!) + for (std::size_t i = 0; i < kUnits - 1; i++) { + if (units_[i] != all_ones()) return false; + } + return units_[kUnits - 1] == n_ones(kTotalBits % kUnitBits); + } + } + + // Return true if *no* bits are set. + bool none() const { + for (std::size_t i = 0; i < kUnits; i++) { + if (units_[i] != 0) return false; + } + return true; + } + + private: + // Given a bit index, return which unit it's stored in. + static constexpr std::size_t unit_for(std::size_t bit) { + return bit / kUnitBits; + } + + // Given a bit index, return a mask to access that bit within it's unit. + static constexpr Uint mask_for(std::size_t bit) { + return Uint{1} << (bit % kUnitBits); + } + + // Return a value that is all ones + static constexpr Uint all_ones() { + return Uint(~Uint(0)); + } + + // Return a value with n bottom bits ones + static constexpr Uint n_ones(std::size_t n) { + return n == kUnitBits ? all_ones() : (Uint(1) << n) - 1; + } + + // The set of units - kUnitBits sized integers that store kUnitBits bits! + Uint units_[kUnits]; +}; + +} // namespace grpc_core + +#endif // GRPC_CORE_LIB_GPRPP_BITSET_H diff --git a/test/core/gprpp/BUILD b/test/core/gprpp/BUILD index ba31c2c3c20..b631b3e0286 100644 --- a/test/core/gprpp/BUILD +++ b/test/core/gprpp/BUILD @@ -89,6 +89,18 @@ grpc_cc_test( ], ) +grpc_cc_test( + name = "bitset_test", + srcs = ["bitset_test.cc"], + external_deps = ["gtest"], + language = "C++", + uses_polling = False, + deps = [ + "//:bitset", + "//test/core/util:grpc_suppressions", + ], +) + grpc_cc_test( name = "match_test", srcs = ["match_test.cc"], diff --git a/test/core/gprpp/bitset_test.cc b/test/core/gprpp/bitset_test.cc new file mode 100644 index 00000000000..bbd0a70ae1f --- /dev/null +++ b/test/core/gprpp/bitset_test.cc @@ -0,0 +1,82 @@ +// Copyright 2021 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/core/lib/gprpp/bitset.h" +#include + +namespace grpc_core { +namespace testing { + +// Stand in type to make the size to test a type +template +struct Size { + static constexpr std::size_t kBits = K; +}; + +using TestSizes = ::testing::Types< + // All sizes up to 17 bits + Size<1>, Size<2>, Size<3>, Size<4>, Size<5>, Size<6>, Size<7>, Size<8>, + Size<9>, Size<10>, Size<11>, Size<12>, Size<13>, Size<14>, Size<15>, + Size<16>, Size<17>, + // Values around 32 bits + Size<24>, Size<25>, Size<26>, Size<27>, Size<28>, Size<29>, Size<30>, + Size<31>, Size<32>, Size<33>, + // Values around 48 bits + Size<47>, Size<48>, Size<49>, + // Values around 64 bits + Size<62>, Size<63>, Size<64>, Size<65>, Size<66>, + // Values around 96 bits + Size<95>, Size<96>, Size<97>, + // Silly numbers of bits + Size<1024>, Size<4000>, Size<4321> >; + +template +struct BitSetTest : public ::testing::Test {}; + +TYPED_TEST_SUITE(BitSetTest, TestSizes); + +TYPED_TEST(BitSetTest, NoneAtInit) { + BitSet b; + EXPECT_TRUE(b.none()); +} + +TYPED_TEST(BitSetTest, OneBit) { + constexpr std::size_t kBits = TypeParam::kBits; + for (std::size_t i = 0; i < kBits; i++) { + BitSet b; + b.set(i); + EXPECT_FALSE(b.none()); + for (std::size_t j = 0; j < kBits; j++) { + EXPECT_EQ(b.is_set(j), i == j); + } + } +} + +TYPED_TEST(BitSetTest, AllSet) { + constexpr std::size_t kBits = TypeParam::kBits; + BitSet b; + for (std::size_t i = 0; i < kBits; i++) { + EXPECT_FALSE(b.all()); + b.set(i); + } + EXPECT_TRUE(b.all()); +} + +} // namespace testing +} // namespace grpc_core + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index 0d167760c63..53f1c1d8fed 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -3453,6 +3453,30 @@ ], "uses_polling": false }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "bitset_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": true,