入参为interface 会触发变量逃逸
以fmt.Sprintf 举例说明
func main() {
hello := "hello"
fmt.Println(fmt.Sprintf("%s", hello))
}
go build -gcflags=-m main.go
# command-line-arguments
./main.go:9:13: inlining call to fmt.Println
./main.go:9:13: ... argument does not escape
./main.go:9:25: fmt.Sprintf("%s", ... argument...) escapes to heap
./main.go:9:25: ... argument does not escape
./main.go:9:32: hello escapes to heap
发现变量hello逃逸到堆上了
更进一步的,查看反编译代码,可以发现调用了 convTstring 将 字符串 转换成interface(eface)结构
- go build -gcflags "-N -l" -o main main.go
- go tool objdump -s "main.main" main
TEXT main.main(SB) /home/liujun.helloworld/app/go/src/go_test/test_cal/main.go
main.go:7 0x80da6a0 658b0d00000000 MOVL GS:0, CX
main.go:7 0x80da6a7 8b89fcffffff MOVL 0xfffffffc(CX), CX
main.go:7 0x80da6ad 3b6108 CMPL SP, 0x8(CX)
main.go:7 0x80da6b0 0f8667010000 JBE 0x80da81d
main.go:7 0x80da6b6 83ec64 SUBL $0x64, SP
main.go:8 0x80da6b9 8d05a63e0f08 LEAL 0x80f3ea6, AX
main.go:8 0x80da6bf 8944241c MOVL AX, 0x1c(SP)
main.go:8 0x80da6c3 c744242005000000 MOVL $0x5, 0x20(SP)
main.go:9 0x80da6cb c744245c00000000 MOVL $0x0, 0x5c(SP)
main.go:9 0x80da6d3 c744246000000000 MOVL $0x0, 0x60(SP)
main.go:9 0x80da6db 8d44245c LEAL 0x5c(SP), AX
main.go:9 0x80da6df 8944243c MOVL AX, 0x3c(SP)
main.go:9 0x80da6e3 8b44241c MOVL 0x1c(SP), AX
main.go:9 0x80da6e7 8b4c2420 MOVL 0x20(SP), CX
main.go:9 0x80da6eb 890424 MOVL AX, 0(SP)
main.go:9 0x80da6ee 894c2404 MOVL CX, 0x4(SP)
main.go:9 0x80da6f2 e89916fdff CALL runtime.convTstring(SB)
main.go:9 0x80da6f7 8b442408 MOVL 0x8(SP), AX
main.go:9 0x80da6fb 89442438 MOVL AX, 0x38(SP)
main.go:9 0x80da6ff 8b4c243c MOVL 0x3c(SP), CX
main.go:9 0x80da703 8401 TESTB AL, 0(CX)
main.go:9 0x80da705 8d15204c0e08 LEAL 0x80e4c20, DX
main.go:9 0x80da70b 8911 MOVL DX, 0(CX)
main.go:9 0x80da70d 8b1598901908 MOVL runtime.writeBarrier(SB), DX
main.go:9 0x80da713 85d2 TESTL DX, DX
main.go:9 0x80da715 7402 JE 0x80da719
main.go:9 0x80da717 eb02 JMP 0x80da71b
main.go:9 0x80da719 eb0f JMP 0x80da72a
main.go:9 0x80da71b e8c0abfdff CALL runtime.gcWriteBarrier2(SB)
main.go:9 0x80da720 8907 MOVL AX, 0(DI)
main.go:9 0x80da722 8b5104 MOVL 0x4(CX), DX
main.go:9 0x80da725 895704 MOVL DX, 0x4(DI)
main.go:9 0x80da728 eb00 JMP 0x80da72a
main.go:9 0x80da72a 894104 MOVL AX, 0x4(CX)
main.go:9 0x80da72d 8b44243c MOVL 0x3c(SP), AX
main.go:9 0x80da731 8400 TESTB AL, 0(AX)
main.go:9 0x80da733 eb00 JMP 0x80da735
main.go:9 0x80da735 89442440 MOVL AX, 0x40(SP)
main.go:9 0x80da739 c744244401000000 MOVL $0x1, 0x44(SP)
main.go:9 0x80da741 c744244801000000 MOVL $0x1, 0x48(SP)
main.go:9 0x80da749 8d0d263d0f08 LEAL 0x80f3d26, CX
main.go:9 0x80da74f 890c24 MOVL CX, 0(SP)
main.go:9 0x80da752 c744240402000000 MOVL $0x2, 0x4(SP)
main.go:9 0x80da75a 89442408 MOVL AX, 0x8(SP)
main.go:9 0x80da75e c744240c01000000 MOVL $0x1, 0xc(SP)
main.go:9 0x80da766 c744241001000000 MOVL $0x1, 0x10(SP)
main.go:9 0x80da76e e80d95ffff CALL fmt.Sprintf(SB)
main.go:9 0x80da773 8b442414 MOVL 0x14(SP), AX
main.go:9 0x80da777 8b4c2418 MOVL 0x18(SP), CX
main.go:9 0x80da77b 89442454 MOVL AX, 0x54(SP)
main.go:9 0x80da77f 894c2458 MOVL CX, 0x58(SP)
main.go:9 0x80da783 c744244c00000000 MOVL $0x0, 0x4c(SP)
main.go:9 0x80da78b c744245000000000 MOVL $0x0, 0x50(SP)
main.go:9 0x80da793 8d44244c LEAL 0x4c(SP), AX
main.go:9 0x80da797 89442428 MOVL AX, 0x28(SP)
main.go:9 0x80da79b 8b442454 MOVL 0x54(SP), AX
main.go:9 0x80da79f 8b4c2458 MOVL 0x58(SP), CX
main.go:9 0x80da7a3 890424 MOVL AX, 0(SP)
main.go:9 0x80da7a6 894c2404 MOVL CX, 0x4(SP)
main.go:9 0x80da7aa e8e115fdff CALL runtime.convTstring(SB)
main.go:9 0x80da7af 8b442408 MOVL 0x8(SP), AX
main.go:9 0x80da7b3 89442424 MOVL AX, 0x24(SP)
main.go:9 0x80da7b7 8b4c2428 MOVL 0x28(SP), CX
main.go:9 0x80da7bb 8401 TESTB AL, 0(CX)
main.go:9 0x80da7bd 8d15204c0e08 LEAL 0x80e4c20, DX
main.go:9 0x80da7c3 8911 MOVL DX, 0(CX)
main.go:9 0x80da7c5 8b1598901908 MOVL runtime.writeBarrier(SB), DX
main.go:9 0x80da7cb 85d2 TESTL DX, DX
main.go:9 0x80da7cd 7402 JE 0x80da7d1
main.go:9 0x80da7cf eb02 JMP 0x80da7d3
main.go:9 0x80da7d1 eb0f JMP 0x80da7e2
main.go:9 0x80da7d3 e808abfdff CALL runtime.gcWriteBarrier2(SB)
main.go:9 0x80da7d8 8907 MOVL AX, 0(DI)
main.go:9 0x80da7da 8b5104 MOVL 0x4(CX), DX
main.go:9 0x80da7dd 895704 MOVL DX, 0x4(DI)
main.go:9 0x80da7e0 eb00 JMP 0x80da7e2
main.go:9 0x80da7e2 894104 MOVL AX, 0x4(CX)
main.go:9 0x80da7e5 8b442428 MOVL 0x28(SP), AX
main.go:9 0x80da7e9 8400 TESTB AL, 0(AX)
main.go:9 0x80da7eb eb00 JMP 0x80da7ed
main.go:9 0x80da7ed 8944242c MOVL AX, 0x2c(SP)
main.go:9 0x80da7f1 c744243001000000 MOVL $0x1, 0x30(SP)
main.go:9 0x80da7f9 c744243401000000 MOVL $0x1, 0x34(SP)
main.go:9 0x80da801 890424 MOVL AX, 0(SP)
main.go:9 0x80da804 c744240401000000 MOVL $0x1, 0x4(SP)
main.go:9 0x80da80c c744240801000000 MOVL $0x1, 0x8(SP)
main.go:9 0x80da814 e8d795ffff CALL fmt.Println(SB)
main.go:10 0x80da819 83c464 ADDL $0x64, SP
main.go:10 0x80da81c c3 RET
main.go:7 0x80da81d e88e97fdff CALL runtime.morestack_noctxt(SB)
main.go:7 0x80da822 e979feffff JMP main.main(SB)
convTstring 方法涉及 堆内存分配
必知必会系列-interface
// convTstring should be an internal detail,
// but widely used packages access it using linkname.
// Notable members of the hall of shame include:
// - github.com/bytedance/sonic
//
// Do not remove or change the type signature.
// See go.dev/issue/67401.
//
//go:linkname convTstring
func convTstring(val string) (x unsafe.Pointer) {
if val == "" {
x = unsafe.Pointer(&zeroVal[0])
} else {
x = mallocgc(unsafe.Sizeof(val), stringType, true)
*(*string)(x) = val
}
return
}
旁路逻辑如何不增加主逻辑耗时?
使用闭包捕获需要的变量 最后异步执行,从而保证旁路逻辑不影响主流程耗时
type UpdLog struct {
ToExec []func(*UpdLog)
}
type UpdRequest struct {
}
func Upd(ctx context.Context, upd *UpdRequest) {
updLog := &UpdLog{}
result1 := DoUpdStepOne() // 主流程 1
updLog.DoThingA(result1) // 旁路逻辑 依赖主流程1 耗时操作
result2 := DoUpdStepTwo() // 主流程 2
updLog.DoThingB(result2) // 旁路逻辑 依赖主流程2 耗时操作
DoUpdStepThree() // 主流程 3
// 异步处理
go func() {
updLog.DoThings()
}()
}
type StepResult struct {
}
func DoUpdStepOne() *StepResult {
return nil
}
func DoUpdStepTwo() *StepResult {
return nil
}
func DoUpdStepThree() *StepResult {
return nil
}
func (u *UpdLog) DoThingA(r *StepResult) {
u.ToExec = append(u.ToExec, func(u *UpdLog) {
// 耗时操作
})
}
func (u *UpdLog) DoThingB(r *StepResult) {
u.ToExec = append(u.ToExec, func(u *UpdLog) {
// 耗时操作
})
}
func (u *UpdLog) DoThings() {
for _, f := range u.ToExec {
f(u)
}
}