七、Golang-高效字符串连接

简单整两句

字符串连接是一个老生常谈的问题了,但因为它在程序中使用的频次实在是太高了,所以这里再次强调一下。

上代码

这段代码使用了几种常见的方式做字符串连接操作,分别是fmt.Sprintf、strings.Builder、bytes.Buffer和+=。

package concat_string

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
	"testing"
)

const numbers = 100

func BenchmarkSprintf(b *testing.B) {
	b.ResetTimer()
	for idx := 0; idx < b.N; idx++ {
		var s string
		for i := 0; i < numbers; i++ {
			s = fmt.Sprintf("%v%v", s, i)
		}
	}
	b.StopTimer()
}

func BenchmarkStringBuilder(b *testing.B) {
	b.ResetTimer()
	for idx := 0; idx < b.N; idx++ {
		var builder strings.Builder
		for i := 0; i < numbers; i++ {
			builder.WriteString(strconv.Itoa(i))

		}
		_ = builder.String()
	}
	b.StopTimer()
}

func BenchmarkBytesBuf(b *testing.B) {
	b.ResetTimer()
	for idx := 0; idx < b.N; idx++ {
		var buf bytes.Buffer
		for i := 0; i < numbers; i++ {
			buf.WriteString(strconv.Itoa(i))
		}
		_ = buf.String()
	}
	b.StopTimer()
}

func BenchmarkStringAdd(b *testing.B) {
	b.ResetTimer()
	for idx := 0; idx < b.N; idx++ {
		var s string
		for i := 0; i < numbers; i++ {
			s += strconv.Itoa(i)
		}

	}
	b.StopTimer()
}

运行这段benchmark测试用例:

$ go test -bench=.
goos: windows
goarch: amd64
pkg: concat_string
BenchmarkSprintf-4                 45138             23350 ns/op
BenchmarkStringBuilder-4         1000000              1181 ns/op
BenchmarkBytesBuf-4               751822              1669 ns/op
BenchmarkStringAdd-4              150050              7773 ns/op
PASS
ok      concat_string   5.515s

其中我们最常使用的+=和fmt.Sprintf,但这两个最常用的方法性能上却是最差的,一旦我们的程序频繁使用这两种方法,将会造成严重的性能问题。
strings.Builder是Go1.10中引入的,它的性能是几个对比方案中最优的。

使用pprof工具分析一下不同的方案的内存情况:

#生成内存profile文件
$ go test -bench=. -memprofile=mem.prof
goos: windows
goarch: amd64
pkg: concat_string
BenchmarkSprintf-4                 49753             23202 ns/op
BenchmarkStringBuilder-4          922437              1180 ns/op
BenchmarkBytesBuf-4               706293              1662 ns/op
BenchmarkStringAdd-4              148202              7870 ns/op
PASS
ok      concat_string   6.364s

#分析内存占用情况
$ go tool pprof mem.prof
Type: alloc_space
Time: Feb 27, 2020 at 9:09am (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top -cum
Showing nodes accounting for 3090.88MB, 88.21% of 3503.97MB total
Showing top 10 nodes out of 12
      flat  flat%   sum%        cum   cum%
         0     0%     0%  3503.97MB   100%  testing.(*B).launch
         0     0%     0%  3503.97MB   100%  testing.(*B).runN
 1465.19MB 41.82% 41.82%  1465.19MB 41.82%  concat_string.BenchmarkStringAdd
     218MB  6.22% 48.04%  1120.62MB 31.98%  concat_string.BenchmarkSprintf
  902.62MB 25.76% 73.80%   902.62MB 25.76%  fmt.Sprintf
         0     0% 73.80%   464.09MB 13.24%  concat_string.BenchmarkBytesBuf
         0     0% 73.80%   454.07MB 12.96%  concat_string.BenchmarkStringBuilder
  454.07MB 12.96% 86.76%   454.07MB 12.96%  strings.(*Builder).WriteString
         0     0% 86.76%   334.07MB  9.53%  bytes.(*Buffer).WriteString
      51MB  1.46% 88.21%   334.07MB  9.53%  bytes.(*Buffer).grow
(pprof)

通过pprof的top命令,我们发现fmt.Sprintf和+=是内存占用最多的,一个合理的解释是,两种方案的内部实现中创建了大量的对象,也就需要大量的垃圾回收(GC),也就更耗性能。

注:

博客内容为极客时间视频课《Go语言从入门到实战》学习笔记。
参考课程链接:
https://time.geekbang.org/course/intro/160?code=NHxez89MnqwIfa%2FvqTiTIaYof1kxYhaEs6o2kf3ZxhU%3D&utm_term=SPoster
博客参考代码:
https://github.com/geektime-geekbang/go_learning/tree/master/code/ch50

若访问github或Google较慢,可使用加速器:
http://91tianlu.date/aff.php?aff=3468

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值