为何Golang的memmove(copy/append)性能差异如此显著?
性能问题求助:两段Go代码的memmove性能差异分析
我近期遇到一个难以解释的性能问题,希望能理解两段代码性能差异巨大的原因。
我正尝试重构一个库的代码以避免性能退化,目标是实现一个内存io.Writer,支持持续追加数据且无需像bytes.Buffer那样执行昂贵的重分配与复制操作。我的解决方案是使用字节切片的切片([][]byte),写入时分配新的字节切片——外层切片可能需要重分配,但相较于数据规模它很小,且实际数据写入后无需再复制。
基础结构体定义如下:
type noReallocWriter struct { data [][]byte idx int off int }
第一个实现:固定容量切片版本
所有独立字节切片都以固定容量分配,Write时向其中复制数据直到满,再分配新的切片。我原以为这会受益于大块连续数据缓冲区及较低的分配频率,但实际写入场景中性能反而更差。
func (w *noReallocWriter) Write(p []byte) (int, error) { lenP := len(p) if lenP == 0 { return 0, nil } for len(p) > 0 { if w.idx == len(w.data) { w.data = append(w.data, make([]byte, 0, FIXED_SIZE)) } curData := w.data[w.idx] n := copy(curData[w.off:cap(curData)], p) w.data[w.idx] = curData[:w.off+n] w.off += n p = p[n:] if w.off >= cap(curData) { w.idx++ w.off = 0 } } return lenP, nil }
第二个实现:可变容量切片版本
为排查问题,我编写了另一个版本,更贴近旧版代码的行为:每次Write都分配一个与输入数据长度匹配容量的新字节切片,并将数据复制进去。
func (w *noReallocWriter) Write(p []byte) (n int, err error) { if w.idx != len(w.data) { panic("noReallocWriter only supports appending to the end.") } if len(p) == 0 { return 0, nil } buf := make([]byte, 0, len(p)) buf = append(buf, p...) w.data = append(w.data, buf) w.idx++ return len(p), nil }
测试结果与疑问
- 在仅测试写入大量数据的极简合成测试中,两者性能相近;但在应用场景的合成测试中,第二个版本性能显著更优,固定容量版本的memmove耗时是后者的3-4倍。
- 两种实现的耗时基本都在memmove(
copy/append)操作上,但固定容量切片版本的耗时分布在memmove_amd64.s的372-375行,而可变容量缓冲区版本的耗时全部集中在375行。 - 测试模式:两个版本写入相同数据与模式,写入大小在小(35字节)和大(略小于256KB,可变)之间交替。测试了32KB到1MB间不同的
FIXED_SIZE值,64KB到256KB区间性能相近,超出该范围性能下降。
我疑惑的是:为何复制相同数据、块大小相近的情况下,memmove的执行分布与耗时差异如此大?
内容的提问来源于stack exchange,提问作者Zach
相关产品推荐
相关产品推荐

