### Maps, silces 与 Go 垃圾回收器
在本节中,我将向你提供一些示例,这些示例说明了为什么你对于垃圾收集器的操作应保持谨慎。本节的重点是要了解,存储指针的方式对垃圾收集器的性能有很大影响,尤其是在处理大量指针时。
> Tip: 所提供的示例使用了指针,切片和映射,它们都是原生 Go 数据类型。你将在*第 3 章,使用基本 Go 数据类型*了解有关指针,切片和映射的更多信息。
#### 使用 slice
这在一节的例子中我们将使用**slice**来存储大量的结构体数据。每一个结构体数据存储两个整数值。`sliceGC.go`中的 Go 代码如下:
```Go
package main
import (
"runtime"
)
type data struct {
i, j int
}
func main() {
var N = 40000000
var structure []data
for i := 0; i < N; i++ {
value := int(i)
structure = append(structure, data{value, value})
}
runtime.GC()
_ = structure[0]
}
```
最后一条语句`(_ = structure[0])`用于防止垃圾回收器过早地垃圾收集结构体变量,因为未在`for`循环之外对其进行引用或使用。随后的三个 Go 程序将使用相同的技术。除此重要细节外,`for`循环用于将所有值放入存储在 slice 中的结构中。
#### 使用 pointers 操作 map
在本小节中,我们将使用映射将所有指针存储为整数。该程序的名称为`mapStar.go`,其中包含以下 Go 代码:
```Go
package main
import (
"runtime"
)
func main() {
var N = 40000000
myMap := make(map[int]*int)
for i := 0; i < N; i++ {
value := int(i)
myMap[value] = &value
}
runtime.GC()
_ = myMap[0]
}
```
存储整数指针的 map 的名称为 `myMap`, `for` 循环用于将整数值放入 map。
#### 不使用 pointers 操作 map
在本小节中,我们将使用一个存储无指针纯值的 map, `mapNoStar.go`的 Go 代码如下:
```Go
package main
import (
"runtime"
)
func main() {
var N = 40000000
myMap := make(map[int]int)
for i := 0; i < N; i++ {
value := int(i)
myMap[value] = value
}
runtime.GC()
_ = myMap[0]
}
```
和之前一样,使用 for 循环将整数值放入 map 中。
#### 分割 map
本小节的实现会将 map 拆分为 maps,这也称为**分片**。本小节的程序另存为`mapSplit.go`,将分两部分介绍。 mapSplit.go 的第一部分包含以下 Go 代码:
```Go
package main
import (
"runtime"
)
func main() {
var N = 40000000
split := make([]map[int]int, 200)
```
这是定义哈希的哈希值的地方。
第二段代码如下:
```Go
for i := range split {
split[i] = make(map[int]int)
}
for i := 0; i < N; i++ {
value := int(i)
split[i%200][value] = value
}
runtime.GC()
_ = split[0][0]
}
```
这次,我们使用了两个`for`循环:一个用于创建哈希散列的`for`循环,以及
另一个用于在哈希哈希中存储所需数据。
#### Comparing the performance of the presented techniques
由于这四个程序都使用巨大复杂的数据结构,因此它们正在消耗大量内存。占用大量内存空间的程序会更频繁地触发 Go 垃圾收集器。因此,在本小节中,我们将使用`time(1)`命令比较这四个实现中每个实现的性能。
输出中重要的不是确切的数字,而是四种不同方法之间的时间差:
```shell
$ time go run sliceGC.go
real 1.50s
user 1.72s
sys 0.71s
$ time go run mapStar.go
real 13.62s
user 23.74s
sys 1.89s
$ time go run mapNoStar.go
real 11.41s
user 10.35s
sys 1.15s
$ time go run mapSplit.go
real 10.60s
user 10.01s
sys 0.74s
```
因此,事实证明,maps 会减慢 Go 垃圾收集器的速度,而 slices 则可以更好地协作。这里应该注意,这不是 map 的问题,而是 Go 垃圾收集器工作方式的结果。但是,除非你要处理的是存储大量数据的 map,否则此问题在你的程序中将不会变得明显。
> Tip: 你将在第 11 章*代码测试,优化和性能分析*中学习有关基准测试的更多信息。此外,在第 3 章*使用基本 Go 数据类型*中,你将学到更专业的方法来测量 Go 中执行命令或程序所花费的时间。
你了解垃圾收集已经足够多了;下一节的主题将是`unsafe code`和`unsafe` Go package。
- 介绍
- 1 Go与操作系统
- 01.1 Go的历史
- 01.2 Go的未来
- 01.3 Go的优点
- 01.3.1 Go是完美的么
- 01.3.2 什么是预处理器
- 01.3.3 godoc
- 01.4 编译Go代码
- 2 理解 Go 的内部构造
- Go 编译器
- Go 的垃圾回收
- 三色算法
- 有关 Go 垃圾收集器操作的更多信息
- Maps, silces 与 Go 垃圾回收器
- Unsafe code
- 有关 unsafe 包
- 另一个 usafe 包的例子
- 从 Go 调用 C 代码
- 在同一文件用 Go 调用 C 代码
- 在单独的文件用 Go 调用 C 代码
- 从 C 调用 Go 代码
- Go 包
- C 代码
- defer 关键字
- 用 defer 打印日志
- Panic 和 Recover
- 单独使用 Panic 函数
- 两个好用的 UNIX 工具
- strace
- dtrace
- 配置 Go 开发环境
- go env 命令
- Go 汇编器
- 节点树
- 进一步了解 Go 构建
- 创建 WebAssembly 代码
- 对 Webassembly 的简单介绍
- 为什么 WebAssembly 很重要
- Go 与 WebAssembly
- 示例
- 使用创建好的 WebAssembly 代码
- Go 编码风格建议
- 练习和相关链接
- 本章小结
- 3 Go基本数据类型
- 03.1 Go循环
- 03.1.1 for循环
- 03.1.2 while循环
- 03.1.3 range关键字
- 03.1.4 for循环代码示例
- 03.3 Go切片
- 03.3.1 切片基本操作
- 03.3.2 切片的扩容
- 03.3.3 字节切片
- 03.3.4 copy()函数
- 03.3.5 多维切片
- 03.3.6 使用切片的代码示例
- 03.3.7 使用sort.Slice()排序
- 03.4 Go 映射(map)
- 03.4.1 Map值为nil的坑
- 03.4.2 何时该使用Map?
- 03.5 Go 常量
- 03.5.1 常量生成器:iota
- 03.6 Go 指针
- 03.7 时间与日期的处理技巧
- 03.7.1 解析时间
- 03.7.2 解析时间的代码示例
- 03.7.3 解析日期
- 03.7.4 解析日期的代码示例
- 03.7.5 格式化时间与日期
- 03.8 延伸阅读
- 03.9 练习
- 03.10 本章小结
- 9 并发-Goroutines,Channel和Pipeline
- 09.1 关于进程,线程和Go协程
- 09.1.1 Go调度器
- 09.1.2 并发与并行
- 09.2 Goroutines
- 09.2.1 创建一个Goroutine
- 09.2.2 创建多个Goroutine
- 09.3 优雅地结束goroutines
- 09.3.1 当Add()和Done()的数量不匹配时会发生什么?
- 09.4 Channel(通道)
- 09.4.1 通道的写入
- 09.4.2 从通道接收数据
- 09.4.3 通道作为函数参数传递
- 09.5 管道
- 09.6 延展阅读
- 09.7 练习
- 09.8 本章小结