达到相同目的,可以有多种写法,每种写法有性能、可读性方面的区别,本文旨在探讨不同写法之间的性能差异
len(str) vs str == ""
本部分参考自:
问个 Go 问题,字符串 len == 0 和 字符串== "" ,有啥区别?
package gotest func Test1() bool { var v string if v == "" { return true } return false } func Test2() bool { var v string if len(v) == 0 { return true } return false }
package gotest import ( "testing" ) func BenchmarkTest1(b *testing.B) { for i := 0; i < b.N; i++ { Test1() } } func BenchmarkTest2(b *testing.B) { for i := 0; i < b.N; i++ { Test2() } }
执行 go test -test.bench=".*"
goos: darwin goarch: amd64 pkg: note/performance BenchmarkTest1-8 1000000000 0.467 ns/op BenchmarkTest2-8 1000000000 0.464 ns/op PASS ok note/performance 1.290s
第4行显示了BenchmarkTest1执行了1000000000次,每次的执行平均时间是0.467纳秒,
第5行显示了BenchmarkTest2也执行了1000000000次,每次的平均执行时间是0.464 纳秒。
最后一行显示总共的执行时间为 1.290s
可使用-count
来指定执行多少次 go test -test.bench=".*" -count=5
:
goos: darwin goarch: amd64 pkg: note/performance BenchmarkTest1-8 1000000000 0.485 ns/op BenchmarkTest1-8 1000000000 0.484 ns/op BenchmarkTest1-8 1000000000 0.464 ns/op BenchmarkTest1-8 1000000000 0.497 ns/op BenchmarkTest1-8 1000000000 0.479 ns/op BenchmarkTest2-8 1000000000 0.490 ns/op BenchmarkTest2-8 1000000000 0.476 ns/op BenchmarkTest2-8 1000000000 0.482 ns/op BenchmarkTest2-8 1000000000 0.469 ns/op BenchmarkTest2-8 1000000000 0.474 ns/op PASS ok note/performance 5.791s
go test --bench=. -benchmem
(添加 -benchmem
参数,可以提供每次操作分配内存的次数,以及每次操作分配的字节数。参考 go benchmark 性能测试)
goos: darwin goarch: amd64 pkg: note/performance BenchmarkTest1-8 1000000000 0.471 ns/op 0 B/op 0 allocs/op BenchmarkTest2-8 1000000000 0.462 ns/op 0 B/op 0 allocs/op PASS ok note/performance 1.457s
经过多次测试,可知:
<1>. 性能几乎没有差别
<2>. 均不涉及内存申请和操作,均为 0 allocs/op。(也说明变量并不是声明了,就有初始化动作. Go 编译器有做优化)
进一步看两者的汇编代码,以细究具体区别在哪里:
go tool compile -S gotest.go:
"".Test1 STEXT nosplit size=6 args=0x8 locals=0x0 0x0000 00000 (gotest.go:3) TEXT "".Test1(SB), NOSPLIT|ABIInternal, $0-8 0x0000 00000 (gotest.go:3) PCDATA $0, $-2 0x0000 00000 (gotest.go:3) PCDATA $1, $-2 0x0000 00000 (gotest.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (gotest.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (gotest.go:3) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (gotest.go:6) PCDATA $0, $0 0x0000 00000 (gotest.go:6) PCDATA $1, $0 0x0000 00000 (gotest.go:6) MOVB $1, "".~r0+8(SP) 0x0005 00005 (gotest.go:6) RET 0x0000 c6 44 24 08 01 c3 .D$... "".Test2 STEXT nosplit size=6 args=0x8 locals=0x0 0x0000 00000 (gotest.go:11) TEXT "".Test2(SB), NOSPLIT|ABIInternal, $0-8 0x0000 00000 (gotest.go:11) PCDATA $0, $-2 0x0000 00000 (gotest.go:11) PCDATA $1, $-2 0x0000 00000 (gotest.go:11) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (gotest.go:11) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (gotest.go:11) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (gotest.go:14) PCDATA $0, $0 0x0000 00000 (gotest.go:14) PCDATA $1, $0 0x0000 00000 (gotest.go:14) MOVB $1, "".~r0+8(SP) 0x0005 00005 (gotest.go:14) RET 0x0000 c6 44 24 08 01 c3 .D$... go.cuinfo.packagename. SDWARFINFO dupok size=0 0x0000 67 6f 74 65 73 74 gotest go.loc."".Test1 SDWARFLOC size=0 go.info."".Test1 SDWARFINFO size=46 0x0000 03 22 22 2e 54 65 73 74 31 00 00 00 00 00 00 00 ."".Test1....... 0x0010 00 00 00 00 00 00 00 00 00 00 01 9c 00 00 00 00 ................ 0x0020 01 0f 7e 72 30 00 01 03 00 00 00 00 00 00 ..~r0......... rel 0+0 t=24 type.bool+0 rel 10+8 t=1 "".Test1+0 rel 18+8 t=1 "".Test1+6 rel 28+4 t=30 gofile../Users/dashen/go/src/note/performance/gotest.go+0 rel 40+4 t=29 go.info.bool+0 go.range."".Test1 SDWARFRANGE size=0 go.debuglines."".Test1 SDWARFMISC size=11 0x0000 04 02 14 06 41 04 01 03 7b 06 01 ....A...{.. go.loc."".Test2 SDWARFLOC size=0 go.info."".Test2 SDWARFINFO size=46 0x0000 03 22 22 2e 54 65 73 74 32 00 00 00 00 00 00 00 ."".Test2....... 0x0010 00 00 00 00 00 00 00 00 00 00 01 9c 00 00 00 00 ................ 0x0020 01 0f 7e 72 30 00 01 0b 00 00 00 00 00 00 ..~r0......... rel 0+0 t=24 type.bool+0 rel 10+8 t=1 "".Test2+0 rel 18+8 t=1 "".Test2+6 rel 28+4 t=30 gofile../Users/dashen/go/src/note/performance/gotest.go+0 rel 40+4 t=29 go.info.bool+0 go.range."".Test2 SDWARFRANGE size=0 go.debuglines."".Test2 SDWARFMISC size=13 0x0000 04 02 03 08 14 06 41 04 01 03 73 06 01 ......A...s.. gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8 0x0000 01 00 00 00 00 00 00 00 ........
编译出来的汇编代码是完全一致的,可以明确 Go 编译器对此做了优化(应该是直接比对了)
----- EOF选看: -----
生成pprof:
go test -bench=".*" -cpuprofile=cpu.profile ../xxx文件夹
此时会在文件夹下,生成一个 xxx.test
go tool pprof xxx.test cpu.profile
:
File: performance.test Type: cpu Time: Apr 12, 2021 at 5:20pm (CST) Duration: 1.23s, Total samples = 970ms (78.99%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) (pprof) o call_tree = false compact_labels = true cumulative = flat //: [cum | flat] divide_by = 1 drop_negative = false edgefraction = 0.001 focus = "" granularity = filefunctions //: [addresses | filefunctions | files | functions | lines] hide = "" ignore = "" mean = false nodecount = -1 //: default nodefraction = 0.005 noinlines = false normalize = false output = "" prune_from = "" relative_percentages = false sample_index = cpu //: [samples | cpu] show = "" show_from = "" tagfocus = "" taghide = "" tagignore = "" tagshow = "" trim = true trim_path = "" unit = minimum
执行 go tool pprof -web xxx.test cpu.profile
----- EOF -----
几种 int转string 方法的性能差异
package shuang import ( "fmt" "strconv" "testing" ) func BenchmarkSprintf(b *testing.B) { n := 10 b.ResetTimer() for i := 0; i < b.N; i++ { fmt.Sprintf("%d", n) } } func BenchmarkItoa(b *testing.B) { n := 10 b.ResetTimer() for i := 0; i < b.N; i++ { strconv.Itoa(n) } } func BenchmarkFormatInt(b *testing.B) { n := int64(10) b.ResetTimer() for i := 0; i < b.N; i++ { strconv.FormatInt(n, 10) } }
执行 go test -test.bench=".*" -benchmem
goos: darwin goarch: amd64 pkg: dashen BenchmarkSprintf-8 14417409 75.9 ns/op 16 B/op 2 allocs/op BenchmarkItoa-8 452276205 2.64 ns/op 0 B/op 0 allocs/op BenchmarkFormatInt-8 492620018 2.42 ns/op 0 B/op 0 allocs/op PASS ok dashen 4.518s
第4行显示了BenchmarkSprintf-8 执行了14417409次,每次的执行平均时间是75.9纳秒, 每次操作有两次内存分配,每次分配了16Byte大小的内存空间
第5行显示了BenchmarkItoa-8 执行了452276205次,每次的平均执行时间是2.64 纳秒, 无内存分配
第6行显示了BenchmarkFormatInt-8 执行了492620018次,每次的平均执行时间是2.42 纳秒, 无内存分配 。
最后一行显示总共的执行时间为 4.518s
可见, strconv.FormatInt(n, 10)
和 strconv.Itoa(n)
性能差不多, fmt.Sprintf()
性能最差