标准库:内存优化:使用unsafe检查

106 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情

2 标准库unsafe的简单介绍

在1.20中,标准库的unsafe包很小, 二个结构体类型,八个函数,在一个文件中。

	package unsage

	type ArbitraryType int
	type IntegerType int
	type Pointer *ArbitraryType

	func Sizeof(x ArbitraryType) uintptr
	func Offsetof(x ArbitraryType) uintptr
	func Alignof(x ArbitraryType) uintptr

	func Add(ptr Pointer, len IntegerType) Pointer
	func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
	func SliceData(slice []ArbitraryType) *ArbitraryType
	func String(ptr *byte, len IntegerType) string
	func StringData(str string) *byte

unsafe包定义了 二个类型和 八个函数,二个类型 ArbitraryType 和 IntegerType 不真正属于unsafe包,我们在Go代码中并不能使用它们定义变量。

它表示一个任意表达式的类型,仅用于文档目的,Go编译器会对其做特殊处理。

虽然位于 unsafe,但是 Alignof,Offsetof,Sizeof,这三个函数的使用是绝对安全的。 以至于Go设计者Rob pike提议移走它们。

这三个函数的共同点是 都返回 uintptr 类型。

之所以使用 uintptr 类型而不是 uint64 整型,因为这三个函数更多应用于 有 unsafe.Pointer和 uintptr类型参数的指针运算。

采用uintptr做为返回值类型可以减少指针运算表达式的显式类型转换。

2.1 获取大小 Sizeof

Sizeof 用于获取一个表达式的大小。 该函数获取一个任意类型的表达式 x,并返回 按bytes计算 的大小,假设变量v,并且v通过 v =x声明。

Sizeof 接收任何类型的表达式x,并返回以bytes字节为单位的大小, 并且假设变量v是通过var v = x声明的。该大小不包括任何可能被x引用的内存。

例如,如果x是一个切片,Sizeof返回切片描述符的大小,而不是该片所引用的内存的大小。 对于一个结构体,其大小包括由字段对齐引入的任何填充。

如果参数x的类型没有变化,不具有可变的大小,Sizeof的返回值是一个Go常数不可变值 。 (如果一个类型是一个类型参数,或者是一个数组,则该类型具有可变的大小或结构类型中的元素大小可变)。

示例:

	var (
		i  int = 5
		a      = [10]int{}
		ss     = a[:]
		f  FuncFoo

		preValue = map[string]uintptr{
			"i":       8,
			"a":       80,
			"ss":      24,
			"f":       48,
			"f.c":     10,
			"int_nil": 8,
		}
	)

	type FuncFoo struct {
		a int
		b string
		c [10]byte
		d float64
	}


	func TestFuncSizeof(t *testing.T) {
		defer setUp(t.Name())()
		fmt.Printf("\tExecute test:%v\n", t.Name())

		if unsafe.Sizeof(i) != preValue["i"] {
			ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["i"]), t)
		}

		if unsafe.Sizeof(a) != preValue["a"] {
			ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["a"]), t)

		}

		if unsafe.Sizeof(ss) != preValue["ss"] {
			ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["ss"]), t)

		}
		if unsafe.Sizeof(f) != preValue["f"] {
			ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["f"]), t)

		}
		if unsafe.Sizeof(f.c) != preValue["f.c"] {
			ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["f.c"]), t)

		}
		if unsafe.Sizeof(unsafe.Sizeof((*int)(nil))) != preValue["int_nil"] {
			ErrorHandler(fmt.Sprintf("size: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t)

		}
	}

Sizeof 函数不支持之间传入无类型信息的nil值,如下错误

	unsafe.Sizeof(nil)  

我们必须显式告知 Sizeof 传入的nil究竟是那个类型,

	unsafe.Sizeof(unsafe.Sizeof((*int)(nil))) 

必须显式告知nil是哪个类型的nil,这就是传入一个值 nil 但是类型明确的变量。

对齐系数 Alignof 用于获取一个表达式的内地地址对齐系数,对齐系数 alignment factor 是一个计算机体系架构 computer architecture 层面的术语。

在不同计算机体系中,处理器对变量地址都有对齐要求,即变量的地址必须可被该变量的对齐系数整除。

它接收一个任何类型的表达式x,并返回所需的排列方式 假设变量v是通过var v = x声明的。 它是m一个最大的值。

例1,

		a      = [10]int{}

		reflect.TypeOf(x).Align()  //8
		unsafe.Alignof(a)   //8

它与reflect.TypeOf(x).Align()返回的值相同。

作为一个特例,如果一个变量s是结构类型,f是一个字段,那么Alignof(s.f)将返回所需的对齐方式。

该类型的字段在结构中的位置。这种情况与reeflect.TypeOf(s.f).FieldAlign()返回的值。

Alignof的返回值是一个Go常数,如果参数的类型不具有可变大小。 (关于可变大小类型的定义,请参见[Sizeof]的描述)。

继上 例2:

 	 var (
		i  int = 5
		a      = [10]int{}
		ss     = a[:]
		f  FuncFoo
		zhs = "文"

		preValue = map[string]uintptr{
			"i":       8,
			"a":       80,
			"ss":      24,
			"f":       48,
			"f.c":     10,
			"int_nil": 8,
		}
	)

	func TestAlignof(t *testing.T) {

		defer setUp(t.Name())()
		fmt.Printf("\tExecute test:%v\n", t.Name())

		var x int 

		b := uintptr(unsafe.Pointer(&x))%unsafe.Alignof(x) == 0
		t.Log("alignof:", b)

		if unsafe.Alignof(i) != preValue["i"] {
			ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t)

		}

		if unsafe.Alignof(a) != preValue["i"] {
			ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t)

		}

		if unsafe.Alignof(ss) != preValue["i"] {
			ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t)

		}

		if unsafe.Alignof(f.a) != preValue["i"] {
			ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t)

		}

		if unsafe.Alignof(f) != preValue["i"] {
			ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["int_nil"]), t)

		}

中文对齐系数 为 8

		if unsafe.Alignof(zhs) != preValue["i"] {
			ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), preValue["i"]), t)
		}

空结构体对齐系数 1

		if unsafe.Alignof(struct{}{}) != 1 {
			ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 1), t)
		}

byte 数组对齐系数为 1

		if unsafe.Alignof(sbyte) != 1 {
			ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 1), t)
		}

长度为0 的数组,与其元素的对齐系数相同

		if unsafe.Alignof([0]int{}) != 8 {
			ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 8), t)
		}

长度为0 的数组,与其元素的对齐系数相同

		if unsafe.Alignof([0]struct{}{}) != 1 {
			ErrorHandler(fmt.Sprintf("Alignof: %v not equal %v", unsafe.Sizeof(i), 1), t)
		}

	}

执行它:

	go test -timeout 30s -run ^TestAlignof$ ./unsafe_case.go

对齐系数 alignment factor,变量的地址必须可被该变量的对齐系数整除。

小结

我们介绍了unsafe包的检查功能,在初始化时,go结构体已经分配了对应的内存空间,

一个结构体而言,结构体属性为随机顺序的,go将分配更多内存空间。 即使是复制后。

比如 结构体的Cloud 字段。

Sizeof表达式大小总是16, 而对齐系数 Alignof 大小总是8, 而在不同的结构体实例中值长度可以为 4,13, 105.

本节源码地址:

https://github.com/hahamx/examples/tree/main/alg_practice/2_sys_io

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 12 天,点击查看活动详情