Download: 深入Go底层原理,重写Redis中间件实战
Go快速入门
1 Go 安装
最新版本下载地址官方下载 golang.org,当前是 1.13.6。如无法访问,可以在 studygolang.com/dl 下载
使用 Linux,可以用如下方式快速安装。
$ wget https://studygolang.com/dl/golang/go1.13.6.linux-amd64.tar.gz
$ tar -zxvf go1.13.6.linux-amd64.tar.gz
$ sudo mv go /usr/local/
$ go version
go version go1.13.6 linux/amd64
从 Go 1.11 版本开始,Go 提供了 Go Modules 的机制,推荐设置以下环境变量,第三方包的下载将通过国内镜像,避免出现官方网址被屏蔽的问题。
$ go env -w GOPROXY=https://goproxy.cn,direct
或在 ~/.profile 中设置环境变量
export GOPROXY=https://goproxy.cn
2 Hello World
新建一个文件 main.go,写入
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
执行go run main.go 或 go run .,将会输出
$ go run .
Hello World!
3 变量与内置数据类型
3.1 变量(Variable)
Go 语言是静态类型的,变量声明时必须明确变量的类型。Go 语言与其他语言显著不同的一个地方在于,Go 语言的类型在变量后面。比如 java 中,声明一个整体一般写成 int a = 1,在 Go 语言中,需要这么写:
var a int // 如果没有赋值,默认为0
var a int = 1 // 声明时赋值
var a = 1 // 声明时赋值
var a = 1,因为 1 是 int 类型的,所以赋值时,a 自动被确定为 int 类型,所以类型名可以省略不写,这种方式还有一种更简单的表达:
a := 1
msg := "Hello World!"
3.2 简单类型
空值:nil
整型类型: int(取决于操作系统), int8, int16, int32, int64, uint8, uint16, …
浮点数类型:float32, float64
字节类型:byte (等价于uint8)
字符串类型:string
布尔值类型:boolean,(true 或 false)
深入Go底层原理,重写Redis中间件实战- Go结构体的内存布局
结构体大小
结构体是占用一块连续的内存,一个结构体变量的大小是由结构体中的字段决定。
type Foo struct {
A int8 // 1
B int8 // 1
C int8 // 1
}
var f Foo
fmt.Println(unsafe.Sizeof(f)) // 3
内存对齐
但是结构体的大小又不完全由结构体的字段决定,例如:
type Bar struct {
x int32 // 4
y *Foo // 8
z bool // 1
}
var b1 Bar
fmt.Println(unsafe.Sizeof(b1)) // 24
有的同学可能会认为结构体变量b1的内存布局如下图所示,那么问题来了,结构体变量b1的大小怎么会是24呢?
memory layout of Bar1
很显然结构体变量b1的内存布局和上图中的并不一致,实际上的布局应该如下图所示,灰色虚线的部分就是内存对齐时的填充(padding)部分。
memory layout of Bar1
Go 在编译的时候会按照一定的规则自动进行内存对齐。之所以这么设计是为了减少 CPU 访问内存的次数,加大 CPU 访问内存的吞吐量。如果不进行内存对齐的话,很可能就会增加CPU访问内存的次数。例如下图中CPU想要获取b1.y字段的值可能就需要两次总线周期。
word size
因为 CPU 访问内存时,并不是逐个字节访问,而是以字(word)为单位访问。比如 64位CPU的字长(word size)为8bytes,那么CPU访问内存的单位也是8字节,每次加载的内存数据也是固定的若干字长,如8words(64bytes)、16words(128bytes)等。
对齐保证
我们上面已经知道了可以通过内置unsafe包的Sizeof函数来获取一个变量的大小,此外我们还可以通过内置unsafe包的Alignof函数来获取一个变量的对齐系数,例如:
// 结构体变量b1的对齐系数
fmt.Println(unsafe.Alignof(b1)) // 8
// b1每一个字段的对齐系数
fmt.Println(unsafe.Alignof(b1.x)) // 4:表示此字段须按4的倍数对齐
fmt.Println(unsafe.Alignof(b1.y)) // 8:表示此字段须按8的倍数对齐
fmt.Println(unsafe.Alignof(b1.z)) // 1:表示此字段须按1的倍数对齐
unsafe.Alignof()的规则如下:
对于任意类型的变量 x ,unsafe.Alignof(x) 至少为 1。
对于 struct 类型的变量 x,计算 x 每一个字段 f 的 unsafe.Alignof(x.f),unsafe.Alignof(x) 等于其中的最大值。
对于 array 类型的变量 x,unsafe.Alignof(x) 等于构成数组的元素类型的对齐倍数。
在了解了上面的规则之后,我们就可以通过调整结构体 Bar 中字段的顺序来减少其大小:
type Bar2 struct {
x int32 // 4
z bool // 1
y *Foo // 8
}
var b2 Bar2
fmt.Println(unsafe.Sizeof(b2)) // 16
此时结构体 Bar2 变量的内存布局示意图如下:
memory layout of Bar2
或者将字段顺序调整为以下顺序。
type Bar3 struct {
z bool // 1
x int32 // 4
y *Foo // 8
}
var b3 Bar3
fmt.Println(unsafe.Sizeof(b3)) // 16