Golang内部构件第1部分:自动生成的函数(以及如何摆脱它们)

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内部功能的见解,并阐明这些自动生成的功能是什么以及您将如何使用它们。

在本系列的后续文章中,我们还将详细介绍指针接收器相对于值接收器功能的性能。


上一篇 下一篇