【go语言圣经】练习2.3-2.5

前置

在做题前,首先解析一下本章节中的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))])
}

解析:

  1. 首先定义了一个全局变量pc,是一个byte类型的数组,长度为256,其对应下标也为0~255。每个pc[i]存储的是 i 转化为二进制时1的数量。例如:pc[0]=0,pc[1]=1,pc[3] = 2
  2. init() 函数的目的是初始化 pc 。在 init() 函数中,用到了一个 for 循环,它遍历 pc 数组的每个索引 i。对于每个索引 i,它计算 i 的二进制表示中 1 的个数,并将结果存储在 pc[i] 中。 pc[i]是如何计算的? 我们可以将 i 分解为两部分:i/2i 的最低位。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。
  3. pc 数组成为了一个查找表,对于任何 0 到 255 的整数 ipc[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)
	}
}

运行截图:

在这里插入图片描述

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言圣经》是一本广受好评的教材,旨在帮助读者系统地学习和掌握Go语言。这本书以简明清晰的方式介绍了Go语言的各种特性、语法和用法,是入门和进阶者的理想选择。 首先,该书提供了对Go语言基础知识的全面介绍。它从简单到复杂地解释了Go语言的基本概念,诸如变量、函数循环和条件语句等等。通过丰富的例子和练习,读者能够逐步理解和掌握这些概念。 其次,该书详细介绍了Go语言的高级特性和用法。读者可以学习到Go语言的面向对象编程、并发编程、网络编程等关键技术。并发编程是Go语言一个独特特性,对于提高程序性能和扩展能力非常重要。 此外,该书还包含了对常见问题和陷阱的讲解,帮助读者避免一些常见的错误和陷阱。同时,书中提供了大量的案例和实践项目,读者可以通过实际操作来巩固所学内容。 《Go语言圣经》以其简洁明了的风格和对细节的深入讲解而闻名。无论是作为初学者的入门指南,还是作为有经验的开发者的参考书,这本书都能满足读者的需求。此外,该书的PDF版本能够方便地在线或离线阅读,为读者提供了更加便捷的学习体验。 综上所述,《Go语言圣经》是一本内容丰富、权威性强的优秀教材。它不仅适合Go语言的初学者,也适用于那些想要深入学习和掌握Go语言的开发者。无论是在线阅读还是PDF版本,读者都能够方便地获取和利用这本宝贵的学习资源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值