用于替换 golang源码包中 gofmt 格式化的工具,可以实现自动对代码中struct进行字节对齐的功能。通过增加这一功能,可以使代码更加整洁、易读,提高代码质量和可维护性,并且通过字节对齐,达到节约内存的极致体验
- 仓库地址 gofmt)
- gofmt 懂的都懂,本工具拥有与gofmt一模一样的功能,还增加了字节对齐格式化的能力,使用 -a 控制
CPU 访问内存时,并不是逐个字节访问,而是以字长(word size)为单位访问。比如 32 位的 CPU ,字长为 4 字节,那么 CPU 访问内存的单位也是 4 字节。 这么设计的目的,是减少 CPU 访问内存的次数,提升 CPU 访问内存的吞吐量。比如同样读取 8 个字节的数据,一次读取 4 个字节那么只需要读取 2 次。
CPU 始终以字长访问内存,如果不进行内存对齐,很可能增加 CPU 访问内存的次数,例如:
img.png 变量 a、b 各占据 3 字节的空间,内存对齐后,a、b 占据 4 字节空间,CPU 读取 b 变量的值只需要进行一次内存访问。 如果不进行内存对齐,CPU 读取 b 变量的值需要进行 2 次内存访问。第一次访问得到 b 变量的第 1 个字节,第二次访问得到 b 变量的后两个字节。
从这个例子中也可以看到,内存对齐对实现变量的原子性操作也是有好处的,每次内存访问是原子的,如果变量的大小不超过字长,那么内存对齐后, 对该变量的访问就是原子的,这个特性在并发场景下至关重要。
内存对齐可以提高内存读写性能,并且便于实现原子性操作。
| 类型 | 大小 |
|---|---|
| bool | 1个字节 |
| intN,uintN,floatN,comlexN | N/8个字节(例如 float64是8个字节) |
| int,uint,uintptr | 1个字 |
| *T | 1个字 |
| string | 2个字(数据、长度) |
| []T | 3个字(数据、长度、内容) |
| map | 1个字 |
| func | 1个字 |
| chan | 1个字 |
| interface | 2个字(类型) |
字长跟CPU相关,32位CPU一个字长就是4字节,64位CPU一个字长是8字节
type People struct { has bool //1 Where []int //24 MachineTime time.Time // 24 Name string // 16 donot interface{} //16 name string //16 Age int // 8 inte uintptr //8 Loves []int // 24 d []int //24 sign chan struct{} //8 age int // 8 a int8 //1 c struct { a string c map[string]int b int32 // } haa struct { } // 8+8+4 =20 e []int //24 b struct{} // 0 } func main() { fmt.Println("before sort", unsafe.Sizeof(People{})) // 256 }
gofmt -a -w file.go
type People struct { b struct{} // 0 c struct { a string c map[string]int b int32 // } haa struct { } // 8+8+4 =20 Loves []int // 24 MachineTime time.Time // 24 d []int //24 Where []int //24 e []int //24 Name string // 16 donot interface{} //16 name string //16 Age int // 8 age int // 8 inte uintptr //8 sign chan struct{} //8 has bool //1 a int8 //1 } // 排序后一个对象占用少了16个字节 func main() { fmt.Println("after sort", unsafe.Sizeof(People{})) // 240 }
- 本工具仅支持 Golang 代码的字节对齐。
- go version >=1.18
- 对于对象引用类型不参与排序,置于底部,因为涉及到跨文件访问,这里没有对对象引用进行处理,可以个人略微计算一下进行调整
如果您对本工具有任何建议或改进意见,欢迎提交 issue 或 pull request。
MIT License
感谢您使用工具!祝您编程愉快!