注:本文的灵感来源于GOPHER 2020年大会陈皓的分享,原PPT的链接可能并不方便获取,所以我下载了一份PDF到git仓,方便大家阅读。我将结合自己的实际项目经历,与大家一起细品这份文档。
注:切勿过早优化!
这部分的内容实战项目中用得不多,大家记住耗子叔总结出来的一个原则即可:
尽量用
time.Time
和time.Duration
,如果必须用string,尽量用time.RFC3339
然而现实情况并没有那么理想,实际项目中用得最频繁,还是自定义的2006-01-02 15:04:05
time.Now().Format("2006-01-02 15:04:05")
主要性能差异是由于Sprint
针对的是复杂的字符串拼接,底层有个buffer,会在它的基础上进行一些字符串的拼接;
而Itoa
直接通过一些位操作组合出字符串。
// 170 ns/op
func Benchmark_Sprint(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = fmt.Sprint(rand.Int())
}
}
// 81.9 ns/op
func Benchmark_Itoa(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = strconv.Itoa(rand.Int())
}
}
主要了解go的string
到[]byte
的转换还是比较耗性能的,但大部分情况下无法避免这种转换。
我们注意一种场景即可:从[]byte
转换为string
,再转换为[]byte
。
// 43.9 ns/op
func Benchmark_String2Bytes(b *testing.B) {
data := "Hello world"
w := ioutil.Discard
for i := 0; i < b.N; i++ {
w.Write([]byte(data))
}
}
// 3.06 ns/op
func Benchmark_Bytes(b *testing.B) {
data := []byte("Hello world")
w := ioutil.Discard
for i := 0; i < b.N; i++ {
w.Write(data)
}
}
了解slice的扩容机制就能很容易地理解。切片越长,影响越大。
var size = 1000
// 4494 ns/op
func Benchmark_NoCap(b *testing.B) {
for n := 0; n < b.N; n++ {
data := make([]int, 0)
for k := 0; k < size; k++ {
data = append(data, k)
}
}
}
// 2086 ns/op
func Benchmark_Cap(b *testing.B) {
for n := 0; n < b.N; n++ {
data := make([]int, 0, size)
for k := 0; k < size; k++ {
data = append(data, k)
}
}
}
频繁拼接字符串的场景并不多,了解即可。
var strLen = 10000
// 0.0107 ns/op
func Benchmark_StringAdd(b *testing.B) {
var str string
for n := 0; n < strLen; n++ {
str += "x"
}
}
// 0.000154 ns/op
func Benchmark_StringBuilder(b *testing.B) {
var builder strings.Builder
for n := 0; n < strLen; n++ {
builder.WriteString("x")
}
}
// 0.000118 ns/op
func Benchmark_BytesBuffer(b *testing.B) {
var buffer bytes.Buffer
for n := 0; n < strLen; n++ {
buffer.WriteString("x")
}
}
注意一下,一定要是热点
,千万不要 过早优化
除了常用的CAS
操作,还有atomic.Value
的Store
和Load
操作,这里简单地放个实例:
func main() {
v := atomic.Value{}
type demo struct {
a int
b string
}
v.Store(&demo{
a: 1,
b: "hello",
})
data, ok := v.Load().(*demo)
fmt.Println(data, ok)
// &{1 hello} true
}
复杂场景下,还是建议用mutex
。
bufio.NewReader()
和bufio.NewWriter()
// 如果匹配的格式不会变化,全局只初始化一次即可
var compiled = regexp.MustCompile(`^[a-z]+[0-9]+$`)
func main() {
fmt.Println(compiled.MatchString("test123"))
fmt.Println(compiled.MatchString("test1234"))
}
go项目内部通信尽量用protobuf
,但如果是对外提供api,比如web前端,json
格式更方便。
var size = 1000000
// 0.0442 ns/op
func Benchmark_MapInt(b *testing.B) {
var m = make(map[int]struct{})
for i := 0; i < size; i++ {
m[i] = struct{}{}
}
b.ResetTimer()
for n := 0; n < size; n++ {
_, _ = m[n]
}
}
// 0.180 ns/op
func Benchmark_MapString(b *testing.B) {
var m = make(map[string]struct{})
for i := 0; i < size; i++ {
m[strconv.Itoa(i)] = struct{}{}
}
b.ResetTimer()
for n := 0; n < size; n++ {
_, _ = m[strconv.Itoa(n)]
}
}
示例中strconv.Itoa
函数对性能多少有点影响,但可以看到string
和int
的差距是在数量级的。
PPT中给出了8个扩展阅读,大家根据情况自行阅读。
如果说你的时间只够读一个材料的话,我推荐大家反复品读一下Effective Go