聊聊 Go 边界检查消除

前言

这篇文章中碰巧看到了Go边界检查消除相关的讨论. 我也借此简单聊聊.

有这样一段代码, 非常简单, 就是一段求向量点积的程序:

go 复制代码
func sum(a, b []int) int {
	if len(a) != len(b) {
		panic("must be same len")
	}
	ret := 0
	for i := 0; i < len(a); i++ {
		ret += a[i] * b[i]
	}
	return ret
}

根据之前CPU 流水线的原理, 将其在数组内部展开可以提高循环计算效率:

go 复制代码
package main

func sum(a, b []int) int {
	if len(a) != len(b) {
		panic("must be same len")
	}
	ret := 0
	for i := 0; i < len(a); i += 4 {
		s1 := a[i] * b[i]
		s2 := a[i+1] * b[i+1]
		s3 := a[i+2] * b[i+2]
		s4 := a[i+3] * b[i+3]
		ret += s1 + s2 + s3 + s4
	}
	return ret
}

到这里, 就要引出Go边界检查的概念了. 我们都知道, 在数组访问越界的时候会触发panic, 这个其实是编译期在编译期间额外添加边界检查代码实现的. 可以给go build命令添加-gcflags='-d=ssa/check_bce'参数来查看哪些地方触发了边界检查:

我们可以理解为, 上面的程序在编译后是这样的:

go 复制代码
func sum(a, b []int) int {
	if len(a) != len(b) {
		panic("must be same len")
	}
	ret := 0
	for i := 0; i < len(a); i += 4 {
		if i >= cap(a) || i >= cap(b) {
			panic("out of bounds")
		}
		s1 := a[i] * b[i]
		if i+1 >= cap(a) || i+1 >= cap(b) {
			panic("out of bounds")
		}
		s2 := a[i+1] * b[i+1]
		if i+2 >= cap(a) || i+2 >= cap(b) {
			panic("out of bounds")
		}
		s3 := a[i+2] * b[i+2]
		if i+3 >= cap(a) || i+3 >= cap(b) {
			panic("out of bounds")
		}
		s4 := a[i+3] * b[i+3]
		ret += s1 + s2 + s3 + s4
	}
	return ret
}

在每次数组访问前都会进行边界检查.

而如果我们将其改造成这样, 就只需要2次边界检查.

go 复制代码
func sum(a, b []int) int {
	if len(a) != len(b) {
		panic("must be same len")
	}
	ret := 0
	for i := 0; i < len(a); i += 4 {
		aTmp := a[i : i+4] //  Found IsSliceInBounds
		bTmp := b[i : i+4] //  Found IsSliceInBounds
		s1 := aTmp[0] * bTmp[0]
		s2 := aTmp[1] * bTmp[1]
		s3 := aTmp[2] * bTmp[2]
		s4 := aTmp[3] * bTmp[3]
		ret += s1 + s2 + s3 + s4
	}
	return ret
}

场景

简单列一些边界检查的场景, 仅供参考:

go 复制代码
func check(a []int, b [5]int, i int) {
	// 重复访问
	_ = a[2] // Found IsSliceInBounds
	_ = a[2] // 重复访问, 消除边界检查

	// 长度判断
	if 3 < len(a) {
		_ = a[3] // 提前判断长度, 无需边界检查
	}

	// 常量数组
	_ = b[4] // 固定长度数组, 无需边界检查

	// 提前边界检查
	_ = a[5] // Found IsSliceInBounds
	_ = a[4] // 因为上边检查过5, 所以这里无需边界检查
	_ = a[3]
}

如果足够自行, 我们也可以在编译的时候添加参数-gcflags=-B 来禁用边界检查.

这篇文章中有一些其他场景供参考.

OK, 这里抛砖引玉, 简单说一下边界检查这玩意, 感兴趣的也可以查看编译后的汇编代码来了解具体是如何进行边界检查的.

相关推荐
JustHappy4 分钟前
古法编程秘籍(七):互联网到底是什么?把两台电脑怎么说话搞懂就够了
前端·后端·网络协议
yaoxin52112314 分钟前
434. Java 日期时间 API - Period 基于日期的时间段
java·开发语言·python
Hommy8825 分钟前
【剪映小助手】添加图片接口(Add Images)
后端·github·剪映小助手·视频剪辑自动化
凡人叶枫41 分钟前
Effective C++ 条款30:透彻了解 inlining 的里里外外
linux·开发语言·c++·嵌入式开发·effective c++
GetcharZp1 小时前
别再盲目用 OpenCV 读图了,这才是 CV 预处理的终极杀手锏!
后端
学逆向的1 小时前
C++纯虚函数
开发语言·c++·网络安全
程序员二叉1 小时前
【JUC】ThreadLocal底层原理|内存泄漏|弱引用|跨线程传递方案
java·开发语言·面试·职场和发展·juc
程序员二叉1 小时前
【JUC】线程池全套深度详解|参数|流程|拒绝策略|调优|异常处理
java·开发语言·jvm·算法·面试·juc
凡人叶枫2 小时前
Effective C++ 条款22:将成员变量声明为 private
linux·开发语言·c++
Qt程序员2 小时前
掌握 Linux 内核调度:从原理到实现(进程篇)
java·开发语言