Go: 编译器针对 map[string] 的特殊优化 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
GopherDaily
V2EX    Go 编程语言

Go: 编译器针对 map[string] 的特殊优化

  •  
  •   GopherDaily 2024-09-05 18:29:48 +08:00 2125 次点击
    这是一个创建于 402 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我们构造个非常简化的例子来看一些比较有意义的事情.

    package main import ( "testing" ) func BenchmarkMapStringWithString(b *testing.B) { for i := 0; i < b.N; i++ { getByString(m, key) } } func BenchmarkMapStringWithBytes(b *testing.B) { for i := 0; i < b.N; i++ { getByBytes(m, key) } } var ( m = map[string]bool{"hello": true} key = []byte("hello") ) //go:noinline func getByString(m map[string]bool, key []byte) bool { k := string(key) return m[k] } //go:noinline func getByBytes(m map[string]bool, key []byte) bool { return m[string(key)] } 

    上述两个 benchmark 的逻辑其实是完全相同的, 但 getByBytes 会显著的快于 getByString.

     go test . --bench . goos: darwin goarch: arm64 pkg: github.com/j2gg0s/j2gg0s/examples/go-map-string-optimize BenchmarkMapStringWithString-10 155190159 7.467 ns/op BenchmarkMapStringWithBytes-10 231703806 5.156 ns/op PASS ok github.com/j2gg0s/j2gg0s/examples/go-map-string-optimize 3.982s 

    这是因为 Go 的编译器有一些针对性的优化, cmd/gc: optimized map[string] lookup from []byte key. 简单的说, 就是当你通过 bytes 去访问 map[string] 时, 编译器会省略将 bytes 转化为 string 的步骤.

    我们首先看常规例子, getByString 的编译结果, 其:

    • 首先调用 slicebytetostring 将 []byte 转换为 stirng
    • 再调用 mapaccess1_faststr 访问 map[string]
    go tool objdump main | grep -A 20 "TEXT main.getByString" TEXT main.getByString(SB) /Users/j2gg0s/go/src/github.com/j2gg0s/j2gg0s/examples/go-map-string-optimize/main.go main.go:15 0x45d260 493b6610 CMPQ SP, 0x10(R14) main.go:15 0x45d264 763f JBE 0x45d2a5 main.go:15 0x45d266 55 PUSHQ BP main.go:15 0x45d267 4889e5 MOVQ SP, BP main.go:15 0x45d26a 4883ec40 SUBQ $0x40, SP main.go:15 0x45d26e 48895c2458 MOVQ BX, 0x58(SP) main.go:17 0x45d273 4889442450 MOVQ AX, 0x50(SP) main.go:16 0x45d278 488d442420 LEAQ 0x20(SP), AX main.go:16 0x45d27d 0f1f00 NOPL 0(AX) main.go:16 0x45d280 e87bc8feff CALL runtime.slicebytetostring(SB) main.go:17 0x45d285 4889c1 MOVQ AX, CX main.go:17 0x45d288 4889df MOVQ BX, DI main.go:17 0x45d28b 488d058e790000 LEAQ 0x798e(IP), AX main.go:17 0x45d292 488b5c2450 MOVQ 0x50(SP), BX main.go:17 0x45d297 e8a416fbff CALL runtime.mapaccess1_faststr(SB) main.go:17 0x45d29c 0fb600 MOVZX 0(AX), AX main.go:17 0x45d29f 4883c440 ADDQ $0x40, SP main.go:17 0x45d2a3 5d POPQ BP main.go:17 0x45d2a4 c3 RET main.go:15 0x45d2a5 4889442408 MOVQ AX, 0x8(SP) 

    而触发了编译器优化的例子, getByBytes, 则不需要 slicebytetostring.

    go tool objdump main | grep -A 20 "TEXT main.getByBytes" TEXT main.getByBytes(SB) /Users/j2gg0s/go/src/github.com/j2gg0s/j2gg0s/examples/go-map-string-optimize/main.go main.go:21 0x45d2e0 493b6610 CMPQ SP, 0x10(R14) main.go:21 0x45d2e4 762b JBE 0x45d311 main.go:21 0x45d2e6 55 PUSHQ BP main.go:21 0x45d2e7 4889e5 MOVQ SP, BP main.go:21 0x45d2ea 4883ec20 SUBQ $0x20, SP main.go:21 0x45d2ee 48895c2438 MOVQ BX, 0x38(SP) main.go:22 0x45d2f3 4889cf MOVQ CX, DI main.go:22 0x45d2f6 4889d9 MOVQ BX, CX main.go:22 0x45d2f9 4889c3 MOVQ AX, BX main.go:22 0x45d2fc 488d051d790000 LEAQ 0x791d(IP), AX main.go:22 0x45d303 e83816fbff CALL runtime.mapaccess1_faststr(SB) main.go:22 0x45d308 0fb600 MOVZX 0(AX), AX main.go:22 0x45d30b 4883c420 ADDQ $0x20, SP main.go:22 0x45d30f 5d POPQ BP main.go:22 0x45d310 c3 RET main.go:21 0x45d311 4889442408 MOVQ AX, 0x8(SP) main.go:21 0x45d316 48895c2410 MOVQ BX, 0x10(SP) main.go:21 0x45d31b 48894c2418 MOVQ CX, 0x18(SP) main.go:21 0x45d320 48897c2420 MOVQ DI, 0x20(SP) main.go:21 0x45d325 e816ccffff CALL runtime.morestack_noctxt.abi0(SB) 

    这种优化的前提是 Go 用个指向首地址的指针和长度来表示 string, 和 bytes 的表示方法基本相同. unsafe.String(ptr *byte, len IntegerType) string 是有力的佐证.

    Link: https://github.com/j2gg0s/j2gg0s/blob/main/_posts/2024-09-06-go-optimize-of-the-compiler-for-map-string.md

    4 条回复    2024-09-06 00:25:49 +08:00
    povsister
        1
    povsister  
       2024-09-05 18:41:17 +08:00 via iPhone
    这编译器优化都是老黄历了
    unsafe.String
    GopherDaily
        2
    GopherDaily  
    OP
       2024-09-05 18:43:12 +08:00
    @povsister
    - 是的,但是不妨碍我最近才看到
    - unsafe.String 只是随便找个例子,看看 string 的结构
    Kisesy
        3
    Kisesy  
       2024-09-05 19:49:01 +08:00   1
    还有一个 string 转 []byte 的优化, https://colobu.com/2024/08/13/string-bytes-benchmark/
    securityCoding
        4
    securityCoding  
       2024-09-06 00:25:49 +08:00 via Android
    有个点不理解是为什么 map 这种容器当初不定制接口
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2464 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 22ms UTC 15:31 PVG 23:31 LAX 08:31 JFK 11:31
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86