Homing So

Homing So

🎉 哈喽啊~各位
github

The number of 1s in binary — "C/C++ Bitwise Operation Black Technology 03"

Principle#

Calculating the number of 1s in a binary number is actually quite simple; you just need to repeatedly use v & (v - 1) to remove the last 1. The principle can be referenced in this article: Powers of 2 — "C/C++ Bit Manipulation Black Technology 02"

The above method is a common way of thinking, and below I will introduce another approach: the parallel counter, to count the number of 1s in a binary number.

In fact, we can view this number as being composed entirely of unit counters, where 1 and 0 represent the state of a single counter. We just need to merge adjacent counters, which is essentially the idea of merging.

Code#

inline unsigned count_bits(uint64_t v)
{
    v = (v & 0x5555555555555555) + ((v >> 1) & 0x5555555555555555);
    v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333);
    v = (v & 0x0f0f0f0f0f0f0f0f) + ((v >> 4) & 0x0f0f0f0f0f0f0f0f);
    v = (v & 0x00ff00ff00ff00ff) + ((v >> 8) & 0x00ff00ff00ff00ff);
    v = (v & 0x0000ffff0000ffff) + ((v >> 16) & 0x0000ffff0000ffff);
    v = (v & 0x00000000ffffffff) + ((v >> 32) & 0x00000000ffffffff);
    return v;
}

inline unsigned count_bits(uint32_t v)
{
    v = (v & 0x55555555) + ((v >> 1) & 0x55555555);
    v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
    v = (v & 0x0f0f0f0f) + ((v >> 4) & 0x0f0f0f0f);
    v = (v & 0x00ff00ff) + ((v >> 8) & 0x00ff00ff);
    v = (v & 0x0000ffff) + ((v >> 16) & 0x0000ffff);
    return v;
}

inline unsigned count_bits(uint16_t v)
{
    v = (v & 0x5555) + ((v >> 1) & 0x5555);
    v = (v & 0x3333) + ((v >> 2) & 0x3333);
    v = (v & 0x0f0f) + ((v >> 4) & 0x0f0f);
    v = (v & 0x00ff) + ((v >> 8) & 0x00ff);
    return v;
}

inline unsigned count_bits(uint8_t v)
{
    v = (v & 0x55) + ((v >> 1) & 0x55);
    v = (v & 0x33) + ((v >> 2) & 0x33);
    v = (v & 0x0f) + ((v >> 4) & 0x0f);
    return v;
}

Principle Analysis#

Let's take 1110001010011110 as an example to explain the method of merging parallel counters:

val1110001010011110
& 0x55550101010101010101
=0100000000010100
val >> 10111000101001111
& 0x55550101010101010101
=0101000101000101

Then adding the two gives the merged count of adjacent 2 counters: 1001000101011001, and we continue to merge counters in pairs of 2 bits.

Val1001000101011001
& 0x33330011001100110011
=0001000100010001
Val >> 20010010001010110
& 0x33330011001100110011
=0010000000010010

Then adding the two gives the merged count of adjacent 4 counters: 0011000100100011, and we continue to merge counters in groups of 4 bits.

Val0011000100100011
& 0x0f0f0000111100001111
=0000000100000011
Val >> 40000001100010010
& 0x0f0f0000111100001111
=0000001100000010

Then adding the two gives the merged count of adjacent 8 counters: 0000010000000101, and we continue to merge counters in groups of 8 bits.

Val0000010000000101
&00ff0000000011111111
=0000000000000101
Val >> 80000000000000100
&00ff0000000011111111
=0000000000000100

Then adding the two gives the merged count of adjacent 8 counters: 0000000000001001, which converts to decimal as 9, matching the number of 1s in the original number.

Benchmark#

#include "benchmark/benchmark.h"

inline unsigned count_bits(uint64_t v)
{
  v = (v & 0x5555555555555555) + ((v >> 1) & 0x5555555555555555);
  v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333);
  v = (v & 0x0f0f0f0f0f0f0f0f) + ((v >> 4) & 0x0f0f0f0f0f0f0f0f);
  v = (v & 0x00ff00ff00ff00ff) + ((v >> 8) & 0x00ff00ff00ff00ff);
  v = (v & 0x0000ffff0000ffff) + ((v >> 16) & 0x0000ffff0000ffff);
  v = (v & 0x00000000ffffffff) + ((v >> 32) & 0x00000000ffffffff);
  return v;
}

inline unsigned count_bits(uint32_t v)
{
  v = (v & 0x55555555) + ((v >> 1) & 0x55555555);
  v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
  v = (v & 0x0f0f0f0f) + ((v >> 4) & 0x0f0f0f0f);
  v = (v & 0x00ff00ff) + ((v >> 8) & 0x00ff00ff);
  v = (v & 0x0000ffff) + ((v >> 16) & 0x0000ffff);
  return v;
}

inline unsigned count_bits(uint16_t v)
{
  v = (v & 0x5555) + ((v >> 1) & 0x5555);
  v = (v & 0x3333) + ((v >> 2) & 0x3333);
  v = (v & 0x0f0f) + ((v >> 4) & 0x0f0f);
  v = (v & 0x00ff) + ((v >> 8) & 0x00ff);
  return v;
}

inline unsigned count_bits(uint8_t v)
{
  v = (v & 0x55) + ((v >> 1) & 0x55);
  v = (v & 0x33) + ((v >> 2) & 0x33);
  v = (v & 0x0f) + ((v >> 4) & 0x0f);
  return v;
}

static void BM_count_64(benchmark::State &state) {
  for (auto _: state) {
    uint64_t n = UINT64_MAX;
    benchmark::DoNotOptimize(count_bits(n));
  }
}

static void BM_count_32(benchmark::State &state) {
  for (auto _: state) {
    uint32_t n = UINT32_MAX;
    benchmark::DoNotOptimize(count_bits(n));
  }
}

static void BM_count_16(benchmark::State &state) {
  for (auto _: state) {
    uint16_t n = UINT16_MAX;
    benchmark::DoNotOptimize(count_bits(n));
  }
}

static void BM_count_8(benchmark::State &state) {
  for (auto _: state) {
    uint8_t n = UINT8_MAX;
    benchmark::DoNotOptimize(count_bits(n));
  }
}

BENCHMARK(BM_count_8);
BENCHMARK(BM_count_16);
BENCHMARK(BM_count_32);
BENCHMARK(BM_count_64);

BENCHMARK_MAIN();

Below are the results obtained using a MacBook Air (M1, 2020) and Apple clang 13.1.6

/Users/hominsu/CLionProjects/bit-hacks-bench/cmake-build-release-appleclang/bench/count_bits
Unable to determine clock rate from sysctl: hw.cpufrequency: No such file or directory
2022-03-27T14:09:30+08:00
Running /Users/hominsu/CLionProjects/bit-hacks-bench/cmake-build-release-appleclang/bench/count_bits
Run on (8 X 24.1205 MHz CPU s)
CPU Caches:
  L1 Data 64 KiB (x8)
  L1 Instruction 128 KiB (x8)
  L2 Unified 4096 KiB (x2)
Load Average: 2.64, 2.22, 1.79
------------------------------------------------------
Benchmark            Time             CPU   Iterations
------------------------------------------------------
BM_count_8       0.319 ns        0.319 ns   1000000000
BM_count_16      0.321 ns        0.321 ns   1000000000
BM_count_32      0.313 ns        0.313 ns   1000000000
BM_count_64      0.316 ns        0.316 ns   1000000000

Below are the results obtained using i5-9500 and gcc 8.5.0 (Red Hat 8.5.0-10) on CentOS-8-Stream

/tmp/tmp.CtmwmpTLjC/cmake-build-release-1104/bench/count_bits
2022-03-27T14:10:07+08:00
Running /tmp/tmp.CtmwmpTLjC/cmake-build-release-1104/bench/count_bits
Run on (6 X 4100.35 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x6)
  L1 Instruction 32 KiB (x6)
  L2 Unified 256 KiB (x6)
  L3 Unified 9216 KiB (x1)
Load Average: 0.57, 0.54, 0.51
------------------------------------------------------
Benchmark            Time             CPU   Iterations
------------------------------------------------------
BM_count_8       0.244 ns        0.244 ns   1000000000
BM_count_16      0.246 ns        0.246 ns   1000000000
BM_count_32      0.245 ns        0.244 ns   1000000000
BM_count_64      0.249 ns        0.248 ns   1000000000
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.