Golang内部构件第1部分:自动生成的函数(以及如何摆脱它们)
也许如果您像Min IO的我们一样,现在时不时地遇到Golang调用堆栈中的“自动生成”函数,并想知道它们的全部含义是什么?
前一天,有一个案例,调用堆栈显示如下内容:
cmd.retryStorage.ListDir(0x12847c0, 0xc420402e70, 0x1, ...) minio/cmd/retry-storage.go:183 +0x72 cmd.(*retryStorage).ListDir(0xc4201624b0, 0xdf3b5f, 0xe, 0x25, ...) :932 +0xaf cmd.cleanupDir.func1(0xf18f1ec540, 0x25, 0x24, 0xde7eb5) minio/cmd/object-api-com.go:215 +0xe1 cmd.cleanupDir(0x1284860, 0xc4201624b0, 0xdf3b5f, ...) minio/cmd/object-api-com.go:231 +0x1d1
如您所见,第二个函数看起来类似于第一个函数(值接收器,在我们的MinIO代码中定义minio/cmd/retry-storage.go:183),但是它取而代之的是指针接收器。同样令人怀疑的是,第二个功能仅显示了一个行号,但是缺少源代码的文件名。
创建此调用堆栈的代码源自以下函数,在该函数中声明了(递归)函数,然后依次调用storage.ListDir,从而storage将传递给cleanupDir。
// Cleanup a directory recursively.
func cleanupDir(storage StorageAPI, volume, dirPath string) error {
var delFunc func(string) error
// Function to delete entries recursively.
delFunc = func(entryPath string) error {
// List directories
entries, err := storage.ListDir(volume, entryPath)
if err != nil {
return err
}
// Recurse and delete all other entries
for _, entry := range entries {
err = delFunc(pathJoin(entryPath, entry))
if err != nil {
return err
}
}
return nil
}
return delFunc(retainSlash(pathJoin(dirPath)))
}查看上面的代码,我们将假定storage.ListDir立即调用(值接收器),而无需cmd.(*retryStorage).ListDir在两者之间调用指针接收器版本()。那么这是怎么回事?
自动生成的功能
首先让我们检查一下该指针接收器函数的全部含义。通过运行,go tool objdump -s ListDir minio我们可以获取该函数的定义,并且可以看到它显然是:
TEXT minio/cmd.(*retryStorage).ListDir(SB) <autogenerated> <autogenerated>:926 0x1f2e00 GS MOVQ GS:0x8a0, CX <autogenerated>:926 0x1f2e09 CMPQ 0x10(CX), SP <autogenerated>:926 0x1f2e0d JBE 0x1f2f42 <autogenerated>:926 0x1f2e13 SUBQ $0x78, SP <autogenerated>:926 0x1f2e17 MOVQ BP, 0x70(SP) <autogenerated>:926 0x1f2e1c LEAQ 0x70(SP), BP <autogenerated>:926 0x1f2e21 MOVQ 0x20(CX), BX <autogenerated>:926 0x1f2e25 TESTQ BX, BX <autogenerated>:926 0x1f2e28 JE 0x1f2e3a <autogenerated>:926 0x1f2e2a LEAQ 0x80(SP), DI <autogenerated>:926 0x1f2e32 CMPQ DI, 0(BX) <autogenerated>:926 0x1f2e35 JNE 0x1f2e3a <autogenerated>:926 0x1f2e37 MOVQ SP, 0(BX) <autogenerated>:926 0x1f2e3a MOVQ 0x80(SP), AX <autogenerated>:926 0x1f2e42 TESTQ AX, AX <autogenerated>:926 0x1f2e45 JE 0x1f2efa <autogenerated>:926 0x1f2e4b MOVQ 0x80(SP), AX <autogenerated>:926 0x1f2e53 MOVQ 0(AX), CX <autogenerated>:926 0x1f2e56 MOVQ CX, 0(SP) <autogenerated>:926 0x1f2e5a LEAQ 0x8(AX), SI <autogenerated>:926 0x1f2e5e LEAQ 0x8(SP), DI <autogenerated>:926 0x1f2e63 MOVQ BP, -0x10(SP) <autogenerated>:926 0x1f2e68 LEAQ -0x10(SP), BP <autogenerated>:926 0x1f2e6d CALL 0x5d5a4 <autogenerated>:926 0x1f2e72 MOVQ 0(BP), BP <autogenerated>:926 0x1f2e76 MOVQ 0x88(SP), AX <autogenerated>:926 0x1f2e7e MOVQ AX, 0x28(SP) <autogenerated>:926 0x1f2e83 MOVQ 0x90(SP), AX <autogenerated>:926 0x1f2e8b MOVQ AX, 0x30(SP) <autogenerated>:926 0x1f2e90 MOVQ 0x98(SP), AX <autogenerated>:926 0x1f2e98 MOVQ AX, 0x38(SP) <autogenerated>:926 0x1f2e9d MOVQ 0xa0(SP), AX <autogenerated>:926 0x1f2ea5 MOVQ AX, 0x40(SP) <autogenerated>:926 0x1f2eaa CALL cmd.retryStorage.ListDir(SB) <autogenerated>:926 0x1f2eaf MOVQ 0x48(SP), AX <autogenerated>:926 0x1f2eb4 MOVQ 0x50(SP), CX <autogenerated>:926 0x1f2eb9 MOVQ 0x58(SP), DX <autogenerated>:926 0x1f2ebe MOVQ 0x60(SP), BX <autogenerated>:926 0x1f2ec3 MOVQ 0x68(SP), SI <autogenerated>:926 0x1f2ec8 MOVQ AX, 0xa8(SP) <autogenerated>:926 0x1f2ed0 MOVQ CX, 0xb0(SP) <autogenerated>:926 0x1f2ed8 MOVQ DX, 0xb8(SP) <autogenerated>:926 0x1f2ee0 MOVQ BX, 0xc0(SP) <autogenerated>:926 0x1f2ee8 MOVQ SI, 0xc8(SP) <autogenerated>:926 0x1f2ef0 MOVQ 0x70(SP), BP <autogenerated>:926 0x1f2ef5 ADDQ $0x78, SP <autogenerated>:926 0x1f2ef9 RET <autogenerated>:926 0x1f2efa LEAQ 0x96c2bb(IP), AX <autogenerated>:926 0x1f2f01 MOVQ AX, 0(SP) <autogenerated>:926 0x1f2f05 MOVQ $0x3, 0x8(SP) <autogenerated>:926 0x1f2f0e LEAQ 0x976870(IP), AX <autogenerated>:926 0x1f2f15 MOVQ AX, 0x10(SP) <autogenerated>:926 0x1f2f1a MOVQ $0xc, 0x18(SP) <autogenerated>:926 0x1f2f23 LEAQ 0x9707a2(IP), AX <autogenerated>:926 0x1f2f2a MOVQ AX, 0x20(SP) <autogenerated>:926 0x1f2f2f MOVQ $0x7, 0x28(SP) <autogenerated>:926 0x1f2f38 CALL runtime.panicwrap(SB) <autogenerated>:926 0x1f2f3d JMP 0x1f2e4b <autogenerated>:926 0x1f2f42 CALL runtime.morestack_noctxt(SB) <autogenerated>:926 0x1f2f47 JMP cmd.(*retryStorage).ListDir(SB)
因此,我们可以看到,大约在偏移量的一半处0x1f2eaa,它将调用retryStorage.ListDir我们在Golang代码中定义的(值接收器)minio/cmd/retry-storage.go:183。在此之前,它将在堆栈上设置所有参数,包括取消引用*retryStorage并根据retryStorage.ListDir需要在堆栈上创建其副本。
后CALL返回自变量(偏移加载0x1f2eaf通过对0x1f2ec3)和复制到相应的槽(偏移0x1f2ec8通过以0x1f2ee8用于返回参数)(*retryStorage).ListDir之后,自动生成的函数返回。
因此,(*retryStorage).ListDir本质上所有要做的就是包装对的调用,retryStorage.ListDir并确保指针接收者指向的对象没有被修改。
Golang为什么这样做?
现在我们知道了(*retryStorage).ListDir,接下来的问题是为什么Golang会这样做?这与以下事实有关StorageAPI:接口类型,因此storagein中的参数cleanupDir是接口值,该接口值实际上是两指针结构:接口值表示为两字对,提供了指向有关所存储类型信息的指针接口中的指针和指向相关数据的指针。
因此,当我们调用时,storage.ListDir会陷入这样的情况:我们只能访问的指针,retryStorage而只能使用的值接收器方法ListDir。这样一来,Golang编译器就可以很好地为我们(自动)生成它,尽管它确实要付出可观的执行成本,因为我们需要取消引用对象并复制返回参数等。感谢@thatcks提供了澄清在这一点上。
如果您不想要这个怎么办?
好吧,首先,您不一定需要“修复”任何东西,因为没有内在的错误,您的代码也可以正常运行。
但是,如果您确实要修复它,那么它看起来很简单:只需将其更改func (f retryStorage) ListDir(...)为func (f *retryStorage) ListDir(...)at,minio/cmd/retry-storage.go:183然后按照以下步骤进行设置:
TEXT github.com/minio/minio/cmd.(*retryStorage).ListDir(SB) retry-storage.go:199 0x15edc0 GS MOVQ GS:0x8a0, CX retry-storage.go:199 0x15edc9 CMPQ 0x10(CX), SP retry-storage.go:199 0x15edcd JBE 0x15efe5 <rest omitted>
显然,您将要确保函数的实现不会偶然更改的值,f因为您随后将开始注意到这一点(无论如何,这很可能有点怪异,因为更改内容的意图f可能是呼叫者会注意到这一点,这在值接收器函数中不会发生)。
而且,作为一项免费赠品,您将刮掉可执行文件大小的数百个字节(仅用于此单个功能,对于仅*在源代码文件中添加单个文件就不会有不好的结果……)。
结论
希望这篇博客文章能给您一些关于Golang内部功能的见解,并阐明这些自动生成的功能是什么以及您将如何使用它们。
在本系列的后续文章中,我们还将详细介绍指针接收器相对于值接收器功能的性能。