go拼接字符串不如php,Go语言字符串高效拼接(三)

在上一篇关于字符串拼接的文章Go语言字符串高效拼接(二) 中,我们终于为Builder拼接正名了,果真不负众望,尤其是拼接的字符串越来越多时,其性能的优越性更加明显。

在上一篇的结尾中,我留下悬念说其实还有优化的空间,这就是今天这篇文章,字符串拼接系列的第三篇,也是字符串拼接的最后一篇产生的原因,今天我们就看下如何再提升Builder的性能。关于第一篇字符串高效拼接的文章可点击

Go语言字符串高效拼接(一) 查看。

Builder 慢在哪

既然要优化Builder拼接,那么我们起码知道他慢在哪,我们继续使用我们上篇文章的测试用例,运行看下性能。

Builder10-8 5000000 258 ns/op 480 B/op 4 allocs/op

Builder100-8 1000000 2012 ns/op 6752 B/op 8 allocs/op

Builder1000-8 100000 21016 ns/op 96224 B/op 16 allocs/op

Builder10000-8 10000 195098 ns/op 1120226 B/op 25 allocs/op

针对既然要优化Builder拼接,采取了10、100、1000、10000四种不同数量的字符串进行拼接测试。我们发现每次操作都有不同次数的内存分配,内存分配越多,越慢,如果引起GC,就更慢了,首先我们先优化这个,减少内存分配的次数。

内存分配优化

通过cpuprofile,查看生成的火焰图可以得知,runtime.growslice函数会被频繁的调用,并且时间占比也比较长。我们查看Builder.WriteString的源代码:

func (b *Builder) WriteString(s string) (int, error) {

b.copyCheck()

b.buf = append(b.buf, s...)

return len(s), nil

}

可以肯定是append方法触发了runtime.growslice,因为b.buf的容量cap不足,所以需要调用runtime.growslice扩充b.buf的容量,然后才可以追加新的元素s...。扩容容量自然会涉及到内存的分配,而且追加的内容越多,内容分配的次数越多,这和我们上面性能测试的数据是一样的。

既然问题的原因找到了,那么我们就可以优化了,核心手段就是减少runtime.growslice调用,甚至不调用。照着这个思路的话,我们就要提前为b.buf分配好容量cap。幸好Builder为我们提供了扩充容量的方法Grow,我们在进行WriteString之前,先通过Grow方法,扩充好容量即可。

现在开始改造我们的StringBuilder函数。

//blog:www.flysnow.org

//微信公众号:flysnow_org

func StringBuilder(p []string,cap int) string {

var b strings.Builder

l:=len(p)

b.Grow(cap)

for i:=0;i

b.WriteString(p[i])

}

return b.String()

}

增加一个参数cap,让使用者告诉我们需要的容量大小。Grow方法的实现非常简单,就是一个通过make函数,扩充b.buf大小,然后再拷贝b.buf的过程。

func (b *Builder) grow(n int) {

buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)

copy(buf, b.buf)

b.buf = buf

}

那么现在我们的性能测试用例变成如下:

func BenchmarkStringBuilder10(b *testing.B) {

p:= initStrings(10)

cap:=10*len(BLOG)

b.ResetTimer()

for i:=0;i

StringBuilder(p,cap)

}

}

func BenchmarkStringBuilder1000(b *testing.B) {

p:= initStrings(1000)

cap:=1000*len(BLOG)

b.ResetTimer()

for i:=0;i

StringBuilder(p,cap)

}

}

为了说明情况和简短代码,这里只有10和1000个元素的用例,其他类似。为了把性能优化到极致,我一次性把需要的容量分配足够。现在我们再运行性能(Benchmark)测试代码。

Builder10-8 10000000 123 ns/op 352 B/op 1 allocs/op

Builder100-8 2000000 898 ns/op 2688 B/op 1 allocs/op

Builder1000-8 200000 7729 ns/op 24576 B/op 1 allocs/op

Builder10000-8 20000 78678 ns/op 237568 B/op 1 allocs/op

性能足足翻了1倍多,只有1次内存分配,每次操作占用的内存也减少了一半多,降低了GC。

小结

这次优化,到了这里,算是结束了,写出来后,大家也会觉得不难,其背后的原理也非常情况,就是预先分配内存,减少append过程中的内存重新分配和数据拷贝,这样我们就可以提升很多的性能。所以对于可以预见的长度的切,都可以提前申请申请好内存。

字符串拼接的系列,到这里结束了,一共三个系列,希望对大家所有帮助。

本文为原创文章,转载注明出处,「总有烂人抓取文章的时候还去掉我的原创说明」欢迎扫码关注公众号flysnow_org或者网站http://www.flysnow.org/,第一时间看后续精彩文章。「防烂人备注**……&*¥」觉得好的话,顺手分享到朋友圈吧,感谢支持。

有疑问加站长微信联系(非本文作者)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值