Go string cmp byte 记一次后知后觉的优化(我宣布个事儿...)

287 阅读2分钟

今天水群的时候发现群友发了一段他两年前写的代码,因为怕被开盒,这里我简单改写一下:

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是会重新分配内存,如果没有分配,那么或许只有一种可能,在编译的时候这段代码被优化了。

关于[]bytestring转换的优化其实我也有一定了解和经验,但是这次实在是抓瞎了。

干脆直接把这段注释喂给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的字符串常量池!

引用:

www.cnblogs.com/ahfuzhang/p…