转载自:https://www.cnblogs.com/shuiyuejiangnan/p/9707066.html
string 与 []byte 的直接转换是通过底层数据 copy 实现的,
1 2 |
var a = []byte("hello boy") var b = string(a) |
这种操作在并发量达到十万百万级别的时候会拖慢程序的处理速度。
通过 gdb 调试来看一下 string 和 []byte 的数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
(gdb) l main.main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 s := "hello, world!" 9 b := []byte(s) 10 11 fmt.Println(s, b) (gdb) b 11 Breakpoint 1 at 0x487cd9: file /export/home/machao/src/test/strbytes.go, line 11. (gdb) r Starting program: /export/home/machao/src/test/test1 Breakpoint 1, main.main () at /export/home/machao/src/test/strbytes.go:11 11 fmt.Println(s, b) (gdb) info locals s = { str = 0x4b8ccf "hello, world!level 3 resetload64 failednil stackbaseout of memorys.allocCount=srmount errorstill in listtimer expiredtriggerRatio=unreachable: value method xadd64 failedxchg64 failed nmidlelocked= on "..., len = 13} b = {array = 0xc4200140e0 "hello, world!", len = 13, cap = 16} (gdb) ptype s type = struct string { uint8 *str; int len; } (gdb) ptype b type = struct []uint8 { uint8 *array; int len; int cap; } |
转换后 [ ]byte 底层数组与原 string 内部指针并不相同,以此可确定数据被复制。那么,如不修改数据,仅转换类型,是否可避开复制,从而提升性能?
从 ptype 输出的结构来看,string 可看做 [2]uintptr,而 []byte 则是 [3]uintptr,这便于我们编写代码,无需额外定义结构类型。如此,str2bytes 只需构建 [3]uintptr{ptr, len, len},而 bytes2str 更简单,直接转换指针类型,忽略掉 cap 即可。
通过 unsafe.Pointer(指针转换)和 uintptr(指针运算)实现转换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package main import ( "fmt" "strings" "unsafe" ) func str2bytes(s string) []byte { x := (*[2]uintptr)(unsafe.Pointer(&s)) h := [3]uintptr{x[0], x[1], x[1]} return *(*[]byte)(unsafe.Pointer(&h)) } func bytes2str(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } func main() { s := strings.Repeat("abc", 3) b := str2bytes(s) s2 := bytes2str(b) fmt.Println(b, s2) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
ZBMAC-C02VQ2BTH:test XXX$ go build -gcflags “-m" -o test main.go # command-line-arguments ./main.go:9:6: can inline str2bytes ./main.go:15:6: can inline bytes2str ./main.go:21:16: inlining call to str2bytes ./main.go:22:17: inlining call to bytes2str ./main.go:9:28: str2bytes s does not escape ./main.go:10:36: str2bytes &s does not escape ./main.go:12:35: str2bytes &h does not escape ./main.go:15:26: leaking param: b to result ~rl level=0 ./main.go:16:35: bytes2str &b does not escape ./main.go:23:13: b escapes to heap ./main.go:23:13: s2 escapes to heap ./main.go:21:16: main &s does not escape ./main.go:21:16: main &h does not escape ./main.go:22:17: main &b does not escape ./main.go:23:13: main ... argument does not escape |
没有出现逃逸现象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package main import ( "testing" "io/ioutil" "time" "fmt" ) var s, _ = ioutil.ReadFile("mydata4vipday.720.datx") func test() { b := string(s) _ = []byte(b) } func test2() { b := bytes2str(s) _ = str2bytes(b) } func BenchmarkTest(b *testing.B) { t1 := time.Now() for i := 0; i < b.N; i++ { test() } fmt.Println("test", time.Now().Sub(t1), b.N) } func BenchmarkTestBlock(b *testing.B) { t1 := time.Now() for i := 0; i < b.N; i++ { test2() } fmt.Println("test block", time.Now().Sub(t1), b.N) } |
对比一下优化前后的性能差异
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
ZBMAC-C02VQ2BTH:test XXX$ go test -v -bench . -benchmem test 26.11237ms 1 goos: darwin goarch: amd64 BenchmarkTest-8 test 206.728605ms 50 test 1.100685701s 300 300 3669038 ns/op 39075857 B/op 2 allocs/op test block 756ns 1 BenchmarkTestBlock-8 test block 1.04µs 100 test block 33.199µs 10000 test block 3.24411ms 1000000 test block 322.757612ms 100000000 test block 1.614465444s 500000000 500000000 3.23 ns/op 0 B/op 0 allocs/op PASS ok _/Users/XXX/Go/src/test 3.298s |
没有额外开辟内存:0B/op,执行效率:5 亿次耗时 1.6 秒;而不用 unsafe.Pointer 和 uintptr 转换 300 次耗时就达到了 1.1 秒,效率对比高下立判。