今天水群的时候发现群友发了一段他两年前写的代码,因为怕被开盒,这里我简单改写一下:
if info[readidx] == []byte("$")[0] {
goto handle_err
}
readidx++
乍一看没什么问题,但好巧不前,前几天我在看golang源码的时候在bytes/bytes.go中发现这样一段注释:
// Equal reports whether a and b
// are the same length and contain the same bytes.
// A nil argument is equivalent to an empty slice.
func Equal(a, b []byte) bool {
// Neither cmd/compile nor gccgo allocates for these string conversions.
return string(a) == string(b)
}
Neither cmd/compile nor gccgo allocates for these string conversions.
我们知道,在Go中的string是不可能变的,而[]byte是能够修改的,在进行类型转换的时候一般会发生拷贝,也就是将[]byte指向的数据拷贝到一块新的地址中,但是这里却说不会发生内存的分配?(有过Java背景的可能已经猜出答案了,但是还请各位赏个脸接着看下去吧:P)
当时发现这点的时候我以为是我漏看了什么地方,但是就算我去翻runtime也找不到什么有用的信息。
现在我唯一知道的是,在将[]byte转成string是会重新分配内存,如果没有分配,那么或许只有一种可能,在编译的时候这段代码被优化了。
关于[]byte和string转换的优化其实我也有一定了解和经验,但是这次实在是抓瞎了。
干脆直接把这段注释喂给Google(其实是bing)算了……
然后我发现了ahfuzhang的博客,看来发现这一点的不只有我一个人:(
有点失落(bushi
从他的博客里可以得出这样的结论:编译器做了优化,而且不止在 return 处,在 if 中的类型转换也会被优化。
好了,扯了这么多,接下来是正文了
为了探究群友的代码是否也会被优化,我写了以下这段 benchmark ——请dalao先不要骂,我知道写的有很大问题:)
const zen = "Crazy Thursday v me ¥50"
func Benchmark_Convt2_string_InIf(b *testing.B) {
bytes := []byte(zen)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if string(bytes) == zen {
}
}
}
func Benchmark_Convt2_byte_slice_InIf(b *testing.B) {
str := zen
b.ResetTimer()
for i := 0; i < b.N; i++ {
if 'C' == []byte(str)[0] {
}
}
}
结果如下:(这里只有相等的情况,虽然对于实际结果来说可能没有太大影响,但是考虑的不够全面qs是被dalao打脸了,wuuu好疼>_<)
Benchmark_Convt2_string_InIf-16 384023348 3.106 ns/op Benchmark_Convt2_byte_slice_InIf-16 360352352 3.314 ns/op
...
看样子群友的代码并没有被优化……不不不,等一下,slice可是有3个字段啊,相比string多了一个cap……
可惜我太菜,完全不知道会造成什么影响,但是因为这个不清楚的因素,这次benchmark的结果也变得无力了qwq
接下来就只剩下最后一招了(对我来说),看下编译后的结果:
(嫌太长的可以使劲往后翻=w=,绝对不是为了水字数,哼╭(╯^╰)╮)
main.main STEXT size=170 args=0x0 locals=0x60 funcid=0x0 align=0x0
0x0000 00000 (D:\misc\code\lab\main.go:3) TEXT main.main(SB), ABIInternal, $96-0
0x0000 00000 (D:\misc\code\lab\main.go:3) CMPQ SP, 16(R14)
0x0004 00004 (D:\misc\code\lab\main.go:3) PCDATA $0, $-2
0x0004 00004 (D:\misc\code\lab\main.go:3) JLS 159
0x000a 00010 (D:\misc\code\lab\main.go:3) PCDATA $0, $-1
0x000a 00010 (D:\misc\code\lab\main.go:3) SUBQ $96, SP
0x000e 00014 (D:\misc\code\lab\main.go:3) MOVQ BP, 88(SP)
0x0013 00019 (D:\misc\code\lab\main.go:3) LEAQ 88(SP), BP
0x0018 00024 (D:\misc\code\lab\main.go:3) FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0018 00024 (D:\misc\code\lab\main.go:3) FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0018 00024 (D:\misc\code\lab\main.go:6) MOVQ $7526676583359279683, DX
0x0022 00034 (D:\misc\code\lab\main.go:6) MOVQ DX, main..autotmp_3+31(SP)
0x0027 00039 (D:\misc\code\lab\main.go:6) MOVQ $8511936754934313589, DX
0x0031 00049 (D:\misc\code\lab\main.go:6) MOVQ DX, main..autotmp_3+39(SP)
0x0036 00054 (D:\misc\code\lab\main.go:6) MOVQ $2339092762162656114, DX
0x0040 00064 (D:\misc\code\lab\main.go:6) MOVQ DX, main..autotmp_3+40(SP)
0x0045 00069 (D:\misc\code\lab\main.go:6) MOVQ $3473864931355420013, DX
0x004f 00079 (D:\misc\code\lab\main.go:6) MOVQ DX, main..autotmp_3+48(SP)
0x0054 00084 (D:\misc\code\lab\main.go:8) LEAQ main..autotmp_3+31(SP), AX
0x0059 00089 (D:\misc\code\lab\main.go:8) LEAQ go.string."Crazy Thursday v me ¥50"(SB), BX
0x0060 00096 (D:\misc\code\lab\main.go:8) MOVL $25, CX
0x0065 00101 (D:\misc\code\lab\main.go:8) PCDATA $1, $0
0x0065 00101 (D:\misc\code\lab\main.go:8) CALL runtime.memequal(SB)
0x006a 00106 (D:\misc\code\lab\main.go:13) LEAQ main..autotmp_6+56(SP), AX
0x006f 00111 (D:\misc\code\lab\main.go:13) LEAQ go.string."Crazy Thursday v me ¥50"(SB), BX
0x0076 00118 (D:\misc\code\lab\main.go:13) MOVL $25, CX
0x007b 00123 (D:\misc\code\lab\main.go:13) NOP
0x0080 00128 (D:\misc\code\lab\main.go:13) CALL runtime.stringtoslicebyte(SB)
0x0085 00133 (D:\misc\code\lab\main.go:13) TESTQ BX, BX
0x0088 00136 (D:\misc\code\lab\main.go:13) JLS 148
0x008a 00138 (D:\misc\code\lab\main.go:15) PCDATA $1, $-1
0x008a 00138 (D:\misc\code\lab\main.go:15) MOVQ 88(SP), BP
0x008f 00143 (D:\misc\code\lab\main.go:15) ADDQ $96, SP
0x0093 00147 (D:\misc\code\lab\main.go:15) RET
0x0094 00148 (D:\misc\code\lab\main.go:13) XORL AX, AX
0x0096 00150 (D:\misc\code\lab\main.go:13) MOVQ AX, CX
0x0099 00153 (D:\misc\code\lab\main.go:13) PCDATA $1, $0
0x0099 00153 (D:\misc\code\lab\main.go:13) CALL runtime.panicIndex(SB)
0x009e 00158 (D:\misc\code\lab\main.go:13) XCHGL AX, AX
0x009f 00159 (D:\misc\code\lab\main.go:13) NOP
0x009f 00159 (D:\misc\code\lab\main.go:3) PCDATA $1, $-1
0x009f 00159 (D:\misc\code\lab\main.go:3) PCDATA $0, $-2
0x009f 00159 (D:\misc\code\lab\main.go:3) NOP
0x00a0 00160 (D:\misc\code\lab\main.go:3) CALL runtime.morestack_noctxt(SB)
0x00a5 00165 (D:\misc\code\lab\main.go:3) PCDATA $0, $-1
0x00a5 00165 (D:\misc\code\lab\main.go:3) JMP 0
这里关注两行:
// ...some code
0x0065 00101 (D:\misc\code\lab\main.go:8) CALL runtime.memequal(SB)
// ...some code
0x0080 00128 (D:\misc\code\lab\main.go:13) CALL runtime.stringtoslicebyte(SB)
// ...some code
到这里就明了了!转换成string后被优化,是直接比较的内存,而转换成[]byte会发生转换,然后才进行比较!
好了,按理说到此一起应该已经清晰了(大概)
但是我还是要吐槽一句:Go的string是不可变的,再加上这里是直接比较的内存地址,这很难不让我联想到Java的字符串常量池!