Skip to content

Instantly share code, notes, and snippets.

@kammce
Last active August 6, 2021 14:28
Show Gist options
  • Save kammce/fcbf226c31afb694f68e6c3a9b5cd78d to your computer and use it in GitHub Desktop.
Save kammce/fcbf226c31afb694f68e6c3a9b5cd78d to your computer and use it in GitHub Desktop.
[Benchmark] C-style multibit insert vs C++ bitset
#include <bitset>
#include <cinttypes>
#include <cstdio>
#include <limits>
#include <type_traits>
namespace xstd {
struct bitrange {
uint32_t position;
uint32_t width;
template <typename T>
constexpr auto mask() const
{
// Need to use an unsigned version of the type T for the mask to make sure
// that the shift right doesn't result in a sign extended shift.
using UnsignedT = typename std::make_unsigned<T>::type;
// At compile time, generate variable containing all 1s with the size of the
// target parameter.
constexpr UnsignedT kFieldOfOnes = std::numeric_limits<UnsignedT>::max();
// At compile time calculate the number of bits in the target parameter.
constexpr size_t kTargetWidth = sizeof(T) * 8;
// Create mask by shifting the set of 1s down so that the number of 1s from
// bit position 0 is equal to the width parameter.
UnsignedT bitmask_at_origin = static_cast<UnsignedT>(kFieldOfOnes >> (kTargetWidth - width));
UnsignedT bitmask = static_cast<UnsignedT>(bitmask_at_origin << position);
return bitmask;
}
template <typename T>
constexpr auto inverted_mask() const
{
return ~mask<T>();
}
};
template <typename T>
class bitset : public std::bitset<sizeof(T) * 8> {
public:
bitset(T& register_reference)
: std::bitset<sizeof(T) * 8>(register_reference)
, register_reference_(register_reference)
{
}
void save() { register_reference_ = static_cast<T>(this->to_ulong()); }
template <bitrange field, typename U>
constexpr auto& insert(U value)
{
auto kBitmask = field.mask<std::remove_volatile_t<T>>();
auto kInvertedBitmask = field.inverted_mask<std::remove_volatile_t<T>>();
// Clear width's number of bits in the target value at the bit position
// specified.
*this &= kInvertedBitmask;
// AND value with mask to remove any bits beyond the specified width.
// Shift masked value into bit position and OR with target value.
*this |= (value << field.position) & kBitmask;
return *this;
}
template <bitrange mask>
[[nodiscard]] constexpr auto extract()
{
// Create mask by shifting the set of 1s down so that the number of 1s
// from bit position 0 is equal to the width parameter.
return std::bitset<mask.width>(this->to_ullong() >> mask.position);
}
~bitset() { save(); }
T& register_reference_;
};
}
struct Register {
volatile uint32_t CTRL1;
volatile uint32_t CTRL2;
};
Register original;
Register * reg = &original;
constexpr uint16_t kTestValue = 0x01AA;
static void ClearThenSet(benchmark::State& state) {
for (auto _ : state) {
reg->CTRL1 &= ~(0xFF << 8);
reg->CTRL1 |= (kTestValue << 8) & 0xFF;
}
}
BENCHMARK(ClearThenSet);
static void SingleLineAssignment(benchmark::State& state) {
for (auto _ : state) {
reg->CTRL1 = (reg->CTRL1 & ~(0xFF << 8)) | ((kTestValue & 0xFF) << 8);
}
}
BENCHMARK(SingleLineAssignment);
static void FastBitmask(benchmark::State& state) {
for (auto _ : state) {
static constexpr uint32_t kMask = 0xFF;
static constexpr uint32_t kMaskInverted = ~(kMask << 8);
auto temp = reg->CTRL1;
temp = (temp & kMaskInverted) | ((kTestValue & kMask) << 8);
reg->CTRL1 = temp;
}
}
BENCHMARK(FastBitmask);
static void UsingBitset(benchmark::State& state) {
for (auto _ : state) {
static constexpr xstd::bitrange kIntensity { 16, 8 };
xstd::bitset(reg->CTRL1).insert<kIntensity>(kTestValue);
}
}
BENCHMARK(UsingBitset);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment