### 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 本章小結