前置
在做题前,首先解析一下本章节中的popcount.go文件中的代码。
package popcount
// pc[i] is the population count of i.
var pc [256]byte
func init() {
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
}
// PopCount returns the population count (number of set bits) of x.
func PopCount(x uint64) int {
return int(pc[byte(x>>(0*8))] +
pc[byte(x>>(1*8))] +
pc[byte(x>>(2*8))] +
pc[byte(x>>(3*8))] +
pc[byte(x>>(4*8))] +
pc[byte(x>>(5*8))] +
pc[byte(x>>(6*8))] +
pc[byte(x>>(7*8))])
}
解析:
- 首先定义了一个全局变量pc,是一个byte类型的数组,长度为256,其对应下标也为0~255。每个pc[i]存储的是 i 转化为二进制时1的数量。例如:pc[0]=0,pc[1]=1,pc[3] = 2
init()
函数的目的是初始化pc
。在init()
函数中,用到了一个 for 循环,它遍历pc
数组的每个索引i
。对于每个索引i
,它计算i
的二进制表示中 1 的个数,并将结果存储在pc[i]
中。pc[i]
是如何计算的? 我们可以将i
分解为两部分:i/2
和i
的最低位。i/2
就是将i
的二进制表示向右移动一位,最低位被丢弃。因此,pc[i/2]
就是i/2
的二进制表示中 1 的个数,也就是i
的二进制表示中除最低位外 1 的个数。byte(i&1)
则计算i
的最低位是否为 1。将这两部分相加,就得到了i
的二进制表示中 1 的个数。例如,如果i
是 5,那么i
的二进制表示是 101,i/2
的二进制表示是 10,i&1
的结果是 1。因此,pc[5]
就是pc[2]
(即 10 的二进制表示中 1 的个数) 加上 1,结果是 2。pc
数组成为了一个查找表,对于任何 0 到 255 的整数i
,pc[i]
都是i
的二进制表示中 1 的个数。这个查找表在PopCount
函数中被用来快速计算一个 64 位整数的二进制表示中 1 的个数。
练习2.3
描述:
重写PopCount函数,用一个循环代替单一的表达式。比较两个版本的性能。
代码:
package popcount
// pc[i] is the population count of i.
var pc [256]byte
func init() {
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
}
// PopCount returns the population count (number of set bits) of x.
func PopCount(x uint64) int {
return int(pc[byte(x>>(0*8))] +
pc[byte(x>>(1*8))] +
pc[byte(x>>(2*8))] +
pc[byte(x>>(3*8))] +
pc[byte(x>>(4*8))] +
pc[byte(x>>(5*8))] +
pc[byte(x>>(6*8))] +
pc[byte(x>>(7*8))])
}
func RangeCount(x uint64) int {
var sum byte = 0
for i := 0; i <= 7; i++ {
sum += pc[byte(x>>(i*8))]
}
return int(sum)
}
package popcount
import "testing"
func BenchmarkPopCount(b *testing.B) {
for i := 0; i < b.N; i++ {
PopCount(999)
}
}
func BenchmarkRangeCount(b *testing.B) {
for i := 0; i < b.N; i++ {
RangeCount(999)
}
}
运行:
go test -bench=Benchmark
练习 2.4
描述:
用移位算法重写PopCount函数,每次测试最右边的1bit,然后统计总数。比较和
查表算法的性能差异。
代码:
package popcount
// pc[i] is the population count of i.
var pc [256]byte
func init() {
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
}
// PopCount returns the population count (number of set bits) of x.
func PopCount(x uint64) int {
return int(pc[byte(x>>(0*8))] +
pc[byte(x>>(1*8))] +
pc[byte(x>>(2*8))] +
pc[byte(x>>(3*8))] +
pc[byte(x>>(4*8))] +
pc[byte(x>>(5*8))] +
pc[byte(x>>(6*8))] +
pc[byte(x>>(7*8))])
}
func RangeCount(x uint64) int {
var sum byte = 0
for i := 0; i <= 7; i++ {
sum += pc[byte(x>>(i*8))]
}
return int(sum)
}
func ShiftCount(x uint64) int {
var count int = 0
for i := 0; i <= 63; i++ {
if x&1 == 1 {
count++
}
x = x >> 1
}
return count
}
package popcount
import "testing"
func BenchmarkPopCount(b *testing.B) {
for i := 0; i < b.N; i++ {
PopCount(999)
}
}
func BenchmarkRangeCount(b *testing.B) {
for i := 0; i < b.N; i++ {
RangeCount(999)
}
}
func BenchmarkShiftCount(b *testing.B) {
for i := 0; i < b.N; i++ {
ShiftCount(999)
}
}
截图:
练习 2.5
描述:
表达式 x&(x-1) 用于将x的最低的一个非零的bit位清零。使用这个算法重写PopCount函数,然后比较性能。(PS:每次运算x&(x-1)可以将x二进制形式中排在最后的那一位1给消除,只需要计算消除1的次数即可)
代码:
package popcount
// pc[i] is the population count of i.
var pc [256]byte
func init() {
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
}
// PopCount returns the population count (number of set bits) of x.
func PopCount(x uint64) int {
return int(pc[byte(x>>(0*8))] +
pc[byte(x>>(1*8))] +
pc[byte(x>>(2*8))] +
pc[byte(x>>(3*8))] +
pc[byte(x>>(4*8))] +
pc[byte(x>>(5*8))] +
pc[byte(x>>(6*8))] +
pc[byte(x>>(7*8))])
}
func RangeCount(x uint64) int {
var sum byte = 0
for i := 0; i <= 7; i++ {
sum += pc[byte(x>>(i*8))]
}
return int(sum)
}
func ShiftCount(x uint64) int {
var count = 0
for i := 0; i <= 63; i++ {
if x&1 == 1 {
count++
}
x = x >> 1
}
return count
}
func AndCount(x uint64) int {
var count = 0
for x != 0 {
x = x & (x - 1)
count++
}
return count
}
package popcount
import "testing"
func BenchmarkPopCount(b *testing.B) {
for i := 0; i < b.N; i++ {
PopCount(999)
}
}
func BenchmarkRangeCount(b *testing.B) {
for i := 0; i < b.N; i++ {
RangeCount(999)
}
}
func BenchmarkShiftCount(b *testing.B) {
for i := 0; i < b.N; i++ {
ShiftCount(999)
}
}
func BenchmarkAndCount(b *testing.B) {
for i := 0; i < b.N; i++ {
AndCount(999)
}
}
运行截图: