golang compiler autogenerated method

直接调用

package main

type user struct {
        age int
}

func (u user) say() {
        println("hello")
}

func main() {
        u := user{13}
        u.say()
}

在上面的代码里,user 类型的方法say() 使用value 语义,编译后,观察生成的代码:

$ go build -gcflags="-S" say.go  2>&1 | less
"".user.say t=1 size=86 args=0x8 locals=0x18
        .................................
        0x001d 00029 (/tmp/test/say.go:9)       CALL    runtime.printlock(SB)
        0x0022 00034 (/tmp/test/say.go:14)      LEAQ    go.string."hello"(SB), AX
        0x0029 00041 (/tmp/test/say.go:9)       MOVQ    AX, (SP)
        0x002d 00045 (/tmp/test/say.go:9)       MOVQ    $5, 8(SP)
        0x0036 00054 (/tmp/test/say.go:9)       PCDATA  $0, $0
        0x0036 00054 (/tmp/test/say.go:9)       CALL    runtime.printstring(SB)
        0x003b 00059 (/tmp/test/say.go:9)       PCDATA  $0, $0
        0x003b 00059 (/tmp/test/say.go:9)       CALL    runtime.printnl(SB)
        0x0040 00064 (/tmp/test/say.go:9)       PCDATA  $0, $0
        0x0040 00064 (/tmp/test/say.go:9)       CALL    runtime.printunlock(SB)
        ..........................
"".(*user).say t=1 dupok size=194 args=0x8 locals=0x38
         .....................................
        0x0041 00065 (<autogenerated>:1)        CALL    runtime.printlock(SB)
        0x0046 00070 (<autogenerated>:1)        LEAQ    go.string."hello"(SB), AX
        0x004d 00077 (<autogenerated>:1)        MOVQ    AX, (SP)
        0x0051 00081 (<autogenerated>:1)        MOVQ    $5, 8(SP)
        0x005a 00090 (<autogenerated>:1)        PCDATA  $0, $1
        0x005a 00090 (<autogenerated>:1)        CALL    runtime.printstring(SB)
        0x005f 00095 (<autogenerated>:1)        PCDATA  $0, $1
        0x005f 00095 (<autogenerated>:1)        CALL    runtime.printnl(SB)
        0x0064 00100 (<autogenerated>:1)        PCDATA  $0, $1
        0x0064 00100 (<autogenerated>:1)        CALL    runtime.printunlock(SB)
        ..........................

上面可以看到, 编译器生成了多余的方法, (*user).say ,它的功能貌似包含了user.say 的功能, 观察最后生成的二进制文件:

$ go tool objdump say |grep  ' main\.'
TEXT main.main(SB) /tmp/test/say.go
        say.go:11       0x44d604        ebaa                    JMP main.main(SB)
TEXT main.init(SB) /tmp/test/say.go
        say.go:15       0x44d65d        ebb1                    JMP main.init(SB)

刚才编译时生成的user.say, (*user).say 哪去了?优化掉了?

$ go build -gcflags="-m" say.go  
./say.go:7: can inline user.say
./say.go:11: can inline main
./say.go:13: inlining call to user.say
<autogenerated>:1: inlining call to user.say
<autogenerated>:1: (*user).say .this does not escape

最后,应该是调用了user.say 方法,只是被内联进main() ,最终二进制里看不见了 。 关掉inline 看看怎么样?

$go build -gcflags="-S -l" say.go  2>&1 |  less
"".(*user).say t=1 dupok size=166 args=0x8 locals=0x38
      0x0048 00072 (<autogenerated>:1)        CALL    "".user.say(SB)

这里(*user).say 调用 user.say, 为什么生成不使用的(*user).say ?

user 的say方法 改成pointer 语义:

package main

type user struct {
        age int
}

func (u *user) say() {
        println("hello")
}

func main() {
        u := &user{13}
        u.say()
}

观察编译结果:

$ go build -gcflags="-S" say.go  2>&1 | less
 "".(*user).say t=1 size=86 args=0x8 locals=0x18
        0x0000 00000 (/tmp/test/say.go:7)       TEXT    "".(*user).say(SB), $24-8
        0x0000 00000 (/tmp/test/say.go:7)       MOVQ    (TLS), CX
        0x0009 00009 (/tmp/test/say.go:7)       CMPQ    SP, 16(CX)
        ...................

只有(*user).say , 没有user.say, 观察二进制:

$ go tool objdump  say | grep  ' main\.'
TEXT main.main(SB) /tmp/test/say.go
        say.go:11       0x44d616        eb98                    JMP main.main(SB)
TEXT main.init(SB) /tmp/test/say.go
        say.go:15       0x44d66d        ebb1                    JMP main.init(SB)

观察优化分析:

$go  build -gcflags="-m" say.go 
./say.go:7: can inline (*user).say
./say.go:11: can inline main
./say.go:13: inlining call to (*user).say
./say.go:7: (*user).say u does not escape
./say.go:12: main &user literal does not escape

使用value 语义定义方法,编译器就暗自生成多余的带有pointer 语义的方法,实现同样的功能。 如果用户已经定义pointer 语义方法,就不再生成value 语义的方法。 仿佛编译器就是要确保类型上要有pointer 方法,猜测原因: golang 的interface 是隐式满足,只要某个接口约束的方法集,某个类型全部都有,就自动满足, 保不准别的代码定义了类似:

type speaker interface {
        say() 
}

而接口内部表达是:

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

包含两个pointer, 其中data 指向 接口实现者: method receiver ,接口内部实现使用pointer 语义。 现在的user 类型怎么去满足speaker ,为了内存布局的兼容, user 类型的say() 方法也需要绑定在*user上。

后来搜索到,有老外观察到这个问题,解释的很好

间接调用

根据上面老外的链接,autogenerated method ,在接口上迂回调用时,才能用上。

package main

type speaker interface {
        say()
}

type user struct {
        age int
}

func (u user) say() {
        println("hello")
}

func main() {
        var s speaker
        u := user{13}
        s = u
        s.say()
}

二进制文件dump:

$ go tool objdump  say  |grep  ' main\.'
TEXT main.user.say(SB) /tmp/test/say.go
        say.go:11       0x44d6b4        ebaa                    JMP main.user.say(SB)
TEXT main.main(SB) /tmp/test/say.go
        say.go:15       0x44d723        eb9b                    JMP main.main(SB)
TEXT main.init(SB) /tmp/test/say.go
        say.go:21       0x44d77d        ebb1                    JMP main.init(SB)
TEXT main.(*user).say(SB) <autogenerated>
        <autogenerated>:1       0x44d83d        e93effffff              JMP main.(*user).say(SB)

autogenerated 的(*user).say 进入了二进制文件, 大小 964404 . 在println后加上panic() 调用,可以观察到 s.say() ->(*user).say -> user.say 的调用链.

package main

type speaker interface {
        say()
}

type user struct {
        age int
}

func (u *user) say() {
        println("hello")
}

func main() {
        var s speaker
        u := &user{13}
        s = u
        s.say()
}

二进制文件dump:

$ go tool objdump say |grep  ' main\.'
TEXT main.(*user).say(SB) /tmp/test/say.go
        say.go:11       0x44d604        ebaa                    JMP main.(*user).say(SB)
TEXT main.main(SB) /tmp/test/say.go
        say.go:15       0x44d665        eba9                    JMP main.main(SB)
TEXT main.init(SB) /tmp/test/say.go
        say.go:21       0x44d6bd        ebb1                    JMP main.init(SB)

二进制文件大小960231, 调用链: s.say() ->(*user).say

转载于:https://my.oschina.net/evilunix/blog/1305726

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值