-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
第 84 期图解 Go 之内存对齐 #588
Comments
内存对齐,前段时间才被坑过......来详细学习了, |
非常感谢 @NewbMiao 的精彩分享,分享中的内容也是 Go 语言里非常有代表性内存对齐问题,而且内存对齐本身是一个非常重要但鲜有提及的话题。 为此,我希望就分享中内存对齐与否产生的性能差异(第 6-7 页)的内容做进一步的讨论。 首先,分享中提到的性能基准测试及其结果并不是有效的,我们不妨选取性能基准测试中存在几个需要指出的问题。 问题1:在 unsafe 包中说明了 unsafe.Pointer 和 uintptr 之间的转换关系。其中分享中所涉及例子的这几行:
首先违反了转换规则 2: "Conversion of a uintptr back to Pointer is not valid in general.",换句话说, 问题2:测量样本数较少。在实际展示的结果是基于 3 次测量样本
可以推算出结果的最坏情况:1.87 * (1-0.05) = 1.78ns,1.47 * (1+0.05) = 1.54ns (取已经观测到的最大误差),最低的提升比例为 (1.78-1.54)/1.78 = 13% 与实际存在的误差比例 5% 相比,结果似乎不那么乐观。 问题3:测试环境不可靠。在给出的容器环境下,perflock 是无法锁定操作系统的 CPU 频率,实际结果仍然受 hyperkit 的影响。
另外,即便是 Linux 下的 cgroup 也仍然不能对计算资源做一个相对稳定的限制。 那么最终这个例子在一台独立的硬件上、简单消除系统噪音后、增加测量样本数后、关闭编译器优化、关闭 GC 、禁用调度器的影响后,一个初步的性能测试的结果显示他们没有区别:
其次,我们必须明确,对齐与非对齐字段的操作速度差异的基准测试,究竟在度量什么指标,我们是在测量 CPU 执行了多个指令周期吗?我们是在测量 CPU 访问内存的延迟吗?等等。 编写这个性能基准测试并不是一件容易的事情,可以说它比几乎能见到的所有性能基准测试都要难且苛刻。因为它需要彻头彻尾的考虑从底层硬件到语言自身中的所有因素:
在我们比较对齐字段和非对齐字段访问速度的差异时,先回答这些问题,远比展示一个执行程序和执行时间的测量结果更加重要。 |
感谢欧神的详细分析,这个对齐字段和非对齐字段访问速度访问差异的压测确实很不严谨。 关于不合理的代码: ptr := unsafe.Pointer(&x.b[9])
// equal to: unsafe.Pointer(uintptr(unsafe.Pointer(&x.b))+9) 另外欧神提到的cpu优化确实没有考虑到,还有好多地方是没有摸到的。 关于cacheLine缓存行影响的话 增加到64bytes ( |
你说的没错。其实,讨论的目的只是尽可能的让基准测试的结果更加严谨,而不是其他。前面提到的问题也只是这个特殊的基准测试里面直观上需要考虑的问题,仍然还有许多影响因素需要考虑,仅做抛砖引玉。最重要的一点,也是前面反复提到过的,我们在比较对齐字段和非对齐字段访问速度时,这个「速度」究竟指的是什么,似乎始终没有被澄清。 关于这个基准测试在其他系统级语言中已经有比较严谨的做法,但是在 Go 中如何进行严谨的测量,甚至能否给出严谨的测量,我想这些问题这些对分享的听众都是非常有趣的,我也期待更加严谨的示例。 |
想要问一个问题,求解惑:
|
@wushuangxiaoyu google-doc 18页底下有描述: |
@NewbMiao 对与x86-64,N byte的数据结构的起始地址必须是N的倍数,我是可以理解的,因为这是x86-64需要遵循的严格要求 |
这个不是 “必须”,比如大于8bytes的数据对齐时,只能保证地址是8的倍数,即按64位系统的机器字大小(8bytes)
这句话应该反过来,是为了能在32位系统上用atomic包处理64位字的原子操作,需要调用方保证该64位字是8bytes对齐,这是atomic包要求的: On x86-32, the 64-bit functions use instructions unavailable before the Pentium MMX.
On non-Linux ARM, the 64-bit functions use instructions unavailable before the ARMv6k core.
**On ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to arrange for 64-bit alignment of 64-bit words accessed atomically**. The first word in a variable or in an allocated struct, array, or slice can be relied upon to be 64-bit aligned. 见:https://golang.org/src/sync/atomic/doc.go?s=1206:1668#L36 具体是下边指令需要: TEXT runtime∕internal∕atomic·Xadd64(SB), NOSPLIT, $0-20
// no XADDQ so use CMPXCHG8B loop
MOVL ptr+0(FP), BP
TESTL $7, BP
JZ 2(PC)
MOVL 0, AX // **crash when unaligned** 至于这个指令为什么需要,以及怎么实现我就不清楚了。 另外,Go其实一直想在32位平台也保证64位字是8bytes对齐,只不过一直没有实现。 |
@NewbMiao 了解了,蟹蟹解惑 |
@NewbMiao 你好,想要问一个问题:64位系统T2比T1多出了8个字节,为什么会做这个padding?对这个0字节的数据做引用,T1.a不需要占用内存吗 |
@sjtuhyh 你说的是
type T1 struct {
a struct{}
x int64
}
type T2 struct {
x int64
a struct{}
} |
@NewbMiao hi,想问个问题,关于final zero field的,没有搞懂为什么空结构体不在最后一个就不需要做padding,如果同样的,有指针指向不处在最后一个的空结构体字段,且该指针一直存活不释放对应内存,就不会有内存泄漏的问题吗? |
@hengli-coder 你好,是否指向zerobase取决于其整体大小是否为零,如果只是field大小为零就不会 |
@jianghaodong-icel 空结构体不在最后一个field, 引用其不释放不被GC不就正常的么。 |
【Go 夜读】 图解Go之内存对齐
关于内存对齐总会有各种声音?为什么要对齐,怎么对齐,不对齐有什么影响么?
这些声音可以离我们很远,也可以很近,比如:
WaitGroup.state1
为什么是[3]uint32
而不是[12]byte
struct
是否占用内存是否合理int64|uint64
时发生panic本次分享借自己研究内存对齐的一些代码及源码示例,为大家带来Go里边的内存对齐是什么样的,以及如何利用内存对齐优化数据结构,提高代码的平台兼容性。
大纲
分享者自我介绍
苗蕾,Thoughtworks,搬砖工。
分享时间
2020-04-02 21:00 UTC+8
分享地址
https://zoom.us/j/6923842137
Slides
google-doc
or
blog-pdf
参考资料
备注
在分享结束后提供。
The text was updated successfully, but these errors were encountered: