本期分享:
1.Go struct内存对齐
2.使用空结构体(struct{})节省内存
Go struct内存对齐
在计算机系统中,CPU 访问内存时并不是逐字节读取的,而是以特定大小的块(通常为 4/8 字节)为单位进行读取。当数据的内存地址正好是其大小的整数倍时,称为自然对齐。Go 编译器会根据平台特性自动进行内存对齐优化,这种机制虽然可能产生填充字节,但能大幅提升内存访问效率。
内存对齐实战案例
javascript
// 未对齐的结构体 (24 bytes)
type BadStruct struct {
a bool // 1 byte
b int64 // 8 bytes
c bool // 1 byte
}
// 对齐优化后的结构体 (16 bytes)
type GoodStruct struct {
b int64 // 8 bytes(偏移量 0)
a bool // 1 byte(偏移量 8)
c bool // 1 byte(偏移量 9)
// 自动填充 6 bytes 到 16 bytes(满足 8 字节对齐)
}
func main() {
fmt.Println(unsafe.Sizeof(BadStruct{})) // 输出 24
fmt.Println(unsafe.Sizeof(GoodStruct{})) // 输出 16
}
优化原理:
BadStruct
中 a
和 c
导致 b
需要填充 7 字节才能对齐
GoodStruct
通过字段排序减少填充,内存占用降低 33%
对齐规则总结
1)结构体整体大小需是最大字段对齐值的整数倍
2)每个字段的偏移量必须能整除其类型大小
3)嵌套结构体继承父结构体的对齐规则
使用空结构体(struct{})节省内存
struct{}
是 Go 语言中唯一零内存的类型:
javascript
fmt.Println(unsafe.Sizeof(struct{}{})) // 输出 0
六大应用场景
1)场景1:实现高效集合(Set)
javascript
type Set map[string]struct{}
func (s Set) Add(key string) {
s[key] = struct{}{}
}
func (s Set) Contains(key string) bool {
_, ok := s[key]
return ok
}
// 使用示例
s := make(Set)
s.Add("apple")
fmt.Println(s.Contains("apple")) // true
2)场景2:通道信号传递
javascript
func worker(stopCh <-chan struct{}) {
for {
select {
case <-stopCh:
return
default:
// 执行任务
}
}
}
// 发送关闭信号
closeCh := make(chanstruct{})
go worker(closeCh)
close(closeCh) // 广播关闭
3)场景3:方法接收器(无状态)
javascript
type Logger struct{}
func (Logger) Info(msg string) {
fmt.Printf("[INFO] %s\n", msg)
}
// 使用零内存接收器
var log Logger
log.Info("service started")
4)场景4:占位通道
javascript
// 限制并发数为 10
sem := make(chan struct{}, 10)
for i := 0; i < 1000; i++ {
sem <- struct{}{}
go func() {
defer func() { <-sem }()
// 业务逻辑
}()
}
5)场景5:接口实现标记
javascript
type Marker interface {
isMarker()
}
type MyMarker struct{}
func (MyMarker) isMarker() {}
// 类型断言检查
func CheckMarker(v interface{}) bool {
_, ok := v.(Marker)
return ok
}
6)场景6:JSON 空对象
javascript
type Response struct {
Data interface{} `json:"data"`
Error struct{} `json:"error,omitempty"`
}
// 序列化时自动忽略空 error 字段
resp := Response{Data: "success"}
jsonData, _ := json.Marshal(resp) // {"data":"success"}
注意事项
1)空结构体作为结构体字段时会产生对齐填充
javascript
type Wrapper struct {
_ struct{} // 0 字节
n int64 // 8 字节(偏移量 0)
}
fmt.Println(unsafe.Sizeof(Wrapper{})) // 8 bytes
2)不同地址的空结构体实例本质相同
javascript
a := struct{}{}
b := struct{}{}
fmt.Println(&a == &b) // 输出 true(编译器优化)
本篇结束~