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
pull/26566/head^2
Craig Tiller 3 years ago committed by GitHub
parent 5820e152cd
commit 0e6fe3f42c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      BUILD
  2. 35
      CMakeLists.txt
  3. 10
      build_autogenerated.yaml
  4. 156
      src/core/lib/gprpp/bitset.h
  5. 12
      test/core/gprpp/BUILD
  6. 82
      test/core/gprpp/bitset_test.cc
  7. 24
      tools/run_tests/generated/tests.json

@ -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++",

@ -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)

@ -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++

@ -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 <grpc/impl/codegen/port_platform.h>
#include <utility>
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 <std::size_t kBits>
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 <std::size_t kBits>
using Uint = typename UintSelector<kBits>::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 <std::size_t kTotalBits,
std::size_t kUnitBits = ChooseUnitBitsForBitSet(kTotalBits)>
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<kUnitBits> mask_for(std::size_t bit) {
return Uint<kUnitBits>{1} << (bit % kUnitBits);
}
// Return a value that is all ones
static constexpr Uint<kUnitBits> all_ones() {
return Uint<kUnitBits>(~Uint<kUnitBits>(0));
}
// Return a value with n bottom bits ones
static constexpr Uint<kUnitBits> n_ones(std::size_t n) {
return n == kUnitBits ? all_ones() : (Uint<kUnitBits>(1) << n) - 1;
}
// The set of units - kUnitBits sized integers that store kUnitBits bits!
Uint<kUnitBits> units_[kUnits];
};
} // namespace grpc_core
#endif // GRPC_CORE_LIB_GPRPP_BITSET_H

@ -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"],

@ -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 <gtest/gtest.h>
namespace grpc_core {
namespace testing {
// Stand in type to make the size to test a type
template <std::size_t K>
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 <typename S>
struct BitSetTest : public ::testing::Test {};
TYPED_TEST_SUITE(BitSetTest, TestSizes);
TYPED_TEST(BitSetTest, NoneAtInit) {
BitSet<TypeParam::kBits> 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<kBits> 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<kBits> 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();
}

@ -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,

Loading…
Cancel
Save