Skip to content

Commit 177dfba

Browse files
committed
math/bits: faster OnesCount
Using some additional suggestions per "Hacker's Delight". Added documentation and extra tests. Measured on 1.7 GHz Intel Core i7, running macOS 10.12.3. benchmark old ns/op new ns/op delta BenchmarkOnesCount-4 7.34 5.38 -26.70% BenchmarkOnesCount8-4 2.03 1.98 -2.46% BenchmarkOnesCount16-4 2.56 2.50 -2.34% BenchmarkOnesCount32-4 2.98 2.39 -19.80% BenchmarkOnesCount64-4 4.22 2.96 -29.86% Change-Id: I566b0ef766e55cf5776b1662b6016024ebe5d878 Reviewed-on: https://go-review.googlesource.com/37223 Reviewed-by: Matthew Dempsky <mdempsky@google.com> Run-TryBot: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
1 parent d9a19f8 commit 177dfba

File tree

2 files changed

+82
-47
lines changed

2 files changed

+82
-47
lines changed

src/math/bits/bits.go

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ const m1 = 0x3333333333333333 // 00110011 ...
5050
const m2 = 0x0f0f0f0f0f0f0f0f // 00001111 ...
5151
const m3 = 0x00ff00ff00ff00ff // etc.
5252
const m4 = 0x0000ffff0000ffff
53-
const m5 = 0x00000000ffffffff
5453

5554
// OnesCount returns the number of one bits ("population count") in x.
5655
func OnesCount(x uint) int {
@@ -65,37 +64,60 @@ func OnesCount8(x uint8) int {
6564
const m = 1<<8 - 1
6665
x = x>>1&(m0&m) + x&(m0&m)
6766
x = x>>2&(m1&m) + x&(m1&m)
68-
return int(x>>4 + x&(m2&m))
67+
x += x >> 4
68+
return int(x) & (1<<4 - 1)
6969
}
7070

7171
// OnesCount16 returns the number of one bits ("population count") in x.
7272
func OnesCount16(x uint16) int {
7373
const m = 1<<16 - 1
7474
x = x>>1&(m0&m) + x&(m0&m)
7575
x = x>>2&(m1&m) + x&(m1&m)
76-
x = x>>4&(m2&m) + x&(m2&m)
77-
return int(x>>8 + x&(m3&m))
76+
x = (x>>4 + x) & (m2 & m)
77+
x += x >> 8
78+
return int(x) & (1<<5 - 1)
7879
}
7980

8081
// OnesCount32 returns the number of one bits ("population count") in x.
8182
func OnesCount32(x uint32) int {
8283
const m = 1<<32 - 1
8384
x = x>>1&(m0&m) + x&(m0&m)
8485
x = x>>2&(m1&m) + x&(m1&m)
85-
x = x>>4&(m2&m) + x&(m2&m)
86-
x = x>>8&(m3&m) + x&(m3&m)
87-
return int(x>>16 + x&(m4&m))
86+
x = (x>>4 + x) & (m2 & m)
87+
x += x >> 8
88+
x += x >> 16
89+
return int(x) & (1<<6 - 1)
8890
}
8991

9092
// OnesCount64 returns the number of one bits ("population count") in x.
9193
func OnesCount64(x uint64) int {
94+
// Implementation: Parallel summing of adjacent bits.
95+
// See "Hacker's Delight", Chap. 5: Counting Bits.
96+
// The following pattern shows the general approach:
97+
//
98+
// x = x>>1&(m0&m) + x&(m0&m)
99+
// x = x>>2&(m1&m) + x&(m1&m)
100+
// x = x>>4&(m2&m) + x&(m2&m)
101+
// x = x>>8&(m3&m) + x&(m3&m)
102+
// x = x>>16&(m4&m) + x&(m4&m)
103+
// x = x>>32&(m5&m) + x&(m5&m)
104+
// return int(x)
105+
//
106+
// Masking (& operations) can be left away when there's no
107+
// danger that a field's sum will carry over into the next
108+
// field: Since the result cannot be > 64, 8 bits is enough
109+
// and we can ignore the masks for the shifts by 8 and up.
110+
// Per "Hacker's Delight", the first line can be simplified
111+
// more, but it saves at best one instruction, so we leave
112+
// it alone for clarity.
92113
const m = 1<<64 - 1
93114
x = x>>1&(m0&m) + x&(m0&m)
94115
x = x>>2&(m1&m) + x&(m1&m)
95-
x = x>>4&(m2&m) + x&(m2&m)
96-
x = x>>8&(m3&m) + x&(m3&m)
97-
x = x>>16&(m4&m) + x&(m4&m)
98-
return int(x>>32 + x&(m5&m))
116+
x = (x>>4 + x) & (m2 & m)
117+
x += x >> 8
118+
x += x >> 16
119+
x += x >> 32
120+
return int(x) & (1<<7 - 1)
99121
}
100122

101123
// --- RotateLeft ---

src/math/bits/bits_test.go

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -232,48 +232,61 @@ func BenchmarkTrailingZeros64(b *testing.B) {
232232
}
233233

234234
func TestOnesCount(t *testing.T) {
235+
var x uint64
236+
for i := 0; i <= 64; i++ {
237+
testOnesCount(t, x, i)
238+
x = x<<1 | 1
239+
}
240+
241+
for i := 64; i >= 0; i-- {
242+
testOnesCount(t, x, i)
243+
x = x << 1
244+
}
245+
235246
for i := 0; i < 256; i++ {
236-
want := tab[i].pop
237247
for k := 0; k < 64-8; k++ {
238-
x := uint64(i) << uint(k)
239-
if x <= 1<<8-1 {
240-
got := OnesCount8(uint8(x))
241-
if got != want {
242-
t.Fatalf("OnesCount8(%#02x) == %d; want %d", x, got, want)
243-
}
244-
}
248+
testOnesCount(t, uint64(i)<<uint(k), tab[i].pop)
249+
}
250+
}
251+
}
245252

246-
if x <= 1<<16-1 {
247-
got := OnesCount16(uint16(x))
248-
if got != want {
249-
t.Fatalf("OnesCount16(%#04x) == %d; want %d", x, got, want)
250-
}
251-
}
253+
func testOnesCount(t *testing.T, x uint64, want int) {
254+
if x <= 1<<8-1 {
255+
got := OnesCount8(uint8(x))
256+
if got != want {
257+
t.Fatalf("OnesCount8(%#02x) == %d; want %d", x, got, want)
258+
}
259+
}
252260

253-
if x <= 1<<32-1 {
254-
got := OnesCount32(uint32(x))
255-
if got != want {
256-
t.Fatalf("OnesCount32(%#08x) == %d; want %d", x, got, want)
257-
}
258-
if UintSize == 32 {
259-
got = OnesCount(uint(x))
260-
if got != want {
261-
t.Fatalf("OnesCount(%#08x) == %d; want %d", x, got, want)
262-
}
263-
}
261+
if x <= 1<<16-1 {
262+
got := OnesCount16(uint16(x))
263+
if got != want {
264+
t.Fatalf("OnesCount16(%#04x) == %d; want %d", x, got, want)
265+
}
266+
}
267+
268+
if x <= 1<<32-1 {
269+
got := OnesCount32(uint32(x))
270+
if got != want {
271+
t.Fatalf("OnesCount32(%#08x) == %d; want %d", x, got, want)
272+
}
273+
if UintSize == 32 {
274+
got = OnesCount(uint(x))
275+
if got != want {
276+
t.Fatalf("OnesCount(%#08x) == %d; want %d", x, got, want)
264277
}
278+
}
279+
}
265280

266-
if x <= 1<<64-1 {
267-
got := OnesCount64(uint64(x))
268-
if got != want {
269-
t.Fatalf("OnesCount64(%#016x) == %d; want %d", x, got, want)
270-
}
271-
if UintSize == 64 {
272-
got = OnesCount(uint(x))
273-
if got != want {
274-
t.Fatalf("OnesCount(%#016x) == %d; want %d", x, got, want)
275-
}
276-
}
281+
if x <= 1<<64-1 {
282+
got := OnesCount64(uint64(x))
283+
if got != want {
284+
t.Fatalf("OnesCount64(%#016x) == %d; want %d", x, got, want)
285+
}
286+
if UintSize == 64 {
287+
got = OnesCount(uint(x))
288+
if got != want {
289+
t.Fatalf("OnesCount(%#016x) == %d; want %d", x, got, want)
277290
}
278291
}
279292
}

0 commit comments

Comments
 (0)