概述
Go 中的空結構體 struct{}{}
的內存大小等于 0,除此之外,還有別的數據類型內存大小也等于 0 嗎?
map 實現 set
Go 的標準庫沒有內置的 Set
類型,在不引用第三方包的情況下,一般是結合內置的 map
類型實現 Set
類型相關功能。
這里假設 Set
元素類型為 int
, 那么我們就以 int
作為 map
的鍵類型,以 bool
作為 map
的值類型 (之所以選擇 bool
類型,是因為其大小為 1 個字節,相對其他數據類型可以節省內存,當然,也可以使用 byte
類型,其大小同樣是 1 個字節)。
package main
import "fmt"
// Set 類型定義
type set map[int]bool
// 初始化一個新的 Set
func newSet() set {
return make(set)
}
// 元素是否存在于與集合中
func (s set) contains(ele int) bool {
_, ok := s[ele]
return ok
}
// 添加元素到集合
func (s set) add(ele int) {
s[ele] = true
}
// 從集合中刪除某個元素
func (s set) remove(ele int) {
delete(s, ele)
}
func main() {
s := newSet()
fmt.Println(s.contains(100))
s.add(100)
fmt.Println(s.contains(100))
s.remove(100)
fmt.Println(s.contains(100))
}
$ go run main.go
# 輸出如下
false
true
false
上述示例代碼通過內置類型 map
實現了 set
類型相關功能,美中不足的一點在于: 每個元素都要浪費一個 bool
類型作為標識占位符, 能否進一步的優化呢 ?當然是可以的,這就是接下來要講到的 空結構體
了。
空結構體
Go 中的空結構體 struct{}{}
是 一個底層的內置變量,不占用任何內存 :
package main
import (
"fmt"
"unsafe"
)
func main() {
fmt.Printf("size = %d\\n", unsafe.Sizeof(struct{}{}))
}
$ go run main.go
# 輸出如下
size = 0
結合剛才的例子,可以將 struct{}{}
作為 Set
的元素,這樣不論 Set
有多少個元素,標志位
內存占用始終為 0 。
使用 bool 實現 Set
測試代碼
package performance
import (
"testing"
)
// Set 類型定義, 使用 bool 類型作為占位符
type set map[int]bool
// 初始化一個新的 Set
func newSet() set {
return make(set)
}
// 元素是否存在于與集合中
func (s set) contains(ele int) bool {
_, ok := s[ele]
return ok
}
// 添加元素到集合
func (s set) add(ele int) {
s[ele] = true
}
// 從集合中刪除某個元素
func (s set) remove(ele int) {
delete(s, ele)
}
func Benchmark_Compare(b *testing.B) {
s := newSet()
for i := 0; i < b.N; i++ {
s.add(i)
}
for i := 0; i < b.N; i++ {
_ = s.contains(i)
s.remove(i)
}
}
運行測試,并將基準測試結果寫入文件:
$ go test -run='^$' -bench=. -count=1 -benchtime=10000000x -benchmem > bool.txt
使用空結構體實現 Set
測試代碼
package performance
import (
"testing"
)
// Set 類型定義, 使用 bool 類型作為占位符
type set map[int]struct{}
// 初始化一個新的 Set
func newSet() set {
return make(set)
}
// 元素是否存在于與集合中
func (s set) contains(ele int) bool {
_, ok := s[ele]
return ok
}
// 添加元素到集合
func (s set) add(ele int) {
s[ele] = struct{}{}
}
// 從集合中刪除某個元素
func (s set) remove(ele int) {
delete(s, ele)
}
func Benchmark_Compare(b *testing.B) {
s := newSet()
for i := 0; i < b.N; i++ {
s.add(i)
}
for i := 0; i < b.N; i++ {
_ = s.contains(i)
s.remove(i)
}
}
運行測試,并將基準測試結果寫入文件:
$ go test -run='^$' -bench=. -count=1 -benchtime=10000000x -benchmem > struct.txt
使用 benchstat 比較差異
$ benchstat -alpha=100 bool.txt struct.txt
# 輸出如下
name old time/op new time/op delta
_Compare-8 371ns ± 0% 332ns ± 0% -10.47% (p=1.000 n=1+1)
name old alloc/op new alloc/op delta
_Compare-8 44.0B ± 0% 40.0B ± 0% -9.09% (p=1.000 n=1+1)
name old allocs/op new allocs/op delta
_Compare-8 0.00 0.00 ~ (all equal)
從輸出的結果中可以看到,相比于使用 bool
作為 Set
元素占位符,使用 空結構體
在性能和內存占用方面,都有了小幅度的優化提升 (10% 左右)。因為時間關系,這里的基準測試只運行了 10000000 次, 運行次數越大,優化的效果越明顯 。感興趣的讀者可以將 -benchtime
調大后看看優化效果。
小結
**Go 中的空結構體 **struct{}{}
不占用任何內存,而且有很清晰的語義性質 (作為占位符使用) 。除了剛才示例中實現 Set
類型功能外, 還可以使用空結構體作為 通道信號標識
、空對象
等,各種使用場景請讀者自行探索。
彩蛋
除了空結構體 struct{}{}
之外,還有一個鮮為人知的內存大小為 0 的數據類型是: 空數組
。但是相對 struct{}{}
豐富的表達性,空數組
使用的場景很少。
package main
import (
"fmt"
"unsafe"
)
func main() {
fmt.Printf("size = %d\\n", unsafe.Sizeof([0]int{}))
}
$ go run main.go
# 輸出如下
size = 0
-
go語言
+關注
關注
1文章
158瀏覽量
9047
發布評論請先 登錄
相關推薦
評論